terminus 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +71 -20
- data/bin/terminus +21 -9
- data/lib/capybara/driver/terminus.rb +47 -0
- data/lib/public/loader.js +1 -0
- data/lib/public/style.css +7 -2
- data/lib/public/syn.js +2355 -0
- data/lib/public/terminus.js +294 -1
- data/lib/terminus.rb +50 -6
- data/lib/terminus/application.rb +9 -4
- data/lib/terminus/browser.rb +140 -0
- data/lib/terminus/controller.rb +78 -0
- data/lib/terminus/node.rb +68 -0
- data/lib/terminus/server.rb +1 -6
- data/lib/terminus/timeouts.rb +27 -0
- data/lib/views/bookmarklet.erb +14 -4
- data/lib/views/index.erb +11 -8
- data/spec/spec_helper.rb +22 -0
- data/spec/terminus_driver_spec.rb +20 -0
- data/spec/terminus_session_spec.rb +32 -0
- metadata +105 -42
- data/lib/public/js.packages.js +0 -1
data/README.rdoc
CHANGED
@@ -2,36 +2,87 @@
|
|
2
2
|
|
3
3
|
* http://github.com/jcoglan/terminus
|
4
4
|
|
5
|
-
Terminus is
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
Terminus is an experimental Capybara driver implemented in client-side
|
6
|
+
JavaScript. It lets you script your application in any browser on any
|
7
|
+
device, without needing browser plugins. This allows several types of
|
8
|
+
testing to be automated:
|
9
9
|
|
10
|
+
* Cross-browser testing
|
11
|
+
* Multi-browser interaction e.g. messaging apps
|
12
|
+
* Run tests on remote machines, phones, iPads etc.
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
sudo gem install terminus
|
14
|
+
It is also a remote scripting tool, giving you a REPL that lets you
|
15
|
+
run JavaScript in any number of browsers at once.
|
14
16
|
|
15
17
|
|
16
18
|
== Usage
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
+
Terminus is a Capybara driver. For the most part, you will not use
|
21
|
+
it directly: you will use the Capybara API and it will send instructions
|
22
|
+
to Terminus for execution. To set Terminus as your driver:
|
23
|
+
|
24
|
+
require 'capybara'
|
25
|
+
require 'terminus'
|
26
|
+
|
27
|
+
Capybara.current_driver = :terminus
|
28
|
+
|
29
|
+
Terminus does require some extra setup before you can use it to control
|
30
|
+
your app. First up, you need to start the Terminus server on the machine
|
31
|
+
where your application will be running:
|
32
|
+
|
33
|
+
$ terminus
|
34
|
+
|
35
|
+
This starts the server on port 7004. Now open a browser at
|
36
|
+
http://127.0.0.1:7004/. (I recommend using the IP address of the Terminus
|
37
|
+
host; Chrome has bugs that can stop WebSockets working if you use the
|
38
|
+
hostname.) This is the 'holding page'. A browser is said to be 'docked'
|
39
|
+
while it is visiting this page, meaning it is ready and waiting to run
|
40
|
+
some tests for you.
|
41
|
+
|
42
|
+
To let Terminus control your app's pages, you need to include this
|
43
|
+
just before the closing +body+ tag when in testing mode:
|
44
|
+
|
45
|
+
<!-- For example if you're using Rails -->
|
46
|
+
|
47
|
+
<% if Rails.env.test? %>
|
48
|
+
<%= Terminus.driver_script '127.0.0.1' %>
|
49
|
+
<% end %>
|
50
|
+
|
51
|
+
If the browser you're using is on a different machine to the Terminus
|
52
|
+
server, replace <tt>127.0.0.1</tt> with the Terminus machine's IP as
|
53
|
+
seen by the browser. For example if I'm running the browser in VirtualBox
|
54
|
+
and the Terminus server on the host OS, then I set the IP to <tt>10.0.2.2</tt>.
|
55
|
+
You could also use <tt>request.host</tt> to get the right IP automatically
|
56
|
+
if you're running your app and your Terminus server on the same machine.
|
57
|
+
|
58
|
+
Finally, in your tests you need to make sure there's a docked browser
|
59
|
+
and select it. In a 'before' block, run the following:
|
60
|
+
|
61
|
+
Terminus.ensure_docked_browser
|
62
|
+
Terminus.browser = :docked
|
63
|
+
|
64
|
+
After each test is finished, you need to return the browser to the
|
65
|
+
holding page to make it ready to accept new work. In an 'after' block:
|
66
|
+
|
67
|
+
Terminus.return_to_dock
|
20
68
|
|
21
|
-
|
69
|
+
This returns all currently connected browsers to the holding page.
|
22
70
|
|
23
|
-
You should see something like this:
|
24
71
|
|
25
|
-
|
26
|
-
>>
|
72
|
+
== Notes / to-do
|
27
73
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
74
|
+
* Support IE, which has no built-in XPath engine for querying the DOM.
|
75
|
+
I'm working on Pathology (see http://github.com/jcoglan/pathology) to
|
76
|
+
try and fix this but it's currently not fast enough.
|
77
|
+
|
78
|
+
* Allow <tt>Terminus.browser=</tt> to select browsers by name and
|
79
|
+
version so we can control multiple browsers at once.
|
80
|
+
|
81
|
+
* It's slow, especially at filling out forms. Use it to sanity-check
|
82
|
+
your cross-browser code and JavaScript, not to test your whole app.
|
83
|
+
|
84
|
+
* It can be a little brittle, and occasionally there seem to be race
|
85
|
+
conditions when running the Capybara specs.
|
35
86
|
|
36
87
|
|
37
88
|
== License
|
data/bin/terminus
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
|
-
require 'readline'
|
5
4
|
require 'oyster'
|
6
|
-
require File.dirname(__FILE__) + '/../lib/terminus'
|
5
|
+
require File.expand_path(File.dirname(__FILE__) + '/../lib/terminus')
|
7
6
|
|
8
7
|
spec = Oyster.spec do
|
9
8
|
name "terminus -- controll web browsers from the command line"
|
10
9
|
synopsis "terminus [--port PORT]"
|
11
10
|
|
12
|
-
integer :port, :default =>
|
11
|
+
integer :port, :default => Terminus::DEFAULT_PORT
|
13
12
|
end
|
14
13
|
|
15
14
|
begin
|
@@ -17,16 +16,29 @@ begin
|
|
17
16
|
app = Terminus.create(options)
|
18
17
|
|
19
18
|
app.run!
|
20
|
-
puts "Terminus running
|
19
|
+
puts "Terminus server running on port #{options[:port]}"
|
21
20
|
puts "Press CTRL-C to exit"
|
21
|
+
puts ""
|
22
22
|
|
23
23
|
trap("INT") { app.stop! ; exit }
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
begin
|
26
|
+
require 'readline'
|
27
|
+
|
28
|
+
puts "This is the Terminus console. You can type JavaScript"
|
29
|
+
puts "in here and it will be executed on all connected pages."
|
30
|
+
puts ""
|
31
|
+
|
32
|
+
loop {
|
33
|
+
script, result = Readline.readline('>> '), nil
|
34
|
+
Readline::HISTORY.push(script)
|
35
|
+
app.execute(script) { |r| result = r }
|
36
|
+
}
|
37
|
+
rescue LoadError
|
38
|
+
puts "If you install the readline library, you'll get a console"
|
39
|
+
puts "that lets you execute JavaScript in connected browsers."
|
40
|
+
puts ""
|
41
|
+
end
|
30
42
|
|
31
43
|
rescue Oyster::HelpRendered
|
32
44
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
class Capybara::Driver::Terminus < Capybara::Driver::Base
|
4
|
+
def initialize(app = nil)
|
5
|
+
raise ArgumentError.new if app.nil?
|
6
|
+
@app = app
|
7
|
+
@rack_server = Capybara::Server.new(@app)
|
8
|
+
@rack_server.boot if Capybara.run_server
|
9
|
+
end
|
10
|
+
|
11
|
+
def visit(path)
|
12
|
+
browser.visit @rack_server.url(path)
|
13
|
+
end
|
14
|
+
|
15
|
+
extend Forwardable
|
16
|
+
def_delegators :browser, :body,
|
17
|
+
:current_path,
|
18
|
+
:current_url,
|
19
|
+
:evaluate_script,
|
20
|
+
:execute_script,
|
21
|
+
:find,
|
22
|
+
:reset!,
|
23
|
+
:source
|
24
|
+
|
25
|
+
def within_window(name)
|
26
|
+
current_browser = browser
|
27
|
+
Terminus.browser = {:name => name}
|
28
|
+
yield
|
29
|
+
Terminus.browser = current_browser
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def browser
|
35
|
+
Terminus.ensure_browser
|
36
|
+
Terminus.browser
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Capybara 0.3.9 looks in the Capybara::Driver namespace for
|
41
|
+
# appropriate drivers. 0.4 uses this registration API instead.
|
42
|
+
if Capybara.respond_to?(:register_driver)
|
43
|
+
Capybara.register_driver :terminus do |app|
|
44
|
+
Capybara::Driver::Terminus.new(app)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1 @@
|
|
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')}});
|
data/lib/public/style.css
CHANGED
@@ -21,8 +21,13 @@ body {
|
|
21
21
|
font: 13px Helvetica, Arial, sans-serif;
|
22
22
|
}
|
23
23
|
|
24
|
+
.footer p {
|
25
|
+
margin: 0;
|
26
|
+
line-height: 1.5em;
|
27
|
+
}
|
28
|
+
|
24
29
|
h1 {
|
25
|
-
font: bold 64px Helvetica, Arial, sans-serif;
|
30
|
+
font: bold 64px FreeSans, Helvetica, Arial, sans-serif;
|
26
31
|
letter-spacing: -0.03em;
|
27
32
|
margin: 0 0 32px;
|
28
33
|
color: #f00846;
|
@@ -39,6 +44,6 @@ a:hover {
|
|
39
44
|
}
|
40
45
|
|
41
46
|
p {
|
42
|
-
margin: 0;
|
47
|
+
margin: 1.5em 0 0;
|
43
48
|
}
|
44
49
|
|
data/lib/public/syn.js
ADDED
@@ -0,0 +1,2355 @@
|
|
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
|
+
|