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
@@ -1,22 +1,26 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
1
4
  root = File.dirname(__FILE__) + '/../'
2
5
 
3
6
  require root + 'vendor/capybara/spec/spec_helper'
4
7
  require root + 'lib/terminus'
5
8
 
6
- class TestApp
7
- template :layout do
8
- <<-HTML
9
- <html>
10
- <head>
11
- <meta http-equiv="Content-type" content="text/html; charset=utf-8">
12
- <title>Capybara Driver Test</title>
13
- </head>
14
- <body>
15
- <%= yield %>
16
- <%= Terminus.driver_script request.host %>
17
- </body>
18
- </html>
19
- HTML
9
+ def select_browser
10
+ if ua = ENV['USER_AGENT']
11
+ Terminus.browser = {:name => ua}
12
+ else
13
+ Terminus.browser = :docked
20
14
  end
21
15
  end
22
16
 
17
+ # We use WEBrick to boot the test app, because if we use Thin (the default) the
18
+ # slow response used to test Ajax resynchronization blocks the event loop. This
19
+ # stops Terminus receiving messages and causes false positives: the client is
20
+ # not really waiting for Ajax to complete, it's just having its messages blocked
21
+ # because EventMachine is frozen.
22
+
23
+ Capybara.server do |app, port|
24
+ handler = Rack::Handler.get('webrick')
25
+ handler.run(app, :Port => port, :AccessLog => [], :Logger => WEBrick::Log::new(nil, 0))
26
+ end
@@ -3,17 +3,19 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
3
  describe Capybara::Driver::Terminus do
4
4
  before do
5
5
  @driver = Capybara::Driver::Terminus.new(TestApp)
6
- Terminus.ensure_docked_browser
7
- Terminus.browser = :docked
6
+ select_browser
8
7
  end
9
8
 
10
9
  after do
11
- Terminus.return_to_dock
10
+ Terminus.browser.return_to_dock
12
11
  end
13
-
12
+
14
13
  it_should_behave_like "driver"
14
+ it_should_behave_like "driver with header support"
15
+ it_should_behave_like "driver with status code support"
15
16
  it_should_behave_like "driver with javascript support"
16
- it_should_behave_like "driver without status code support"
17
+ it_should_behave_like "driver with resynchronization support"
18
+ it_should_behave_like "driver with frame support"
17
19
  it_should_behave_like "driver with support for window switching"
18
20
  it_should_behave_like "driver with cookies support"
19
21
  it_should_behave_like "driver with infinite redirect detection"
@@ -4,29 +4,16 @@ describe Capybara::Session do
4
4
  context 'with terminus driver' do
5
5
  before do
6
6
  @session = Capybara::Session.new(:terminus, TestApp)
7
- Terminus.ensure_docked_browser
8
- Terminus.browser = :docked
7
+ select_browser
9
8
  end
10
9
 
11
10
  after do
12
- Terminus.return_to_dock
11
+ Terminus.browser.return_to_dock
13
12
  end
14
-
15
- describe '#driver' do
16
- it "should be a terminus driver" do
17
- @session.driver.should be_an_instance_of(Capybara::Driver::Terminus)
18
- end
19
- end
20
-
21
- describe '#mode' do
22
- it "should remember the mode" do
23
- @session.mode.should == :terminus
24
- end
25
- end
26
-
13
+
27
14
  it_should_behave_like "session"
28
- it_should_behave_like "session without headers support"
15
+ it_should_behave_like "session with headers support"
16
+ it_should_behave_like "session with status code support"
29
17
  it_should_behave_like "session with javascript support"
30
- it_should_behave_like "session without status code support"
31
18
  end
32
19
  end
metadata CHANGED
@@ -1,12 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terminus
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 2
8
- - 0
9
- version: 0.2.0
4
+ prerelease:
5
+ version: 0.3.0
10
6
  platform: ruby
11
7
  authors:
12
8
  - James Coglan
@@ -14,7 +10,7 @@ autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
12
 
17
- date: 2010-10-24 00:00:00 +01:00
13
+ date: 2011-07-20 00:00:00 +01:00
18
14
  default_executable:
19
15
  dependencies:
20
16
  - !ruby/object:Gem::Dependency
@@ -25,9 +21,6 @@ dependencies:
25
21
  requirements:
26
22
  - - ">="
27
23
  - !ruby/object:Gem::Version
28
- segments:
29
- - 1
30
- - 0
31
24
  version: "1.0"
32
25
  type: :runtime
33
26
  version_requirements: *id001
@@ -39,9 +32,6 @@ dependencies:
39
32
  requirements:
40
33
  - - ">="
41
34
  - !ruby/object:Gem::Version
42
- segments:
43
- - 1
44
- - 2
45
35
  version: "1.2"
46
36
  type: :runtime
47
37
  version_requirements: *id002
@@ -53,9 +43,6 @@ dependencies:
53
43
  requirements:
54
44
  - - ">="
55
45
  - !ruby/object:Gem::Version
56
- segments:
57
- - 0
58
- - 12
59
46
  version: "0.12"
60
47
  type: :runtime
61
48
  version_requirements: *id003
@@ -65,72 +52,88 @@ dependencies:
65
52
  requirement: &id004 !ruby/object:Gem::Requirement
66
53
  none: false
67
54
  requirements:
68
- - - ">="
55
+ - - ~>
69
56
  - !ruby/object:Gem::Version
70
- segments:
71
- - 0
72
- - 5
73
- - 2
74
- version: 0.5.2
57
+ version: 0.6.3
75
58
  type: :runtime
76
59
  version_requirements: *id004
77
60
  - !ruby/object:Gem::Dependency
78
- name: capybara
61
+ name: sinatra
79
62
  prerelease: false
80
63
  requirement: &id005 !ruby/object:Gem::Requirement
81
64
  none: false
82
65
  requirements:
83
66
  - - ">="
84
67
  - !ruby/object:Gem::Version
85
- segments:
86
- - 0
87
- - 4
88
- - 0
89
- version: 0.4.0
68
+ version: "1.0"
90
69
  type: :runtime
91
70
  version_requirements: *id005
92
71
  - !ruby/object:Gem::Dependency
93
- name: sinatra
72
+ name: packr
94
73
  prerelease: false
95
74
  requirement: &id006 !ruby/object:Gem::Requirement
96
75
  none: false
97
76
  requirements:
98
77
  - - ">="
99
78
  - !ruby/object:Gem::Version
100
- segments:
101
- - 1
102
- - 0
103
- version: "1.0"
79
+ version: "3.1"
104
80
  type: :runtime
105
81
  version_requirements: *id006
106
82
  - !ruby/object:Gem::Dependency
107
- name: packr
83
+ name: capybara
108
84
  prerelease: false
109
85
  requirement: &id007 !ruby/object:Gem::Requirement
110
86
  none: false
111
87
  requirements:
112
88
  - - ">="
113
89
  - !ruby/object:Gem::Version
114
- segments:
115
- - 3
116
- - 1
117
- version: "3.1"
90
+ version: 0.4.0
118
91
  type: :runtime
119
92
  version_requirements: *id007
120
93
  - !ruby/object:Gem::Dependency
121
- name: oyster
94
+ name: rack-proxy
122
95
  prerelease: false
123
96
  requirement: &id008 !ruby/object:Gem::Requirement
124
97
  none: false
125
98
  requirements:
126
99
  - - ">="
127
100
  - !ruby/object:Gem::Version
128
- segments:
129
- - 0
130
- - 9
131
- version: "0.9"
101
+ version: "0.3"
132
102
  type: :runtime
133
103
  version_requirements: *id008
104
+ - !ruby/object:Gem::Dependency
105
+ name: useragent
106
+ prerelease: false
107
+ requirement: &id009 !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: "0.3"
113
+ type: :runtime
114
+ version_requirements: *id009
115
+ - !ruby/object:Gem::Dependency
116
+ name: oyster
117
+ prerelease: false
118
+ requirement: &id010 !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: "0.9"
124
+ type: :runtime
125
+ version_requirements: *id010
126
+ - !ruby/object:Gem::Dependency
127
+ name: rspec
128
+ prerelease: false
129
+ requirement: &id011 !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: "0"
135
+ type: :development
136
+ version_requirements: *id011
134
137
  description:
135
138
  email: jcoglan@gmail.com
136
139
  executables:
@@ -141,26 +144,41 @@ extra_rdoc_files:
141
144
  - README.rdoc
142
145
  files:
143
146
  - README.rdoc
144
- - lib/views/index.erb
145
- - lib/views/bookmarklet.erb
146
- - lib/public/loader.js
147
- - lib/public/syn.js
148
- - lib/public/terminus.js
149
- - lib/public/style.css
147
+ - bin/terminus
150
148
  - lib/terminus.rb
151
149
  - lib/terminus/node.rb
150
+ - lib/terminus/proxy.rb
152
151
  - lib/terminus/timeouts.rb
153
152
  - lib/terminus/browser.rb
153
+ - lib/terminus/proxy/driver_body.rb
154
+ - lib/terminus/proxy/external.rb
155
+ - lib/terminus/proxy/rewrite.rb
156
+ - lib/terminus/views/bootstrap.erb
157
+ - lib/terminus/views/infinite.html
158
+ - lib/terminus/views/index.erb
154
159
  - lib/terminus/application.rb
155
160
  - lib/terminus/server.rb
161
+ - lib/terminus/public/loader.js
162
+ - lib/terminus/public/terminus.js
163
+ - lib/terminus/public/icon.png
164
+ - lib/terminus/public/style.css
156
165
  - lib/terminus/controller.rb
166
+ - lib/terminus/host.rb
157
167
  - lib/capybara/driver/terminus.rb
168
+ - spec/reports/firefox.txt
169
+ - spec/reports/chrome.txt
170
+ - spec/reports/opera.txt
171
+ - spec/reports/safari.txt
158
172
  - spec/terminus_session_spec.rb
159
173
  - spec/terminus_driver_spec.rb
160
174
  - spec/spec_helper.rb
161
- - bin/terminus
175
+ - lib/terminus/public/syn/synthetic.js
176
+ - lib/terminus/public/syn/mouse.js
177
+ - lib/terminus/public/syn/browsers.js
178
+ - lib/terminus/public/syn/key.js
179
+ - lib/terminus/public/syn/drag/drag.js
162
180
  has_rdoc: true
163
- homepage: http://github.com/jcoglan/terminus
181
+ homepage: http://terminus.jcoglan.com
164
182
  licenses: []
165
183
 
166
184
  post_install_message:
@@ -174,23 +192,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
174
192
  requirements:
175
193
  - - ">="
176
194
  - !ruby/object:Gem::Version
177
- segments:
178
- - 0
179
195
  version: "0"
180
196
  required_rubygems_version: !ruby/object:Gem::Requirement
181
197
  none: false
182
198
  requirements:
183
199
  - - ">="
184
200
  - !ruby/object:Gem::Version
185
- segments:
186
- - 0
187
201
  version: "0"
188
202
  requirements: []
189
203
 
190
204
  rubyforge_project:
191
- rubygems_version: 1.3.7
205
+ rubygems_version: 1.6.2
192
206
  signing_key:
193
207
  specification_version: 3
194
- summary: Capybara driver for cross-browser testing
208
+ summary: Capybara driver for cross-browser and remote scripting
195
209
  test_files: []
196
210
 
@@ -1 +0,0 @@
1
- JS=(typeof JS==='undefined')?{}:JS;JS.Package=function(a){var b=JS.Package.OrderedSet;JS.Package._5(this);this._0=a;this._2=new b();this._6=new b();this._b=new b();this._3={};this._7={}};(function(g){var m=g.OrderedSet=function(a,b){this._c=this.list=[];this._5={};if(!a)return;for(var c=0,d=a.length;c<d;c++)this.push(b?b(a[c]):a[c])};m.prototype.push=function(a){var b=(a.id!==undefined)?a.id:a,c=this._5;if(c.hasOwnProperty(b))return;c[b]=this._c.length;this._c.push(a)};g._4=this;if((this.document||{}).getElementsByTagName){var n=document.getElementsByTagName('script')[0];g._k=(n.readyState!==undefined)}var j=g.prototype;j.addDependency=function(a){this._6.push(a)};j.addSoftDependency=function(a){this._b.push(a)};j.addName=function(a){this._2.push(a);g.getFromCache(a).pkg=this};j.onload=function(a){this._d=a};j.on=function(a,b,c){if(this._7[a])return b.call(c);var d=this._3[a]=this._3[a]||[];d.push([b,c])};j.fire=function(a){if(this._7[a])return false;this._7[a]=true;var b=this._3[a];if(!b)return true;delete this._3[a];for(var c=0,d=b.length;c<d;c++)b[c][0].call(b[c][1]);return true};j.isLoaded=function(a){if(!a&&this._8!==undefined)return this._8;var b=this._2.list,c=b.length,d,f;while(c--){d=b[c];f=g.getObject(d);if(f!==undefined)continue;if(a)throw new Error('Expected package at '+this._0+' to define '+d);else return this._8=false}return this._8=true};j.load=function(){if(!this.fire('request'))return;var c=this._6.list.concat(this._b.list),d='load',f={};f[d]=this._6.list;g.when(f,function(){g.when({complete:c,load:[this]},function(){this.fire('complete')},this);var a=this,b=function(){if(a._d)a._d();a.isLoaded(true);a.fire('load')};if(this.isLoaded()){this.fire('download');return this.fire('load')}if(this._0===undefined)throw new Error('No load path found for '+this._2.list[0]);typeof this._0==='function'?this._0(b):g.Loader.loadFile(this._0,b);this.fire('download')},this)};j.toString=function(){return'Package:'+this._2.list.join(',')};g.when=function(b,c,d){var f=[],h,k,i;for(h in b){if(!b.hasOwnProperty(h))continue;k=new g.OrderedSet(b[h],function(a){return g.getByName(a)});i=k.list.length;while(i--)f.push([h,k.list[i]])}var l=i=f.length;if(l===0)return c&&c.call(d);while(i--){f[i][1].on(f[i][0],function(){l-=1;if(l===0&&c)c.call(d)});f[i][1].load()}};g._e=1;g._f={};g._g={};g._h=[];g._5=function(a){a.id=this._e;this._e+=1};g.getByPath=function(a){var b=a.toString();return this._f[b]=this._f[b]||new this(a)};g.getByName=function(a){if(typeof a!=='string')return a;var b=this.getFromCache(a);if(b.pkg)return b.pkg;var c=this._i(a);if(c)return c;var d=new this();d.addName(a);return d};g.autoload=function(a,b){this._h.push([a,b])};g._i=function(d){var f=this._h,h=f.length,k,i,l;for(k=0;k<h;k++){i=f[k];if(!i[0].test(d))continue;l=i[1].from+'/'+d.replace(/([a-z])([A-Z])/g,function(a,b,c){return b+'_'+c}).replace(/\./g,'/').toLowerCase()+'.js';pkg=new this(l);pkg.addName(d);if(l=i[1].require)pkg.addDependency(d.replace(i[0],l));return pkg}return null};g.getFromCache=function(a){return this._g[a]=this._g[a]||{}};g.getObject=function(a){var b=this.getFromCache(a);if(b.obj!==undefined)return b.obj;var c=this._4,d=a.split('.'),f;while(f=d.shift())c=c&&c[f];return this.getFromCache(a).obj=c}})(JS.Package);JS.Package.DomLoader={usable:function(){return!!JS.Package.getObject('window.document.getElementsByTagName')},__FILE__:function(){var a=document.getElementsByTagName('script');return a[a.length-1].src},loadFile:function(c,d){var f=this,h=document.createElement('script');h.type='text/javascript';h.src=c;h.onload=h.onreadystatechange=function(){var a=h.readyState,b=h.status;if(!a||a==='loaded'||a==='complete'||(a===4&&b===200)){d();h.onload=h.onreadystatechange=f._j;h=null}};if(window.console&&console.info)console.info('Loading '+c);document.getElementsByTagName('head')[0].appendChild(h)},_j:function(){}};JS.Package.CommonJSLoader={usable:function(){return typeof require==='function'&&typeof exports==='object'},setup:function(){var b=this;require=(function(a){return function(){b._1=arguments[0]+'.js';return a.apply(JS.Package._4,arguments)}})(require)},__FILE__:function(){return this._1},loadFile:function(a,b){var c=(typeof process==='object'),d=c?process.cwd():require('file').cwd(),f=a.replace(/\.[^\.]+$/g,''),h=c?require('path'):require('file');require(h.join(d,f));b()}};JS.Package.ServerLoader={usable:function(){return typeof JS.Package.getObject('load')==='function'&&typeof JS.Package.getObject('version')==='function'},setup:function(){var b=this;load=(function(a){return function(){b._1=arguments[0];return a.apply(JS.Package._4,arguments)}})(load)},__FILE__:function(){return this._1},loadFile:function(a,b){load(a);b()}};JS.Package.WshLoader={usable:function(){return!!JS.Package.getObject('ActiveXObject')&&!!JS.Package.getObject('WScript')},__FILE__:function(){return this._1},loadFile:function(a,b){this._1=a;var c=new ActiveXObject('Scripting.FileSystemObject'),d,f;try{d=c.OpenTextFile(a);f=function(){eval(d.ReadAll())};f();b()}finally{try{if(d)d.Close()}catch(e){}}}};(function(){var a=[JS.Package.DomLoader,JS.Package.CommonJSLoader,JS.Package.ServerLoader,JS.Package.WshLoader],b=a.length,c,d;for(c=0;c<b;c++){d=a[c];if(d.usable()){JS.Package.Loader=d;if(d.setup)d.setup();break}}})();JS.Package.DSL={__FILE__:function(){return JS.Package.Loader.__FILE__()},pkg:function(a,b){var c=b?JS.Package.getByPath(b):JS.Package.getByName(a);c.addName(a);return new JS.Package.Description(c)},file:function(a){var b=JS.Package.getByPath(a);return new JS.Package.Description(b)},load:function(a,b){JS.Package.Loader.loadFile(a,b)},autoload:function(a,b){JS.Package.autoload(a,b)}};JS.Package.Description=function(a){this._9=a};(function(f){f._a=function(a,b){var c=b.length,a=this._9[a],d;for(d=0;d<c;d++)a.call(this._9,b[d]);return this};f.provides=function(){return this._a('addName',arguments)};f.requires=function(){return this._a('addDependency',arguments)};f.uses=function(){return this._a('addSoftDependency',arguments)};f.setup=function(a){this._9.onload(a);return this}})(JS.Package.Description.prototype);JS.Package.DSL.loader=JS.Package.DSL.file;JS.Packages=function(a){a.call(JS.Package.DSL)};JS.require=function(){var a=[],b=0;while(typeof arguments[b]==='string'){a.push(arguments[b]);b+=1}JS.Package.when({complete:a},arguments[b],arguments[b+1])};JS.Packages(function(){with(this){var b=JS.Package._4.JSCLASS_PATH||__FILE__().replace(/[^\/]*$/g,'');if(!/\/$/.test(b))b=b+'/';var c=function(a){return file(b+a+'.js')};c('core').provides('JS.Module','JS.Class','JS.Kernel','JS.Singleton','JS.Interface');c('comparable').provides('JS.Comparable').requires('JS.Module');c('constant_scope').provides('JS.ConstantScope').requires('JS.Module');c('forwardable').provides('JS.Forwardable').requires('JS.Module');c('enumerable').provides('JS.Enumerable').requires('JS.Module','JS.Class');c('observable').provides('JS.Observable').requires('JS.Module');c('hash').provides('JS.Hash').requires('JS.Class','JS.Enumerable','JS.Comparable');c('set').provides('JS.Set','JS.HashSet','JS.SortedSet').requires('JS.Class','JS.Enumerable').uses('JS.Hash');c('linked_list').provides('JS.LinkedList','JS.LinkedList.Doubly','JS.LinkedList.Doubly.Circular').requires('JS.Class','JS.Enumerable');c('command').provides('JS.Command','JS.Command.Stack').requires('JS.Class','JS.Enumerable','JS.Observable');c('decorator').provides('JS.Decorator').requires('JS.Module','JS.Class');c('method_chain').provides('JS.MethodChain').requires('JS.Module','JS.Kernel');c('proxy').provides('JS.Proxy','JS.Proxy.Virtual').requires('JS.Module','JS.Class');c('ruby').provides('JS.Ruby').requires('JS.Class');c('stack_trace').provides('JS.StackTrace').requires('JS.Module','JS.Singleton');c('state').provides('JS.State').requires('JS.Module','JS.Class')}});
@@ -1,49 +0,0 @@
1
- body {
2
- background: #222;
3
- margin: 0;
4
- padding: 40px;
5
- }
6
-
7
- .container {
8
- background: #fff;
9
- color: #444;
10
- margin: 0 auto;
11
- padding: 40px;
12
- width: 400px;
13
- font: 16px/1.5 Georgia, Times New Roman, serif;
14
- }
15
-
16
- .footer {
17
- margin: 0 auto;
18
- padding: 40px;
19
- width: 400px;
20
- color: #fff;
21
- font: 13px Helvetica, Arial, sans-serif;
22
- }
23
-
24
- .footer p {
25
- margin: 0;
26
- line-height: 1.5em;
27
- }
28
-
29
- h1 {
30
- font: bold 64px FreeSans, Helvetica, Arial, sans-serif;
31
- letter-spacing: -0.03em;
32
- margin: 0 0 32px;
33
- color: #f00846;
34
- }
35
-
36
- a {
37
- color: #f00846;
38
- text-decoration: none;
39
- }
40
-
41
- a:hover {
42
- background: #f00846;
43
- color: #fff;
44
- }
45
-
46
- p {
47
- margin: 1.5em 0 0;
48
- }
49
-
@@ -1,2355 +0,0 @@
1
- // funcunit/synthetic/synthetic.js
2
-
3
- (function($){
4
-
5
-
6
-
7
- var extend = function(d, s) { for (var p in s) d[p] = s[p]; return d;},
8
- // only uses browser detection for key events
9
- browser = {
10
- msie: !!(window.attachEvent && !window.opera),
11
- opera: !!window.opera,
12
- webkit : navigator.userAgent.indexOf('AppleWebKit/') > -1,
13
- safari: navigator.userAgent.indexOf('AppleWebKit/') > -1 && navigator.userAgent.indexOf('Chrome/') == -1,
14
- gecko: navigator.userAgent.indexOf('Gecko') > -1,
15
- mobilesafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/),
16
- rhino : navigator.userAgent.match(/Rhino/) && true
17
- },
18
- createEventObject = function(type, options, element){
19
- var event = element.ownerDocument.createEventObject();
20
- return extend(event, options);
21
- },
22
- data = {},
23
- id = 0,
24
- expando = "_synthetic"+(new Date() - 0),
25
- bind,
26
- unbind,
27
- key = /keypress|keyup|keydown/,
28
- page = /load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll/,
29
-
30
- /**
31
- * @constructor Syn
32
- * @download funcunit/dist/syn.js
33
- * @test funcunit/synthetic/qunit.html
34
- * Syn is used to simulate user actions. It creates synthetic events and
35
- * performs their default behaviors.
36
- *
37
- * <h2>Basic Use</h2>
38
- * The following clicks an input element with <code>id='description'</code>
39
- * and then types <code>'Hello World'</code>.
40
- *
41
- @codestart
42
- Syn.click({},'description')
43
- .type("Hello World")
44
- @codeend
45
- * <h2>User Actions and Events</h2>
46
- * <p>Syn is typically used to simulate user actions as opposed to triggering events. Typing characters
47
- * is an example of a user action. The keypress that represents an <code>'a'</code>
48
- * character being typed is an example of an event.
49
- * </p>
50
- * <p>
51
- * While triggering events is supported, it's much more useful to simulate actual user behavior. The
52
- * following actions are supported by Syn:
53
- * </p>
54
- * <ul>
55
- * <li><code>[Syn.prototype.click click]</code> - a mousedown, focus, mouseup, and click.</li>
56
- * <li><code>[Syn.prototype.dblclick dblclick]</code> - two <code>click!</code> events followed by a <code>dblclick</code>.</li>
57
- * <li><code>[Syn.prototype.key key]</code> - types a single character (keydown, keypress, keyup).</li>
58
- * <li><code>[Syn.prototype.type type]</code> - types multiple characters into an element.</li>
59
- * <li><code>[Syn.prototype.move move]</code> - moves the mouse from one position to another (triggering mouseover / mouseouts).</li>
60
- * <li><code>[Syn.prototype.drag drag]</code> - a mousedown, followed by mousemoves, and a mouseup.</li>
61
- * </ul>
62
- * All actions run asynchronously.
63
- * Click on the links above for more
64
- * information on how to use the specific action.
65
- * <h2>Asynchronous Callbacks</h2>
66
- * Actions don't complete immediately. This is almost
67
- * entirely because <code>focus()</code>
68
- * doesn't run immediately in IE.
69
- * If you provide a callback function to Syn, it will
70
- * be called after the action is completed.
71
- * <br/>The following checks that "Hello World" was entered correctly:
72
- @codestart
73
- Syn.click({},'description')
74
- .type("Hello World", function(){
75
-
76
- ok("Hello World" == document.getElementById('description').value)
77
- })
78
- @codeend
79
- <h2>Asynchronous Chaining</h2>
80
- <p>You might have noticed the [Syn.prototype.then then] method. It provides chaining
81
- so you can do a sequence of events with a single (final) callback.
82
- </p><p>
83
- If an element isn't provided to then, it uses the previous Syn's element.
84
- </p>
85
- The following does a lot of stuff before checking the result:
86
- @codestart
87
- Syn.type('ice water','title')
88
- .type('ice and water','description')
89
- .click({},'create')
90
- .drag({to: 'favorites'},'newRecipe',
91
- function(){
92
- ok($('#newRecipe').parents('#favorites').length);
93
- })
94
- @codeend
95
-
96
- <h2>jQuery Helper</h2>
97
- If jQuery is present, Syn adds a triggerSyn helper you can use like:
98
- @codestart
99
- $("#description").triggerSyn("type","Hello World");
100
- @codeend
101
- * <h2>Key Event Recording</h2>
102
- * <p>Every browser has very different rules for dispatching key events.
103
- * As there is no way to feature detect how a browser handles key events,
104
- * synthetic uses a description of how the browser behaves generated
105
- * by a recording application. </p>
106
- * <p>
107
- * If you want to support a browser not currently supported, you can
108
- * record that browser's key event description and add it to
109
- * <code>Syn.key.browsers</code> by it's navigator agent.
110
- * </p>
111
- @codestart
112
- Syn.key.browsers["Envjs\ Resig/20070309 PilotFish/1.2.0.10\1.6"] = {
113
- 'prevent':
114
- {"keyup":[],"keydown":["char","keypress"],"keypress":["char"]},
115
- 'character':
116
- { ... }
117
- }
118
- @codeend
119
- * <h2>Limitations</h2>
120
- * Syn fully supports IE 6+, FF 3+, Chrome, Safari, Opera 10+.
121
- * With FF 1+, drag / move events are only partially supported. They will
122
- * not trigger mouseover / mouseout events.<br/>
123
- * Safari crashes when a mousedown is triggered on a select. Syn will not
124
- * create this event.
125
- * <h2>Contributing to Syn</h2>
126
- * Have we missed something? We happily accept patches. The following are
127
- * important objects and properties of Syn:
128
- * <ul>
129
- * <li><code>Syn.create</code> - contains methods to setup, convert options, and create an event of a specific type.</li>
130
- * <li><code>Syn.defaults</code> - default behavior by event type (except for keys).</li>
131
- * <li><code>Syn.key.defaults</code> - default behavior by key.</li>
132
- * <li><code>Syn.keycodes</code> - supported keys you can type.</li>
133
- * </ul>
134
- * <h2>Roll Your Own Functional Test Framework</h2>
135
- * <p>Syn is really the foundation of JavaScriptMVC's functional testing framework - [FuncUnit].
136
- * But, we've purposely made Syn work without any dependencies in the hopes that other frameworks or
137
- * testing solutions can use it as well.
138
- * </p>
139
- * @init
140
- * Creates a synthetic event on the element.
141
- * @param {Object} type
142
- * @param {Object} options
143
- * @param {Object} element
144
- * @param {Object} callback
145
- * @return Syn
146
- */
147
- Syn = function(type, options, element, callback){
148
- return ( new Syn.init(type, options, element, callback) )
149
- }
150
-
151
- if(window.addEventListener){ // Mozilla, Netscape, Firefox
152
- bind = function(el, ev, f){
153
- el.addEventListener(ev, f, false)
154
- }
155
- unbind = function(el, ev, f){
156
- el.removeEventListener(ev, f, false)
157
- }
158
- }else{
159
- bind = function(el, ev, f){
160
- el.attachEvent("on"+ev, f)
161
- }
162
- unbind = function(el, ev, f){
163
- el.detachEvent("on"+ev, f)
164
- }
165
- }
166
- /**
167
- * @Static
168
- */
169
- extend(Syn,{
170
- /**
171
- * Creates a new synthetic event instance
172
- * @hide
173
- * @param {Object} type
174
- * @param {Object} options
175
- * @param {Object} element
176
- * @param {Object} callback
177
- */
178
- init : function(type, options, element, callback){
179
- var args = Syn.args(options,element, callback),
180
- self = this;
181
- this.queue = [];
182
- this.element = args.element;
183
-
184
- //run event
185
- if(typeof this[type] == "function") {
186
- this[type](args.options, args.element, function(defaults,el ){
187
- args.callback && args.callback.apply(self, arguments);
188
- self.done.apply(self, arguments)
189
- })
190
- }else{
191
- this.result = Syn.trigger(type, args.options, args.element);
192
- args.callback && args.callback.call(this, args.element, this.result);
193
- }
194
- },
195
- jquery : function(el, fast){
196
- if(window.FuncUnit && window.FuncUnit.jquery){
197
- return window.FuncUnit.jquery
198
- } if (el){
199
- return Syn.helpers.getWindow(el).jQuery || window.jQuery
200
- }
201
- else{
202
- return window.jQuery
203
- }
204
- },
205
- /**
206
- * Returns an object with the args for a Syn.
207
- * @hide
208
- * @return {Object}
209
- */
210
- args : function(){
211
- var res = {}
212
- for(var i=0; i < arguments.length; i++){
213
- if(typeof arguments[i] == 'function'){
214
- res.callback = arguments[i]
215
- }else if(arguments[i] && arguments[i].jquery){
216
- res.element = arguments[i][0];
217
- }else if(arguments[i] && arguments[i].nodeName){
218
- res.element = arguments[i];
219
- }else if(res.options && typeof arguments[i] == 'string'){ //we can get by id
220
- res.element = document.getElementById(arguments[i])
221
- }
222
- else if(arguments[i]){
223
- res.options = arguments[i];
224
- }
225
- }
226
- return res;
227
- },
228
- click : function( options, element, callback){
229
- Syn('click!',options,element, callback);
230
- },
231
- /**
232
- * @attribute defaults
233
- * Default actions for events. Each default function is called with this as its
234
- * element. It should return true if a timeout
235
- * should happen after it. If it returns an element, a timeout will happen
236
- * and the next event will happen on that element.
237
- */
238
- defaults : {
239
- focus : function(){
240
- if(!Syn.support.focusChanges){
241
- var element = this,
242
- nodeName = element.nodeName.toLowerCase();
243
- Syn.data(element,"syntheticvalue", element.value)
244
-
245
- if(nodeName == "input"){
246
-
247
- bind(element, "blur", function(){
248
-
249
- if( Syn.data(element,"syntheticvalue") != element.value){
250
-
251
- Syn.trigger("change", {}, element);
252
- }
253
- unbind(element,"blur", arguments.callee)
254
- })
255
-
256
- }
257
- }
258
- },
259
- submit : function(){
260
- Syn.onParents(this, function(el){
261
- if( el.nodeName.toLowerCase() == 'form'){
262
- el.submit()
263
- return false;
264
- }
265
- });
266
- }
267
- },
268
- changeOnBlur : function(element, prop, value){
269
-
270
- bind(element, "blur", function(){
271
- if( value != element[prop]){
272
- Syn.trigger("change", {}, element);
273
- }
274
- unbind(element,"blur", arguments.callee)
275
- })
276
-
277
- },
278
- /**
279
- * Returns the closest element of a particular type.
280
- * @hide
281
- * @param {Object} el
282
- * @param {Object} type
283
- */
284
- closest : function(el, type){
285
- while(el && el.nodeName.toLowerCase() != type.toLowerCase()){
286
- el = el.parentNode
287
- }
288
- return el;
289
- },
290
- /**
291
- * adds jQuery like data (adds an expando) and data exists FOREVER :)
292
- * @hide
293
- * @param {Object} el
294
- * @param {Object} key
295
- * @param {Object} value
296
- */
297
- data : function(el, key, value){
298
- var d;
299
- if(!el[expando]){
300
- el[expando] = id++;
301
- }
302
- if(!data[el[expando]]){
303
- data[el[expando]] = {};
304
- }
305
- d = data[el[expando]]
306
- if(value){
307
- data[el[expando]][key] = value;
308
- }else{
309
- return data[el[expando]][key];
310
- }
311
- },
312
- /**
313
- * Calls a function on the element and all parents of the element until the function returns
314
- * false.
315
- * @hide
316
- * @param {Object} el
317
- * @param {Object} func
318
- */
319
- onParents : function(el, func){
320
- var res;
321
- while(el && res !== false){
322
- res = func(el)
323
- el = el.parentNode
324
- }
325
- return el;
326
- },
327
- //regex to match focusable elements
328
- focusable : /^(a|area|frame|iframe|label|input|select|textarea|button|html|object)$/i,
329
- /**
330
- * Returns if an element is focusable
331
- * @hide
332
- * @param {Object} elem
333
- */
334
- isFocusable : function(elem){
335
- var attributeNode;
336
- return ( this.focusable.test(elem.nodeName) || (
337
- (attributeNode = elem.getAttributeNode( "tabIndex" )) && attributeNode.specified ) )
338
- && Syn.isVisible(elem)
339
- },
340
- /**
341
- * Returns if an element is visible or not
342
- * @hide
343
- * @param {Object} elem
344
- */
345
- isVisible : function(elem){
346
- return (elem.offsetWidth && elem.offsetHeight) || (elem.clientWidth && elem.clientHeight)
347
- },
348
- /**
349
- * Gets the tabIndex as a number or null
350
- * @hide
351
- * @param {Object} elem
352
- */
353
- tabIndex : function(elem){
354
- var attributeNode = elem.getAttributeNode( "tabIndex" );
355
- return attributeNode && attributeNode.specified && ( parseInt( elem.getAttribute('tabIndex') ) || 0 )
356
- },
357
- bind : bind,
358
- unbind : unbind,
359
- browser: browser,
360
- //some generic helpers
361
- helpers : {
362
- createEventObject : createEventObject,
363
- createBasicStandardEvent : function(type, defaults){
364
- var event;
365
- try {
366
- event = document.createEvent("Events");
367
- } catch(e2) {
368
- event = document.createEvent("UIEvents");
369
- } finally {
370
- event.initEvent(type, true, true);
371
- extend(event, defaults);
372
- }
373
- return event;
374
- },
375
- inArray : function(item, array){
376
- for(var i =0; i < array.length; i++){
377
- if(array[i] == item){
378
- return i;
379
- }
380
- }
381
- return -1;
382
- },
383
- getWindow : function(element){
384
- return element.ownerDocument.defaultView || element.ownerDocument.parentWindow
385
- },
386
- extend: extend,
387
- scrollOffset : function(win){
388
- var doc = win.document.documentElement,
389
- body = win.document.body;
390
- return {
391
- left : (doc && doc.scrollLeft || body && body.scrollLeft || 0) + (doc.clientLeft || 0),
392
- top : (doc && doc.scrollTop || body && body.scrollTop || 0) + (doc.clientTop || 0)
393
- }
394
-
395
- },
396
- addOffset : function(options, el){
397
- var jq = Syn.jquery(el)
398
- if(typeof options == 'object' &&
399
- options.clientX === undefined &&
400
- options.clientY === undefined &&
401
- options.pageX === undefined &&
402
- options.pageY === undefined && jq){
403
- var el = jq(el)
404
- off = el.offset();
405
- options.pageX = off.left + el.width() /2 ;
406
- options.pageY = off.top + el.height() /2 ;
407
- }
408
- }
409
- },
410
- // place for key data
411
- key : {
412
- ctrlKey : null,
413
- altKey : null,
414
- shiftKey : null,
415
- metaKey : null
416
- },
417
- //triggers an event on an element, returns true if default events should be run
418
- /**
419
- * Dispatches an event and returns true if default events should be run.
420
- * @hide
421
- * @param {Object} event
422
- * @param {Object} element
423
- * @param {Object} type
424
- * @param {Object} autoPrevent
425
- */
426
- dispatch : (document.documentElement.dispatchEvent ?
427
- function(event, element, type, autoPrevent){
428
- var preventDefault = event.preventDefault,
429
- prevents = autoPrevent ? -1 : 0;
430
-
431
- //automatically prevents the default behavior for this event
432
- //this is to protect agianst nasty browser freezing bug in safari
433
- if(autoPrevent){
434
- bind(element, type, function(ev){
435
- ev.preventDefault()
436
- unbind(this, type, arguments.callee)
437
- })
438
- }
439
-
440
-
441
- event.preventDefault = function(){
442
- prevents++;
443
- if(++prevents > 0){
444
- preventDefault.apply(this,[]);
445
- }
446
- }
447
- element.dispatchEvent(event)
448
- return prevents <= 0;
449
- } :
450
- function(event, element, type){
451
- try {window.event = event;}catch(e) {}
452
- //source element makes sure element is still in the document
453
- return element.sourceIndex <= 0 || element.fireEvent('on'+type, event)
454
- }
455
- ),
456
- /**
457
- * @attribute
458
- * @hide
459
- * An object of eventType -> function that create that event.
460
- */
461
- create : {
462
- //-------- PAGE EVENTS ---------------------
463
- page : {
464
- event : document.createEvent ? function(type, options, element){
465
- var event = element.ownerDocument.createEvent("Events");
466
- event.initEvent(type, true, true );
467
- return event;
468
- } : createEventObject
469
- },
470
- // unique events
471
- focus : {
472
- event : function(type, options, element){
473
- Syn.onParents(element, function(el){
474
- if( Syn.isFocusable(el)){
475
- if(el.nodeName.toLowerCase() != 'html'){
476
- el.focus();
477
- }
478
- return false
479
- }
480
- });
481
- return true;
482
- }
483
- }
484
- },
485
- /**
486
- * @attribute support
487
- * Feature detected properties of a browser's event system.
488
- * Support has the following properties:
489
- * <ul>
490
- * <li><code>clickChanges</code> - clicking on an option element creates a change event.</li>
491
- * <li><code>clickSubmits</code> - clicking on a form button submits the form.</li>
492
- * <li><code>mouseupSubmits</code> - a mouseup on a form button submits the form.</li>
493
- * <li><code>radioClickChanges</code> - clicking a radio button changes the radio.</li>
494
- * <li><code>focusChanges</code> - focus/blur creates a change event.</li>
495
- * <li><code>linkHrefJS</code> - An achor's href JavaScript is run.</li>
496
- * <li><code>mouseDownUpClicks</code> - A mousedown followed by mouseup creates a click event.</li>
497
- * <li><code>tabKeyTabs</code> - A tab key changes tabs.</li>
498
- * <li><code>keypressOnAnchorClicks</code> - Keying enter on an anchor triggers a click.</li>
499
- * </ul>
500
- */
501
- support : {
502
- clickChanges : false,
503
- clickSubmits : false,
504
- keypressSubmits : false,
505
- mouseupSubmits: false,
506
- radioClickChanges : false,
507
- focusChanges : false,
508
- linkHrefJS : false,
509
- keyCharacters : false,
510
- backspaceWorks : false,
511
- mouseDownUpClicks : false,
512
- tabKeyTabs : false,
513
- keypressOnAnchorClicks : false,
514
- optionClickBubbles : false
515
- },
516
- /**
517
- * Creates a synthetic event and dispatches it on the element.
518
- * This will run any default actions for the element.
519
- * Typically you want to use Syn, but if you want the return value, use this.
520
- * @param {String} type
521
- * @param {Object} options
522
- * @param {HTMLElement} element
523
- * @return {Boolean} true if default events were run, false if otherwise.
524
- */
525
- trigger : function(type, options, element){
526
- options || (options = {});
527
-
528
- var create = Syn.create,
529
- setup = create[type] && create[type].setup,
530
- kind = key.test(type) ?
531
- 'key' :
532
- ( page.test(type) ?
533
- "page" : "mouse" ),
534
- createType = create[type] || {},
535
- createKind = create[kind],
536
- event,
537
- ret,
538
- autoPrevent = options._autoPrevent,
539
- dispatchEl = element;
540
-
541
- //any setup code?
542
- Syn.support.ready && setup && setup(type, options, element);
543
-
544
-
545
- //get kind
546
-
547
- delete options._autoPrevent;
548
-
549
- if(createType.event){
550
- ret = createType.event(type, options, element)
551
- }else{
552
- //convert options
553
- options = createKind.options ? createKind.options(type,options,element) : options;
554
-
555
- if(!Syn.support.changeBubbles && /option/i.test(element.nodeName)){
556
- dispatchEl = element.parentNode; //jQuery expects clicks on select
557
- }
558
-
559
- //create the event
560
- event = createKind.event(type,options,dispatchEl)
561
-
562
- //send the event
563
- ret = Syn.dispatch(event, dispatchEl, type, autoPrevent)
564
- }
565
-
566
- //run default behavior
567
- ret && Syn.support.ready
568
- && Syn.defaults[type]
569
- && Syn.defaults[type].call(element, options, autoPrevent);
570
- return ret;
571
- },
572
- eventSupported: function( eventName ) {
573
- var el = document.createElement("div");
574
- eventName = "on" + eventName;
575
-
576
- var isSupported = (eventName in el);
577
- if ( !isSupported ) {
578
- el.setAttribute(eventName, "return;");
579
- isSupported = typeof el[eventName] === "function";
580
- }
581
- el = null;
582
-
583
- return isSupported;
584
- }
585
-
586
- });
587
- var h = Syn.helpers;
588
- /**
589
- * @Prototype
590
- */
591
- extend(Syn.init.prototype,{
592
- /**
593
- * @function then
594
- * <p>
595
- * Then is used to chain a sequence of actions to be run one after the other.
596
- * This is useful when many asynchronous actions need to be performed before some
597
- * final check needs to be made.
598
- * </p>
599
- * <p>The following clicks and types into the <code>id='age'</code> element and then checks that only numeric characters can be entered.</p>
600
- * <h3>Example</h3>
601
- * @codestart
602
- * Syn('click',{},'age')
603
- * .then('type','I am 12',function(){
604
- * equals($('#age').val(),"12")
605
- * })
606
- * @codeend
607
- * If the element argument is undefined, then the last element is used.
608
- *
609
- * @param {String} type The type of event or action to create: "_click", "_dblclick", "_drag", "_type".
610
- * @param {Object} options Optiosn to pass to the event.
611
- * @param {String|HTMLElement} [element] A element's id or an element. If undefined, defaults to the previous element.
612
- * @param {Function} [callback] A function to callback after the action has run, but before any future chained actions are run.
613
- */
614
- then : function(type, options, element, callback){
615
- if(Syn.autoDelay){
616
- this.delay();
617
- }
618
- var args = Syn.args(options,element, callback),
619
- self = this;
620
-
621
-
622
- //if stack is empty run right away
623
-
624
- //otherwise ... unshift it
625
- this.queue.unshift(function(el, prevented){
626
-
627
- if(typeof this[type] == "function") {
628
- this.element = args.element || el;
629
- this[type](args.options, this.element, function(defaults, el){
630
- args.callback && args.callback.apply(self, arguments);
631
- self.done.apply(self, arguments)
632
- })
633
- }else{
634
- this.result = Syn.trigger(type, args.options, args.element);
635
- args.callback && args.callback.call(this, args.element, this.result);
636
- return this;
637
- }
638
- })
639
- return this;
640
- },
641
- /**
642
- * Delays the next command a set timeout.
643
- * @param {Number} [timeout]
644
- * @param {Function} [callback]
645
- */
646
- delay : function(timeout, callback){
647
- if(typeof timeout == 'function'){
648
- callback = timeout;
649
- timeout = null;
650
- }
651
- timeout = timeout || 600
652
- var self = this;
653
- this.queue.unshift(function(){
654
- setTimeout(function(){
655
- callback && callback.apply(self,[])
656
- self.done.apply(self, arguments)
657
- },timeout)
658
- })
659
- return this;
660
- },
661
- done : function( defaults, el){
662
- el && (this.element = el);;
663
- if(this.queue.length){
664
- this.queue.pop().call(this, this.element, defaults);
665
- }
666
-
667
- },
668
- /**
669
- * @function click
670
- * Clicks an element by triggering a mousedown,
671
- * mouseup,
672
- * and a click event.
673
- * <h3>Example</h3>
674
- * @codestart
675
- * Syn.click({},'create',function(){
676
- * //check something
677
- * })
678
- * @codeend
679
- * You can also provide the coordinates of the click.
680
- * If jQuery is present, it will set clientX and clientY
681
- * for you. Here's how to set it yourself:
682
- * @codestart
683
- * Syn.click(
684
- * {clientX: 20, clientY: 100},
685
- * 'create',
686
- * function(){
687
- * //check something
688
- * })
689
- * @codeend
690
- * You can also provide pageX and pageY and Syn will convert it for you.
691
- * @param {Object} options
692
- * @param {HTMLElement} element
693
- * @param {Function} callback
694
- */
695
- "_click" : function(options, element, callback, force){
696
- Syn.helpers.addOffset(options, element);
697
- Syn.trigger("mousedown", options, element);
698
-
699
- //timeout is b/c IE is stupid and won't call focus handlers
700
- setTimeout(function(){
701
- Syn.trigger("mouseup", options, element)
702
- if(!Syn.support.mouseDownUpClicks || force){
703
- Syn.trigger("click", options, element)
704
- callback(true)
705
- }else{
706
- //we still have to run the default (presumably)
707
- Syn.create.click.setup('click',options,element)
708
- Syn.defaults.click.call(element)
709
- //must give time for callback
710
- setTimeout(function(){
711
- callback(true)
712
- },1)
713
- }
714
-
715
- },1)
716
- },
717
- /**
718
- * Right clicks in browsers that support it (everyone but opera).
719
- * @param {Object} options
720
- * @param {Object} element
721
- * @param {Object} callback
722
- */
723
- "_rightClick" : function(options, element, callback){
724
- Syn.helpers.addOffset(options, element);
725
- var mouseopts = extend( extend({},Syn.mouse.browser.right.mouseup ), options)
726
-
727
- Syn.trigger("mousedown", mouseopts, element);
728
-
729
- //timeout is b/c IE is stupid and won't call focus handlers
730
- setTimeout(function(){
731
- Syn.trigger("mouseup", mouseopts, element)
732
- if (Syn.mouse.browser.contextmenu) {
733
- Syn.trigger("contextmenu",
734
- extend( extend({},Syn.mouse.browser.right.contextmenu ), options),
735
- element)
736
- }
737
- callback(true)
738
- },1)
739
- },
740
- /**
741
- * @function dblclick
742
- * Dblclicks an element. This runs two [Syn.prototype.click click] events followed by
743
- * a dblclick on the element.
744
- * <h3>Example</h3>
745
- * @codestart
746
- * Syn.dblclick({},'open')
747
- * @codeend
748
- * @param {Object} options
749
- * @param {HTMLElement} element
750
- * @param {Function} callback
751
- */
752
- "_dblclick" : function(options, element, callback){
753
- Syn.helpers.addOffset(options, element);
754
- var self = this;
755
- this._click(options, element, function(){
756
- setTimeout(function(){
757
- self._click(options, element, function(){
758
- Syn.trigger("dblclick", options, element)
759
- callback(true)
760
- },true)
761
- },2)
762
-
763
- })
764
- }
765
- })
766
-
767
- var actions = ["click","dblclick","move","drag","key","type",'rightClick'],
768
- makeAction = function(name){
769
- Syn[name] = function(options, element, callback){
770
- return Syn("_"+name, options, element, callback)
771
- }
772
- Syn.init.prototype[name] = function(options, element, callback){
773
- return this.then("_"+name, options, element, callback)
774
- }
775
- }
776
- for(var i=0; i < actions.length; i++){
777
- makeAction(actions[i]);
778
- }
779
- /**
780
- * Used for creating and dispatching synthetic events.
781
- * @codestart
782
- * new MVC.Syn('click').send(MVC.$E('id'))
783
- * @codeend
784
- * @init Sets up a synthetic event.
785
- * @param {String} type type of event, ex: 'click'
786
- * @param {optional:Object} options
787
- */
788
-
789
- if (window.jQuery || (window.FuncUnit && window.FuncUnit.jquery)) {
790
- (window.jQuery || window.FuncUnit.jquery).fn.triggerSyn = function(type, options, callback){
791
- Syn(type, options, this[0], callback)
792
- return this;
793
- };
794
- }
795
-
796
- window.Syn = Syn;
797
-
798
-
799
- })();
800
-
801
- // funcunit/synthetic/mouse.js
802
-
803
- (function($){
804
-
805
-
806
- var h = Syn.helpers;
807
-
808
- Syn.mouse = {};
809
- h.extend(Syn.defaults,{
810
- mousedown : function(options){
811
- Syn.trigger("focus", {}, this)
812
- },
813
- click : function(){
814
- // prevents the access denied issue in IE if the click causes the element to be destroyed
815
- var element = this;
816
- try {
817
- element.nodeType;
818
- } catch(e){
819
- return;
820
- }
821
- //get old values
822
- var href,
823
- checked = Syn.data(element,"checked"),
824
- scope = Syn.helpers.getWindow(element),
825
- nodeName = element.nodeName.toLowerCase();
826
-
827
- if( (href = Syn.data(element,"href") ) ){
828
- element.setAttribute('href',href)
829
- }
830
-
831
-
832
-
833
- //run href javascript
834
- if(!Syn.support.linkHrefJS
835
- && /^\s*javascript:/.test(element.href)){
836
- //eval js
837
- var code = element.href.replace(/^\s*javascript:/,"")
838
-
839
- //try{
840
- if (code != "//" && code.indexOf("void(0)") == -1) {
841
- if(window.selenium){
842
- eval("with(selenium.browserbot.getCurrentWindow()){"+code+"}")
843
- }else{
844
- eval("with(scope){"+code+"}")
845
- }
846
- }
847
- }
848
-
849
- //submit a form
850
- if(nodeName == "input"
851
- && element.type == "submit"
852
- && !(Syn.support.clickSubmits)){
853
-
854
- var form = Syn.closest(element, "form");
855
- if(form){
856
- Syn.trigger("submit",{},form)
857
- }
858
-
859
- }
860
- //follow a link, probably needs to check if in an a.
861
- if(nodeName == "a"
862
- && element.href
863
- && !/^\s*javascript:/.test(element.href)){
864
-
865
- scope.location.href = element.href;
866
-
867
- }
868
-
869
- //change a checkbox
870
- if(nodeName == "input"
871
- && element.type == "checkbox"){
872
-
873
- if(!Syn.support.clickChecks && !Syn.support.changeChecks){
874
- element.checked = !element.checked;
875
- }
876
- if(!Syn.support.clickChanges){
877
- Syn.trigger("change",{}, element );
878
- }
879
- }
880
-
881
- //change a radio button
882
- if(nodeName == "input" && element.type == "radio"){ // need to uncheck others if not checked
883
-
884
- if(!Syn.support.clickChecks && !Syn.support.changeChecks){
885
- //do the checks manually
886
- if(!element.checked){ //do nothing, no change
887
- element.checked = true;
888
- }
889
- }
890
- if(checked != element.checked && !Syn.support.radioClickChanges){
891
- Syn.trigger("change",{}, element );
892
- }
893
- }
894
- // change options
895
- if(nodeName == "option" && Syn.data(element,"createChange")){
896
- Syn.trigger("change",{}, element.parentNode);//does not bubble
897
- Syn.data(element,"createChange",false)
898
- }
899
- }
900
- })
901
-
902
-
903
- //add create and setup behavior for mosue events
904
- h.extend(Syn.create,{
905
- mouse : {
906
- options : function(type, options, element){
907
- var doc = document.documentElement, body = document.body,
908
- center = [options.pageX || 0, options.pageY || 0],
909
- //browser might not be loaded yet (doing support code)
910
- left = Syn.mouse.browser && Syn.mouse.browser.left[type],
911
- right = Syn.mouse.browser && Syn.mouse.browser.right[type];
912
- return h.extend({
913
- bubbles : true,cancelable : true,
914
- view : window,
915
- detail : 1,
916
- screenX : 1, screenY : 1,
917
- clientX : options.clientX || center[0] -(doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0),
918
- clientY : options.clientY || center[1] -(doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0),
919
- ctrlKey : !!Syn.key.ctrlKey,
920
- altKey : !!Syn.key.altKey,
921
- shiftKey : !!Syn.key.shiftKey,
922
- metaKey : !!Syn.key.metaKey,
923
- button : left && left.button != null ?
924
- left.button :
925
- right && right.button || (type == 'contextmenu' ? 2 : 0),
926
- relatedTarget : document.documentElement
927
- }, options);
928
- },
929
- event : document.createEvent ?
930
- function(type, defaults, element){ //Everyone Else
931
- var event;
932
-
933
- try {
934
- event = element.ownerDocument.createEvent('MouseEvents');
935
- event.initMouseEvent(type,
936
- defaults.bubbles, defaults.cancelable,
937
- defaults.view,
938
- defaults.detail,
939
- defaults.screenX, defaults.screenY,defaults.clientX,defaults.clientY,
940
- defaults.ctrlKey,defaults.altKey,defaults.shiftKey,defaults.metaKey,
941
- defaults.button,defaults.relatedTarget);
942
- } catch(e) {
943
- event = h.createBasicStandardEvent(type,defaults)
944
- }
945
- event.synthetic = true;
946
- return event;
947
- } :
948
- h.createEventObject
949
- },
950
- click : {
951
- setup : function(type, options, element){
952
- try{
953
- Syn.data(element,"checked", element.checked);
954
- }catch(e){}
955
- if(
956
- element.nodeName.toLowerCase() == "a"
957
- && element.href
958
- && !/^\s*javascript:/.test(element.href)){
959
-
960
- //save href
961
- Syn.data(element,"href", element.href)
962
-
963
- //remove b/c safari/opera will open a new tab instead of changing the page
964
- element.setAttribute('href','javascript://')
965
- }
966
- //if select or option, save old value and mark to change
967
- if(/option/i.test(element.nodeName)){
968
- var child = element.parentNode.firstChild,
969
- i = -1;
970
- while(child){
971
- if(child.nodeType ==1){
972
- i++;
973
- if(child == element) break;
974
- }
975
- child = child.nextSibling;
976
- }
977
- if(i !== element.parentNode.selectedIndex){
978
- //shouldn't this wait on triggering
979
- //change?
980
- element.parentNode.selectedIndex = i;
981
- Syn.data(element,"createChange",true)
982
- }
983
- }
984
- }
985
- },
986
- mousedown : {
987
- setup : function(type,options, element){
988
- var nn = element.nodeName.toLowerCase();
989
- //we have to auto prevent default to prevent freezing error in safari
990
- if(Syn.browser.safari && (nn == "select" || nn == "option" )){
991
- options._autoPrevent = true;
992
- }
993
- }
994
- }
995
- });
996
- //do support code
997
- (function(){
998
- if(!document.body){
999
- setTimeout(arguments.callee,1)
1000
- return;
1001
- }
1002
- var oldSynth = window.__synthTest;
1003
- window.__synthTest = function(){
1004
- Syn.support.linkHrefJS = true;
1005
- }
1006
- var div = document.createElement("div"),
1007
- checkbox,
1008
- submit,
1009
- form,
1010
- input,
1011
- select;
1012
-
1013
- div.innerHTML = "<form id='outer'>"+
1014
- "<input name='checkbox' type='checkbox'/>"+
1015
- "<input name='radio' type='radio' />"+
1016
- "<input type='submit' name='submitter'/>"+
1017
- "<input type='input' name='inputter'/>"+
1018
- "<input name='one'>"+
1019
- "<input name='two'/>"+
1020
- "<a href='javascript:__synthTest()' id='synlink'></a>"+
1021
- "<select><option></option></select>"+
1022
- "</form>";
1023
- document.documentElement.appendChild(div);
1024
- form = div.firstChild
1025
- checkbox = form.childNodes[0];
1026
- submit = form.childNodes[2];
1027
- select = form.getElementsByTagName('select')[0]
1028
-
1029
- checkbox.checked = false;
1030
- checkbox.onchange = function(){
1031
- Syn.support.clickChanges = true;
1032
- }
1033
-
1034
- Syn.trigger("click", {}, checkbox)
1035
- Syn.support.clickChecks = checkbox.checked;
1036
- checkbox.checked = false;
1037
-
1038
- Syn.trigger("change", {}, checkbox);
1039
-
1040
- Syn.support.changeChecks = checkbox.checked;
1041
-
1042
- form.onsubmit = function(ev){
1043
- if (ev.preventDefault)
1044
- ev.preventDefault();
1045
- Syn.support.clickSubmits = true;
1046
- return false;
1047
- }
1048
- Syn.trigger("click", {}, submit)
1049
-
1050
-
1051
-
1052
- form.childNodes[1].onchange = function(){
1053
- Syn.support.radioClickChanges = true;
1054
- }
1055
- Syn.trigger("click", {}, form.childNodes[1])
1056
-
1057
-
1058
- Syn.bind(div, 'click', function(){
1059
- Syn.support.optionClickBubbles = true;
1060
- Syn.unbind(div,'click', arguments.callee)
1061
- })
1062
- Syn.trigger("click",{},select.firstChild)
1063
-
1064
-
1065
- Syn.support.changeBubbles = Syn.eventSupported('change');
1066
-
1067
- //test if mousedown followed by mouseup causes click (opera), make sure there are no clicks after this
1068
- var clicksCount = 0
1069
- div.onclick = function(){
1070
- Syn.support.mouseDownUpClicks = true;
1071
- //we should use this to check for opera potentially, but would
1072
- //be difficult to remove element correctly
1073
- //Syn.support.mouseDownUpRepeatClicks = (2 == (++clicksCount))
1074
- }
1075
- Syn.trigger("mousedown",{},div)
1076
- Syn.trigger("mouseup",{},div)
1077
-
1078
- //setTimeout(function(){
1079
- // Syn.trigger("mousedown",{},div)
1080
- // Syn.trigger("mouseup",{},div)
1081
- //},1)
1082
-
1083
-
1084
- document.documentElement.removeChild(div);
1085
-
1086
- //check stuff
1087
- window.__synthTest = oldSynth;
1088
- //support.ready = true;
1089
- })();
1090
-
1091
-
1092
-
1093
- })();
1094
-
1095
- // funcunit/synthetic/browsers.js
1096
-
1097
- (function($){
1098
-
1099
- Syn.key.browsers = {
1100
- webkit : {
1101
- 'prevent':
1102
- {"keyup":[],"keydown":["char","keypress"],"keypress":["char"]},
1103
- 'character':
1104
- {"keydown":[0,"key"],"keypress":["char","char"],"keyup":[0,"key"]},
1105
- 'specialChars':
1106
- {"keydown":[0,"char"],"keyup":[0,"char"]},
1107
- 'navigation':
1108
- {"keydown":[0,"key"],"keyup":[0,"key"]},
1109
- 'special':
1110
- {"keydown":[0,"key"],"keyup":[0,"key"]},
1111
- 'tab':
1112
- {"keydown":[0,"char"],"keyup":[0,"char"]},
1113
- 'pause-break':
1114
- {"keydown":[0,"key"],"keyup":[0,"key"]},
1115
- 'caps':
1116
- {"keydown":[0,"key"],"keyup":[0,"key"]},
1117
- 'escape':
1118
- {"keydown":[0,"key"],"keyup":[0,"key"]},
1119
- 'num-lock':
1120
- {"keydown":[0,"key"],"keyup":[0,"key"]},
1121
- 'scroll-lock':
1122
- {"keydown":[0,"key"],"keyup":[0,"key"]},
1123
- 'print':
1124
- {"keyup":[0,"key"]},
1125
- 'function':
1126
- {"keydown":[0,"key"],"keyup":[0,"key"]},
1127
- '\r':
1128
- {"keydown":[0,"key"],"keypress":["char","key"],"keyup":[0,"key"]}
1129
- },
1130
- gecko : {
1131
- 'prevent':
1132
- {"keyup":[],"keydown":["char"],"keypress":["char"]},
1133
- 'character':
1134
- {"keydown":[0,"key"],"keypress":["char",0],"keyup":[0,"key"]},
1135
- 'specialChars':
1136
- {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]},
1137
- 'navigation':
1138
- {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]},
1139
- 'special':
1140
- {"keydown":[0,"key"],"keyup":[0,"key"]},
1141
- '\t':
1142
- {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]},
1143
- 'pause-break':
1144
- {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]},
1145
- 'caps':
1146
- {"keydown":[0,"key"],"keyup":[0,"key"]},
1147
- 'escape':
1148
- {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]},
1149
- 'num-lock':
1150
- {"keydown":[0,"key"],"keyup":[0,"key"]},
1151
- 'scroll-lock':
1152
- {"keydown":[0,"key"],"keyup":[0,"key"]},
1153
- 'print':
1154
- {"keyup":[0,"key"]},
1155
- 'function':
1156
- {"keydown":[0,"key"],"keyup":[0,"key"]},
1157
- '\r':
1158
- {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}
1159
- },
1160
- msie : {
1161
- 'prevent':{"keyup":[],"keydown":["char","keypress"],"keypress":["char"]},
1162
- 'character':{"keydown":[null,"key"],"keypress":[null,"char"],"keyup":[null,"key"]},
1163
- 'specialChars':{"keydown":[null,"char"],"keyup":[null,"char"]},
1164
- 'navigation':{"keydown":[null,"key"],"keyup":[null,"key"]},
1165
- 'special':{"keydown":[null,"key"],"keyup":[null,"key"]},
1166
- 'tab':{"keydown":[null,"char"],"keyup":[null,"char"]},
1167
- 'pause-break':{"keydown":[null,"key"],"keyup":[null,"key"]},
1168
- 'caps':{"keydown":[null,"key"],"keyup":[null,"key"]},
1169
- 'escape':{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]},
1170
- 'num-lock':{"keydown":[null,"key"],"keyup":[null,"key"]},
1171
- 'scroll-lock':{"keydown":[null,"key"],"keyup":[null,"key"]},
1172
- 'print':{"keyup":[null,"key"]},
1173
- 'function':{"keydown":[null,"key"],"keyup":[null,"key"]},
1174
- '\r':{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}
1175
- },
1176
- opera : {
1177
- 'prevent':
1178
- {"keyup":[],"keydown":[],"keypress":["char"]},
1179
- 'character':
1180
- {"keydown":[null,"key"],"keypress":[null,"char"],"keyup":[null,"key"]},
1181
- 'specialChars':
1182
- {"keydown":[null,"char"],"keypress":[null,"char"],"keyup":[null,"char"]},
1183
- 'navigation':
1184
- {"keydown":[null,"key"],"keypress":[null,"key"]},
1185
- 'special':
1186
- {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]},
1187
- 'tab':
1188
- {"keydown":[null,"char"],"keypress":[null,"char"],"keyup":[null,"char"]},
1189
- 'pause-break':
1190
- {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]},
1191
- 'caps':
1192
- {"keydown":[null,"key"],"keyup":[null,"key"]},
1193
- 'escape':
1194
- {"keydown":[null,"key"],"keypress":[null,"key"]},
1195
- 'num-lock':
1196
- {"keyup":[null,"key"],"keydown":[null,"key"],"keypress":[null,"key"]},
1197
- 'scroll-lock':
1198
- {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]},
1199
- 'print':
1200
- {},
1201
- 'function':
1202
- {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]},
1203
- '\r':
1204
- {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}
1205
- }
1206
- };
1207
-
1208
- Syn.mouse.browsers = {
1209
- webkit : {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}},
1210
- "left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}},
1211
- opera: {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3}},
1212
- "left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}},
1213
- msie: { "right":{"mousedown":{"button":2},"mouseup":{"button":2},"contextmenu":{"button":0}},
1214
- "left":{"mousedown":{"button":1},"mouseup":{"button":1},"click":{"button":0}}},
1215
- chrome : {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}},
1216
- "left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}},
1217
- gecko: {"left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}},
1218
- "right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}}}
1219
- }
1220
-
1221
- //set browser
1222
- Syn.key.browser =
1223
- (function(){
1224
- if(Syn.key.browsers[window.navigator.userAgent]){
1225
- return Syn.key.browsers[window.navigator.userAgent];
1226
- }
1227
- for(var browser in Syn.browser){
1228
- if(Syn.browser[browser] && Syn.key.browsers[browser]){
1229
- return Syn.key.browsers[browser]
1230
- }
1231
- }
1232
- return Syn.key.browsers.gecko;
1233
- })();
1234
-
1235
- Syn.mouse.browser =
1236
- (function(){
1237
- if(Syn.mouse.browsers[window.navigator.userAgent]){
1238
- return Syn.mouse.browsers[window.navigator.userAgent];
1239
- }
1240
- for(var browser in Syn.browser){
1241
- if(Syn.browser[browser] && Syn.mouse.browsers[browser]){
1242
- return Syn.mouse.browsers[browser]
1243
- }
1244
- }
1245
- return Syn.mouse.browsers.gecko;
1246
- })();
1247
-
1248
-
1249
- })();
1250
-
1251
- // funcunit/synthetic/key.js
1252
-
1253
- (function($){
1254
-
1255
-
1256
- var h = Syn.helpers,
1257
- S = Syn,
1258
-
1259
- // gets the selection of an input or textarea
1260
- getSelection = function(el){
1261
- // use selectionStart if we can
1262
- if (el.selectionStart !== undefined) {
1263
- // this is for opera, so we don't have to focus to type how we think we would
1264
- if(document.activeElement
1265
- && document.activeElement != el
1266
- && el.selectionStart == el.selectionEnd
1267
- && el.selectionStart == 0){
1268
- return {start: el.value.length, end: el.value.length};
1269
- }
1270
- return {start: el.selectionStart, end: el.selectionEnd}
1271
- }else{
1272
- //check if we aren't focused
1273
- //if(document.activeElement && document.activeElement != el){
1274
-
1275
-
1276
- //}
1277
- try {
1278
- //try 2 different methods that work differently (IE breaks depending on type)
1279
- if (el.nodeName.toLowerCase() == 'input') {
1280
- var real = h.getWindow(el).document.selection.createRange(), r = el.createTextRange();
1281
- r.setEndPoint("EndToStart", real);
1282
-
1283
- var start = r.text.length
1284
- return {
1285
- start: start,
1286
- end: start + real.text.length
1287
- }
1288
- }
1289
- else {
1290
- var real = h.getWindow(el).document.selection.createRange(), r = real.duplicate(), r2 = real.duplicate(), r3 = real.duplicate();
1291
- r2.collapse();
1292
- r3.collapse(false);
1293
- r2.moveStart('character', -1)
1294
- r3.moveStart('character', -1)
1295
- //select all of our element
1296
- r.moveToElementText(el)
1297
- //now move our endpoint to the end of our real range
1298
- r.setEndPoint('EndToEnd', real);
1299
- var start = r.text.length - real.text.length, end = r.text.length;
1300
- if (start != 0 && r2.text == "") {
1301
- start += 2;
1302
- }
1303
- if (end != 0 && r3.text == "") {
1304
- end += 2;
1305
- }
1306
- //if we aren't at the start, but previous is empty, we are at start of newline
1307
- return {
1308
- start: start,
1309
- end: end
1310
- }
1311
- }
1312
- }catch(e){
1313
- return {start: el.value.length, end: el.value.length};
1314
- }
1315
- }
1316
- },
1317
- // gets all focusable elements
1318
- getFocusable = function(el){
1319
- var document = h.getWindow(el).document,
1320
- res = [];
1321
-
1322
- var els = document.getElementsByTagName('*'),
1323
- len = els.length;
1324
-
1325
- for(var i=0; i< len; i++){
1326
- Syn.isFocusable(els[i]) && els[i] != document.documentElement && res.push(els[i])
1327
- }
1328
- return res;
1329
-
1330
-
1331
- };
1332
-
1333
- /**
1334
- * @add Syn static
1335
- */
1336
- h.extend(Syn,{
1337
- /**
1338
- * @attribute
1339
- * A list of the keys and their keycodes codes you can type.
1340
- * You can add type keys with
1341
- * @codestart
1342
- * Syn('key','delete','title');
1343
- *
1344
- * //or
1345
- *
1346
- * Syn('type','One Two Three[left][left][delete]','title')
1347
- * @codeend
1348
- *
1349
- * The following are a list of keys you can type:
1350
- * @codestart text
1351
- * \b - backspace
1352
- * \t - tab
1353
- * \r - enter
1354
- * ' ' - space
1355
- * a-Z 0-9 - normal characters
1356
- * /!@#$*,.? - All other typeable characters
1357
- * page-up - scrolls up
1358
- * page-down - scrolls down
1359
- * end - scrolls to bottom
1360
- * home - scrolls to top
1361
- * insert - changes how keys are entered
1362
- * delete - deletes the next character
1363
- * left - moves cursor left
1364
- * right - moves cursor right
1365
- * up - moves the cursor up
1366
- * down - moves the cursor down
1367
- * f1-12 - function buttons
1368
- * shift, ctrl, alt - special keys
1369
- * pause-break - the pause button
1370
- * scroll-lock - locks scrolling
1371
- * caps - makes caps
1372
- * escape - escape button
1373
- * num-lock - allows numbers on keypad
1374
- * print - screen capture
1375
- * @codeend
1376
- */
1377
- keycodes: {
1378
- //backspace
1379
- '\b':'8',
1380
-
1381
- //tab
1382
- '\t':'9',
1383
-
1384
- //enter
1385
- '\r':'13',
1386
-
1387
- //special
1388
- 'shift':'16','ctrl':'17','alt':'18',
1389
-
1390
- //weird
1391
- 'pause-break':'19',
1392
- 'caps':'20',
1393
- 'escape':'27',
1394
- 'num-lock':'144',
1395
- 'scroll-lock':'145',
1396
- 'print' : '44',
1397
-
1398
- //navigation
1399
- 'page-up':'33','page-down':'34','end':'35','home':'36',
1400
- 'left':'37','up':'38','right':'39','down':'40','insert':'45','delete':'46',
1401
-
1402
- //normal characters
1403
- ' ':'32',
1404
- '0':'48','1':'49','2':'50','3':'51','4':'52','5':'53','6':'54','7':'55','8':'56','9':'57',
1405
- 'a':'65','b':'66','c':'67','d':'68','e':'69','f':'70','g':'71','h':'72','i':'73','j':'74','k':'75','l':'76','m':'77',
1406
- 'n':'78','o':'79','p':'80','q':'81','r':'82','s':'83','t':'84','u':'85','v':'86','w':'87','x':'88','y':'89','z':'90',
1407
- //normal-characters, numpad
1408
- 'num0':'96','num1':'97','num2':'98','num3':'99','num4':'100','num5':'101','num6':'102','num7':'103','num8':'104','num9':'105',
1409
- '*':'106','+':'107','-':'109','.':'110',
1410
- //normal-characters, others
1411
- '/':'111',
1412
- ';':'186',
1413
- '=':'187',
1414
- ',':'188',
1415
- '-':'189',
1416
- '.':'190',
1417
- '/':'191',
1418
- '`':'192',
1419
- '[':'219',
1420
- '\\':'220',
1421
- ']':'221',
1422
- "'":'222',
1423
-
1424
- //ignore these, you shouldn't use them
1425
- 'left window key':'91','right window key':'92','select key':'93',
1426
-
1427
-
1428
- 'f1':'112','f2':'113','f3':'114','f4':'115','f5':'116','f6':'117',
1429
- 'f7':'118','f8':'119','f9':'120','f10':'121','f11':'122','f12':'123'
1430
- },
1431
-
1432
- // what we can type in
1433
- typeable : /input|textarea/i,
1434
-
1435
- // selects text on an element
1436
- selectText: function(el, start, end){
1437
- if(el.setSelectionRange){
1438
- if(!end){
1439
- el.focus();
1440
- el.setSelectionRange(start, start);
1441
- } else {
1442
- el.selectionStart = start;
1443
- el.selectionEnd = end;
1444
- }
1445
- }else if (el.createTextRange) {
1446
- //el.focus();
1447
- var r = el.createTextRange();
1448
- r.moveStart('character', start);
1449
- end = end || start;
1450
- r.moveEnd('character', end - el.value.length);
1451
-
1452
- r.select();
1453
- }
1454
- },
1455
- getText: function(el){
1456
- //first check if the el has anything selected ..
1457
- if(Syn.typeable.test(el.nodeName)){
1458
- var sel = getSelection(el);
1459
- return el.value.substring(sel.start, sel.end)
1460
- }
1461
- //otherwise get from page
1462
- var win = Syn.helpers.getWindow(el);
1463
- if (win.getSelection) {
1464
- return win.getSelection().toString();
1465
- }
1466
- else if (win.document.getSelection) {
1467
- return win.document.getSelection().toString()
1468
- }
1469
- else {
1470
- return win.document.selection.createRange().text;
1471
- }
1472
- },
1473
- getSelection : getSelection
1474
- });
1475
-
1476
- h.extend(Syn.key,{
1477
- // retrieves a description of what events for this character should look like
1478
- data : function(key){
1479
- //check if it is described directly
1480
- if(S.key.browser[key]){
1481
- return S.key.browser[key];
1482
- }
1483
- for(var kind in S.key.kinds){
1484
- if(h.inArray(key, S.key.kinds[kind] ) > -1){
1485
- return S.key.browser[kind]
1486
- }
1487
- }
1488
- return S.key.browser.character
1489
- },
1490
-
1491
- //returns the special key if special
1492
- isSpecial : function(keyCode){
1493
- var specials = S.key.kinds.special;
1494
- for(var i=0; i < specials.length; i++){
1495
- if(Syn.keycodes[ specials[i] ] == keyCode){
1496
- return specials[i];
1497
- }
1498
- }
1499
- },
1500
- /**
1501
- * @hide
1502
- * gets the options for a key and event type ...
1503
- * @param {Object} key
1504
- * @param {Object} event
1505
- */
1506
- options : function(key, event){
1507
- var keyData = Syn.key.data(key);
1508
-
1509
- if(!keyData[event]){
1510
- //we shouldn't be creating this event
1511
- return null;
1512
- }
1513
-
1514
- var charCode = keyData[event][0],
1515
- keyCode = keyData[event][1],
1516
- result = {};
1517
-
1518
- if(keyCode == 'key'){
1519
- result.keyCode = Syn.keycodes[key]
1520
- } else if (keyCode == 'char'){
1521
- result.keyCode = key.charCodeAt(0)
1522
- }else{
1523
- result.keyCode = keyCode;
1524
- }
1525
-
1526
- if(charCode == 'char'){
1527
- result.charCode = key.charCodeAt(0)
1528
- }else if(charCode !== null){
1529
- result.charCode = charCode;
1530
- }
1531
-
1532
-
1533
- return result
1534
- },
1535
- //types of event keys
1536
- kinds : {
1537
- special : ["shift",'ctrl','alt','caps'],
1538
- specialChars : ["\b"],
1539
- navigation: ["page-up",'page-down','end','home','left','up','right','down','insert','delete'],
1540
- 'function' : ['f1','f2','f3','f4','f5','f6','f7','f8','f9','f10','f11','f12']
1541
- },
1542
- //returns the default function
1543
- getDefault : function(key){
1544
- //check if it is described directly
1545
- if(Syn.key.defaults[key]){
1546
- return Syn.key.defaults[key];
1547
- }
1548
- for(var kind in Syn.key.kinds){
1549
- if(h.inArray(key, Syn.key.kinds[kind])> -1 && Syn.key.defaults[kind] ){
1550
- return Syn.key.defaults[kind];
1551
- }
1552
- }
1553
- return Syn.key.defaults.character
1554
- },
1555
- // default behavior when typing
1556
- defaults : {
1557
- 'character' : function(options, scope, key, force, sel){
1558
- if(/num\d+/.test(key)){
1559
- key = key.match(/\d+/)[0]
1560
- }
1561
-
1562
- if(force || (!S.support.keyCharacters && Syn.typeable.test(this.nodeName))){
1563
- var current = this.value,
1564
- before = current.substr(0,sel.start),
1565
- after = current.substr(sel.end),
1566
- character = key;
1567
-
1568
- this.value = before+character+after;
1569
- //handle IE inserting \r\n
1570
- var charLength = character == "\n" && S.support.textareaCarriage ? 2 : character.length;
1571
- Syn.selectText(this, before.length + charLength)
1572
- }
1573
- },
1574
- 'c' : function(options, scope, key, force, sel){
1575
- if(Syn.key.ctrlKey){
1576
- Syn.key.clipboard = Syn.getText(this)
1577
- }else{
1578
- Syn.key.defaults.character.apply(this, arguments);
1579
- }
1580
- },
1581
- 'v' : function(options, scope, key, force, sel){
1582
- if(Syn.key.ctrlKey){
1583
- Syn.key.defaults.character.call(this, options,scope, Syn.key.clipboard, true,sel);
1584
- }else{
1585
- Syn.key.defaults.character.apply(this, arguments);
1586
- }
1587
- },
1588
- 'a' : function(options, scope, key, force, sel){
1589
- if(Syn.key.ctrlKey){
1590
- Syn.selectText(this, 0, this.value.length)
1591
- }else{
1592
- Syn.key.defaults.character.apply(this, arguments);
1593
- }
1594
- },
1595
- 'home' : function(){
1596
- Syn.onParents(this, function(el){
1597
- if(el.scrollHeight != el.clientHeight){
1598
- el.scrollTop = 0;
1599
- return false;
1600
- }
1601
- })
1602
- },
1603
- 'end' : function(){
1604
- Syn.onParents(this, function(el){
1605
- if(el.scrollHeight != el.clientHeight){
1606
- el.scrollTop = el.scrollHeight;
1607
- return false;
1608
- }
1609
- })
1610
- },
1611
- 'page-down' : function(){
1612
- //find the first parent we can scroll
1613
- Syn.onParents(this, function(el){
1614
- if(el.scrollHeight != el.clientHeight){
1615
- var ch = el.clientHeight
1616
- el.scrollTop += ch;
1617
- return false;
1618
- }
1619
- })
1620
- },
1621
- 'page-up' : function(){
1622
- Syn.onParents(this, function(el){
1623
- if(el.scrollHeight != el.clientHeight){
1624
- var ch = el.clientHeight
1625
- el.scrollTop -= ch;
1626
- return false;
1627
- }
1628
- })
1629
- },
1630
- '\b' : function(options, scope, key, force, sel){
1631
- //this assumes we are deleting from the end
1632
- if(!S.support.backspaceWorks && Syn.typeable.test(this.nodeName)){
1633
- var current = this.value,
1634
- before = current.substr(0,sel.start),
1635
- after = current.substr(sel.end);
1636
-
1637
- if(sel.start == sel.end && sel.start > 0){
1638
- //remove a character
1639
- this.value = before.substring(0, before.length - 1)+after
1640
- Syn.selectText(this, sel.start-1)
1641
- }else{
1642
- this.value = before+after;
1643
- Syn.selectText(this, sel.start)
1644
- }
1645
-
1646
- //set back the selection
1647
- }
1648
- },
1649
- 'delete' : function(options, scope, key, force, sel){
1650
- if(!S.support.backspaceWorks && Syn.typeable.test(this.nodeName)){
1651
- var current = this.value,
1652
- before = current.substr(0,sel.start),
1653
- after = current.substr(sel.end);
1654
- if(sel.start == sel.end && sel.start <= this.value.length - 1){
1655
- this.value = before+after.substring(1)
1656
- }else{
1657
- this.value = before+after;
1658
-
1659
- }
1660
- Syn.selectText(this, sel.start)
1661
- }
1662
- },
1663
- '\r' : function(options, scope, key, force, sel){
1664
-
1665
- var nodeName = this.nodeName.toLowerCase()
1666
- // submit a form
1667
- if(!S.support.keypressSubmits && nodeName == 'input'){
1668
- var form = Syn.closest(this, "form");
1669
- if(form){
1670
- Syn.trigger("submit", {}, form);
1671
- }
1672
-
1673
- }
1674
- //newline in textarea
1675
- if(!S.support.keyCharacters && nodeName == 'textarea'){
1676
- Syn.key.defaults.character.call(this, options, scope, "\n", undefined, sel)
1677
- }
1678
- // 'click' hyperlinks
1679
- if(!S.support.keypressOnAnchorClicks && nodeName == 'a'){
1680
- Syn.trigger("click", {}, this);
1681
- }
1682
- },
1683
- //
1684
- // Gets all focusable elements. If the element (this)
1685
- // doesn't have a tabindex, finds the next element after.
1686
- // If the element (this) has a tabindex finds the element
1687
- // with the next higher tabindex OR the element with the same
1688
- // tabindex after it in the document.
1689
- // @return the next element
1690
- //
1691
- '\t' : function(options, scope){
1692
- // focusable elements
1693
- var focusEls = getFocusable(this),
1694
- // the current element's tabindex
1695
- tabIndex = Syn.tabIndex(this),
1696
- // will be set to our guess for the next element
1697
- current = null,
1698
- // the next index we care about
1699
- currentIndex = 1000000000,
1700
- // set to true once we found 'this' element
1701
- found = false,
1702
- i = 0,
1703
- el,
1704
- //the tabindex of the tabable element we are looking at
1705
- elIndex,
1706
- firstNotIndexed,
1707
- prev;
1708
- orders = [];
1709
- for(; i< focusEls.length; i++){
1710
- orders.push([focusEls[i], i]);
1711
- }
1712
- var sort = function(order1, order2){
1713
- var el1 = order1[0],
1714
- el2 = order2[0],
1715
- tab1 = Syn.tabIndex(el1) || 0,
1716
- tab2 = Syn.tabIndex(el2) || 0;
1717
- if(tab1 == tab2){
1718
- return order1[1] - order2[1]
1719
- }else{
1720
- if(tab1 == 0){
1721
- return 1;
1722
- }else if(tab2 == 0){
1723
- return -1;
1724
- }else{
1725
- return tab1-tab2;
1726
- }
1727
- }
1728
- }
1729
- orders.sort(sort);
1730
- //now find current
1731
- for(i=0; i< orders.length; i++){
1732
- el = orders[i][0];
1733
- if(this== el ){
1734
- if(!Syn.key.shiftKey){
1735
- current = orders[i+1][0];
1736
- if(!current){
1737
- current = orders[0][0]
1738
- }
1739
- }else{
1740
- current = orders[i-1][0];
1741
- if(!current){
1742
- current = orders[focusEls.length-1][0]
1743
- }
1744
- }
1745
-
1746
- }
1747
- }
1748
-
1749
- //restart if we didn't find anything
1750
- if(!current){
1751
- current = firstNotIndexed;
1752
- }
1753
- current && current.focus();
1754
- return current;
1755
- },
1756
- 'left' : function(options, scope, key, force, sel){
1757
- if( Syn.typeable.test(this.nodeName) ){
1758
- if(Syn.key.shiftKey){
1759
- Syn.selectText(this, sel.start == 0 ? 0 : sel.start - 1, sel.end)
1760
- }else{
1761
- Syn.selectText(this, sel.start == 0 ? 0 : sel.start - 1)
1762
- }
1763
- }
1764
- },
1765
- 'right' : function(options, scope, key, force, sel){
1766
- if( Syn.typeable.test(this.nodeName) ){
1767
- if(Syn.key.shiftKey){
1768
- Syn.selectText(this, sel.start, sel.end+1 > this.value.length ? this.value.length : sel.end+1)
1769
- }else{
1770
- Syn.selectText(this, sel.end+1 > this.value.length ? this.value.length : sel.end+1)
1771
- }
1772
- }
1773
- },
1774
- 'up' : function(){
1775
- if(/select/i.test(this.nodeName)){
1776
-
1777
- this.selectedIndex = this.selectedIndex ? this.selectedIndex-1 : 0;
1778
- //set this to change on blur?
1779
- }
1780
- },
1781
- 'down' : function(){
1782
- if(/select/i.test(this.nodeName)){
1783
- Syn.changeOnBlur(this, "selectedIndex", this.selectedIndex)
1784
- this.selectedIndex = this.selectedIndex+1;
1785
- //set this to change on blur?
1786
- }
1787
- },
1788
- 'shift' : function(){
1789
- return null;
1790
- }
1791
- }
1792
- });
1793
-
1794
-
1795
- h.extend(Syn.create,{
1796
- keydown : {
1797
- setup : function(type, options, element){
1798
- if(h.inArray(options,Syn.key.kinds.special ) != -1){
1799
- Syn.key[options+"Key"] = element;
1800
- }
1801
- }
1802
- },
1803
- keyup : {
1804
- setup : function(type, options, element){
1805
- if(h.inArray(options,Syn.key.kinds.special )!= -1){
1806
- Syn.key[options+"Key"] = null;
1807
- }
1808
- }
1809
- },
1810
- key : {
1811
- // return the options for a key event
1812
- options : function(type, options, element){
1813
- //check if options is character or has character
1814
- options = typeof options != "object" ? {character : options} : options;
1815
-
1816
- //don't change the orignial
1817
- options = h.extend({}, options)
1818
- if(options.character){
1819
- h.extend(options, S.key.options(options.character, type));
1820
- delete options.character;
1821
- }
1822
-
1823
- options = h.extend({
1824
- ctrlKey: !!Syn.key.ctrlKey,
1825
- altKey: !!Syn.key.altKey,
1826
- shiftKey: !!Syn.key.shiftKey,
1827
- metaKey: !!Syn.key.metaKey
1828
- }, options)
1829
-
1830
- return options;
1831
- },
1832
- // creates a key event
1833
- event : document.createEvent ?
1834
- function(type, options, element){ //Everyone Else
1835
- var event;
1836
-
1837
- try {
1838
-
1839
- event = element.ownerDocument.createEvent("KeyEvents");
1840
- event.initKeyEvent(type, true, true, window,
1841
- options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
1842
- options.keyCode, options.charCode );
1843
- } catch(e) {
1844
- event = h.createBasicStandardEvent(type,options)
1845
- }
1846
- event.synthetic = true;
1847
- return event;
1848
-
1849
- } :
1850
- function(type, options, element){
1851
- var event = h.createEventObject.apply(this,arguments);
1852
- h.extend(event, options)
1853
-
1854
- return event;
1855
- }
1856
- }
1857
- });
1858
-
1859
- var convert = {
1860
- "enter" : "\r",
1861
- "backspace" : "\b",
1862
- "tab" : "\t",
1863
- "space" : " "
1864
- }
1865
-
1866
- /**
1867
- * @add Syn prototype
1868
- */
1869
- h.extend(Syn.init.prototype,
1870
- {
1871
- /**
1872
- * @function key
1873
- * Types a single key. The key should be
1874
- * a string that matches a
1875
- * [Syn.static.keycodes].
1876
- *
1877
- * The following sends a carridge return
1878
- * to the 'name' element.
1879
- * @codestart
1880
- * Syn.key('\r','name')
1881
- * @codeend
1882
- * For each character, a keydown, keypress, and keyup is triggered if
1883
- * appropriate.
1884
- * @param {String} options
1885
- * @param {HTMLElement} [element]
1886
- * @param {Function} [callback]
1887
- * @return {HTMLElement} the element currently focused.
1888
- */
1889
- _key : function(options, element, callback){
1890
- //first check if it is a special up
1891
- if(/-up$/.test(options)
1892
- && h.inArray(options.replace("-up",""),Syn.key.kinds.special )!= -1){
1893
- Syn.trigger('keyup',options.replace("-up",""), element )
1894
- callback(true, element);
1895
- return;
1896
- }
1897
-
1898
-
1899
- var caret = Syn.typeable.test(element.nodeName) && getSelection(element),
1900
- key = convert[options] || options,
1901
- // should we run default events
1902
- runDefaults = Syn.trigger('keydown',key, element ),
1903
-
1904
- // a function that gets the default behavior for a key
1905
- getDefault = Syn.key.getDefault,
1906
-
1907
- // how this browser handles preventing default events
1908
- prevent = Syn.key.browser.prevent,
1909
-
1910
- // the result of the default event
1911
- defaultResult,
1912
-
1913
- // options for keypress
1914
- keypressOptions = Syn.key.options(key, 'keypress')
1915
-
1916
-
1917
- if(runDefaults){
1918
- //if the browser doesn't create keypresses for this key, run default
1919
- if(!keypressOptions){
1920
- defaultResult = getDefault(key).call(element, keypressOptions, h.getWindow(element), key, undefined, caret)
1921
- }else{
1922
- //do keypress
1923
- runDefaults = Syn.trigger('keypress',keypressOptions, element )
1924
- if(runDefaults){
1925
- defaultResult = getDefault(key).call(element, keypressOptions, h.getWindow(element), key, undefined, caret)
1926
- }
1927
- }
1928
- }else{
1929
- //canceled ... possibly don't run keypress
1930
- if(keypressOptions && h.inArray('keypress',prevent.keydown) == -1 ){
1931
- Syn.trigger('keypress',keypressOptions, element )
1932
- }
1933
- }
1934
- if(defaultResult && defaultResult.nodeName){
1935
- element = defaultResult
1936
- }
1937
-
1938
- if(defaultResult !== null){
1939
- setTimeout(function(){
1940
- Syn.trigger('keyup',Syn.key.options(key, 'keyup'), element )
1941
- callback(runDefaults, element)
1942
- },1)
1943
- }else{
1944
- callback(runDefaults, element)
1945
- }
1946
-
1947
-
1948
- //do mouseup
1949
-
1950
- return element;
1951
- // is there a keypress? .. if not , run default
1952
- // yes -> did we prevent it?, if not run ...
1953
-
1954
- },
1955
- /**
1956
- * @function type
1957
- * Types sequence of [Syn.prototype.key key actions]. Each
1958
- * character is typed, one at a type.
1959
- * Multi-character keys like 'left' should be
1960
- * enclosed in square brackents.
1961
- *
1962
- * The following types 'JavaScript MVC' then deletes the space.
1963
- * @codestart
1964
- * Syn.type('JavaScript MVC[left][left][left]\b','name')
1965
- * @codeend
1966
- *
1967
- * Type is able to handle (and move with) tabs (\t).
1968
- * The following simulates tabing and entering values in a form and
1969
- * eventually submitting the form.
1970
- * @codestart
1971
- * Syn.type("Justin\tMeyer\t27\tjustinbmeyer@gmail.com\r")
1972
- * @codeend
1973
- * @param {String} options the text to type
1974
- * @param {HTMLElement} [element] an element or an id of an element
1975
- * @param {Function} [callback] a function to callback
1976
- */
1977
- _type : function(options, element, callback){
1978
- //break it up into parts ...
1979
- //go through each type and run
1980
- var parts = options.match(/(\[[^\]]+\])|([^\[])/g),
1981
- self = this,
1982
- runNextPart = function(runDefaults, el){
1983
- var part = parts.shift();
1984
- if(!part){
1985
- callback(runDefaults, el);
1986
- return;
1987
- }
1988
- el = el || element;
1989
- if(part.length > 1){
1990
- part = part.substr(1,part.length - 2)
1991
- }
1992
- self._key(part, el, runNextPart)
1993
- }
1994
-
1995
- runNextPart();
1996
-
1997
- }
1998
- });
1999
-
2000
-
2001
- //do support code
2002
- (function(){
2003
- if(!document.body){
2004
- setTimeout(arguments.callee,1)
2005
- return;
2006
- }
2007
-
2008
- var div = document.createElement("div"),
2009
- checkbox,
2010
- submit,
2011
- form,
2012
- input,
2013
- submitted = false,
2014
- anchor,
2015
- textarea;
2016
-
2017
- div.innerHTML = "<form id='outer'>"+
2018
- "<input name='checkbox' type='checkbox'/>"+
2019
- "<input name='radio' type='radio' />"+
2020
- "<input type='submit' name='submitter'/>"+
2021
- "<input type='input' name='inputter'/>"+
2022
- "<input name='one'>"+
2023
- "<input name='two'/>"+
2024
- "<a href='#abc'></a>"+
2025
- "<textarea>1\n2</textarea>"
2026
- "</form>";
2027
-
2028
- document.documentElement.appendChild(div);
2029
- form = div.firstChild;
2030
- checkbox = form.childNodes[0];
2031
- submit = form.childNodes[2];
2032
- anchor = form.getElementsByTagName("a")[0];
2033
- textarea = form.getElementsByTagName("textarea")[0]
2034
- form.onsubmit = function(ev){
2035
- if (ev.preventDefault)
2036
- ev.preventDefault();
2037
- S.support.keypressSubmits = true;
2038
- ev.returnValue = false;
2039
- return false;
2040
- }
2041
- Syn.trigger("keypress", "\r", form.childNodes[3]);
2042
-
2043
-
2044
- Syn.trigger("keypress", "a", form.childNodes[3]);
2045
- S.support.keyCharacters = form.childNodes[3].value == "a";
2046
-
2047
-
2048
- form.childNodes[3].value = "a"
2049
- Syn.trigger("keypress", "\b", form.childNodes[3]);
2050
- S.support.backspaceWorks = form.childNodes[3].value == "";
2051
-
2052
-
2053
-
2054
- form.childNodes[3].onchange = function(){
2055
- S.support.focusChanges = true;
2056
- }
2057
- form.childNodes[3].focus();
2058
- Syn.trigger("keypress", "a", form.childNodes[3]);
2059
- form.childNodes[5].focus();
2060
-
2061
- //test keypress \r on anchor submits
2062
- S.bind(anchor,"click",function(ev){
2063
- if (ev.preventDefault)
2064
- ev.preventDefault();
2065
- S.support.keypressOnAnchorClicks = true;
2066
- ev.returnValue = false;
2067
- return false;
2068
- })
2069
- Syn.trigger("keypress", "\r", anchor);
2070
-
2071
- S.support.textareaCarriage = textarea.value.length == 4
2072
- document.documentElement.removeChild(div);
2073
-
2074
- S.support.ready = true;
2075
- })();
2076
-
2077
-
2078
-
2079
-
2080
-
2081
- })();
2082
-
2083
- // funcunit/synthetic/drag/drag.js
2084
-
2085
- (function($){
2086
-
2087
- // document body has to exists for this test
2088
-
2089
- (function(){
2090
- if (!document.body) {
2091
- setTimeout(arguments.callee, 1)
2092
- return;
2093
- }
2094
- var div = document.createElement('div')
2095
- document.body.appendChild(div);
2096
- Syn.helpers.extend(div.style, {
2097
- width: "100px",
2098
- height: "10000px",
2099
- backgroundColor: "blue",
2100
- position: "absolute",
2101
- top: "10px",
2102
- left: "0px",
2103
- zIndex: 19999
2104
- });
2105
- document.body.scrollTop = 11;
2106
- if(!document.elementFromPoint){
2107
- return;
2108
- }
2109
- var el = document.elementFromPoint(3, 1)
2110
- if (el == div) {
2111
- Syn.support.elementFromClient = true;
2112
- }
2113
- else {
2114
- Syn.support.elementFromPage = true;
2115
- }
2116
- document.body.removeChild(div);
2117
- document.body.scrollTop = 0;
2118
- })();
2119
-
2120
-
2121
- //gets an element from a point
2122
- var elementFromPoint = function(point, element){
2123
- var clientX = point.clientX, clientY = point.clientY, win = Syn.helpers.getWindow(element)
2124
-
2125
- if (Syn.support.elementFromPage) {
2126
- var off = Syn.helpers.scrollOffset(win);
2127
- clientX = clientX + off.left; //convert to pageX
2128
- clientY = clientY + off.top; //convert to pageY
2129
- }
2130
-
2131
- return win.document.elementFromPoint ? win.document.elementFromPoint(clientX, clientY) : element;
2132
- }, //creates an event at a certain point
2133
- createEventAtPoint = function(event, point, element){
2134
- var el = elementFromPoint(point, element)
2135
- Syn.trigger(event, point, el || element)
2136
- return el;
2137
- }, // creates a mousemove event, but first triggering mouseout / mouseover if appropriate
2138
- mouseMove = function(point, element, last){
2139
- var el = elementFromPoint(point, element)
2140
- if (last != el && el && last) {
2141
- var options = Syn.helpers.extend({},point);
2142
- options.relatedTarget = el;
2143
- Syn.trigger("mouseout", options, last);
2144
- options.relatedTarget = last;
2145
- Syn.trigger("mouseover", options, el);
2146
- }
2147
-
2148
- Syn.trigger("mousemove", point, el || element)
2149
- return el;
2150
- }, // start and end are in clientX, clientY
2151
- startMove = function(start, end, duration, element, callback){
2152
- var startTime = new Date(),
2153
- distX = end.clientX - start.clientX,
2154
- distY = end.clientY - start.clientY,
2155
- win = Syn.helpers.getWindow(element),
2156
- current = elementFromPoint(start, element),
2157
- cursor = win.document.createElement('div'),
2158
- calls = 0;
2159
- move = function(){
2160
- //get what fraction we are at
2161
- var now = new Date(),
2162
- scrollOffset = Syn.helpers.scrollOffset(win),
2163
- fraction = ( calls == 0 ? 0 : now - startTime) / duration,
2164
- options = {
2165
- clientX: distX * fraction + start.clientX,
2166
- clientY: distY * fraction + start.clientY
2167
- };
2168
- calls++;
2169
- if (fraction < 1) {
2170
- Syn.helpers.extend(cursor.style, {
2171
- left: (options.clientX + scrollOffset.left + 2) + "px",
2172
- top: (options.clientY + scrollOffset.top + 2) + "px"
2173
- })
2174
- current = mouseMove(options, element, current)
2175
- setTimeout(arguments.callee, 15)
2176
- }
2177
- else {
2178
- current = mouseMove(end, element, current);
2179
- win.document.body.removeChild(cursor)
2180
- callback();
2181
- }
2182
- }
2183
- Syn.helpers.extend(cursor.style, {
2184
- height: "5px",
2185
- width: "5px",
2186
- backgroundColor: "red",
2187
- position: "absolute",
2188
- zIndex: 19999,
2189
- fontSize: "1px"
2190
- })
2191
- win.document.body.appendChild(cursor)
2192
- move();
2193
- },
2194
- startDrag = function(start, end, duration, element, callback){
2195
- createEventAtPoint("mousedown", start, element);
2196
- startMove(start, end, duration, element, function(){
2197
- createEventAtPoint("mouseup", end, element);
2198
- callback();
2199
- })
2200
- },
2201
- center = function(el){
2202
- var j = Syn.jquery()(el),
2203
- o = j.offset();
2204
- return{
2205
- pageX: o.left + (j.width() / 2),
2206
- pageY: o.top + (j.height() / 2)
2207
- }
2208
- },
2209
- convertOption = function(option, win, from){
2210
- var page = /(\d+)[x ](\d+)/,
2211
- client = /(\d+)X(\d+)/,
2212
- relative = /([+-]\d+)[xX ]([+-]\d+)/
2213
- //check relative "+22x-44"
2214
- if (typeof option == 'string' && relative.test(option) && from) {
2215
- var cent = center(from),
2216
- parts = option.match(relative);
2217
- option = {
2218
- pageX: cent.pageX + parseInt(parts[1]),
2219
- pageY: cent.pageY +parseInt(parts[2])
2220
- }
2221
- }
2222
- if (typeof option == 'string' && page.test(option)) {
2223
- var parts = option.match(page)
2224
- option = {
2225
- pageX: parseInt(parts[1]),
2226
- pageY: parseInt(parts[2])
2227
- }
2228
- }
2229
- if (typeof option == 'string' && client.test(option)) {
2230
- var parts = option.match(client)
2231
- option = {
2232
- clientX: parseInt(parts[1]),
2233
- clientY: parseInt(parts[2])
2234
- }
2235
- }
2236
- if (typeof option == 'string') {
2237
- option = Syn.jquery()(option, win.document)[0];
2238
- }
2239
- if (option.nodeName) {
2240
- option = center(option)
2241
- }
2242
- if (option.pageX) {
2243
- var off = Syn.helpers.scrollOffset(win);
2244
- option = {
2245
- clientX: option.pageX - off.left,
2246
- clientY: option.pageY - off.top
2247
- }
2248
- }
2249
- return option;
2250
- }
2251
- /**
2252
- * @add Syn prototype
2253
- */
2254
- Syn.helpers.extend(Syn.init.prototype,{
2255
- /**
2256
- * @function move
2257
- * Moves the cursor from one point to another.
2258
- * <h3>Quick Example</h3>
2259
- * The following moves the cursor from (0,0) in
2260
- * the window to (100,100) in 1 second.
2261
- * @codestart
2262
- * Syn.move(
2263
- * {
2264
- * from: {clientX: 0, clientY: 0},
2265
- * to: {clientX: 100, clientY: 100},
2266
- * duration: 1000
2267
- * },
2268
- * document.document)
2269
- * @codeend
2270
- * <h2>Options</h2>
2271
- * There are many ways to configure the endpoints of the move.
2272
- *
2273
- * <h3>PageX and PageY</h3>
2274
- * If you pass pageX or pageY, these will get converted
2275
- * to client coordinates.
2276
- * @codestart
2277
- * Syn.move(
2278
- * {
2279
- * from: {pageX: 0, pageY: 0},
2280
- * to: {pageX: 100, pageY: 100}
2281
- * },
2282
- * document.document)
2283
- * @codeend
2284
- * <h3>String Coordinates</h3>
2285
- * You can set the pageX and pageY as strings like:
2286
- * @codestart
2287
- * Syn.move(
2288
- * {
2289
- * from: "0x0",
2290
- * to: "100x100"
2291
- * },
2292
- * document.document)
2293
- * @codeend
2294
- * <h3>Element Coordinates</h3>
2295
- * If jQuery is present, you can pass an element as the from or to option
2296
- * and the coordinate will be set as the center of the element.
2297
- * @codestart
2298
- * Syn.move(
2299
- * {
2300
- * from: $(".recipe")[0],
2301
- * to: $("#trash")[0]
2302
- * },
2303
- * document.document)
2304
- * @codeend
2305
- * <h3>Query Strings</h3>
2306
- * If jQuery is present, you can pass a query string as the from or to option.
2307
- * @codestart
2308
- * Syn.move(
2309
- * {
2310
- * from: ".recipe",
2311
- * to: "#trash"
2312
- * },
2313
- * document.document)
2314
- * @codeend
2315
- * <h3>No From</h3>
2316
- * If you don't provide a from, the element argument passed to Syn is used.
2317
- * @codestart
2318
- * Syn.move(
2319
- * { to: "#trash" },
2320
- * 'myrecipe')
2321
- * @codeend
2322
- * @param {Object} options
2323
- * @param {HTMLElement} from
2324
- * @param {Function} callback
2325
- */
2326
- _move : function(options, from, callback){
2327
- //need to convert if elements
2328
- var win = Syn.helpers.getWindow(from),
2329
- fro = convertOption(options.from || from, win),
2330
- to = convertOption(options.to || options, win);
2331
-
2332
- startMove(fro, to, options.duration || 500, from, callback);
2333
- },
2334
- /**
2335
- * @function drag
2336
- * Creates a mousedown and drags from one point to another.
2337
- * Check out [Syn.prototype.move move] for API details.
2338
- *
2339
- * @param {Object} options
2340
- * @param {Object} from
2341
- * @param {Object} callback
2342
- */
2343
- _drag : function(options, from, callback){
2344
- //need to convert if elements
2345
- var win = Syn.helpers.getWindow(from),
2346
- fro = convertOption(options.from || from, win, from),
2347
- to = convertOption(options.to || options, win, from);
2348
-
2349
- startDrag(fro, to, options.duration || 500, from, callback);
2350
- }
2351
- })
2352
-
2353
-
2354
- })();
2355
-