PageRenderTime 616ms CodeModel.GetById 131ms app.highlight 289ms RepoModel.GetById 154ms app.codeStats 1ms

/wwwroot/vendor/syn.js

http://github.com/AF83/ucengine
JavaScript | 2289 lines | 1477 code | 204 blank | 608 comment | 341 complexity | e5fa2e4a4b1cf500ba0b16a4414fea33 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1// funcunit/synthetic/synthetic.js
   2
   3(function($){
   4
   5	
   6	
   7var 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
  42Syn.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
  73Syn.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 
  81so you can do a sequence of events with a single (final) callback.  
  82</p><p>
  83If an element isn't provided to then, it uses the previous Syn's element.
  84</p>
  85The following does a lot of stuff before checking the result:
  86@codestart
  87Syn.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>
  97If 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
 112Syn.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 */
 147Syn = function(type, options, element, callback){		
 148	return ( new Syn.init(type, options, element, callback) )
 149}
 150	
 151if(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 */	
 169extend(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	/**
 196	 * Returns an object with the args for a Syn.
 197	 * @hide
 198	 * @return {Object}
 199	 */
 200	args : function(){
 201		var res = {}
 202		for(var i=0; i < arguments.length; i++){
 203			if(typeof arguments[i] == 'function'){
 204				res.callback = arguments[i]
 205			}else if(arguments[i] && arguments[i].jquery){
 206				res.element = arguments[i][0];
 207			}else if(arguments[i] && arguments[i].nodeName){
 208				res.element = arguments[i];
 209			}else if(res.options && typeof arguments[i] == 'string'){ //we can get by id
 210				res.element = document.getElementById(arguments[i])
 211			}
 212			else if(arguments[i]){
 213				res.options = arguments[i];
 214			}
 215		}
 216		return res;
 217	},
 218	click : function( options, element, callback){
 219		Syn('click!',options,element, callback);
 220	},
 221	/**
 222	 * @attribute defaults
 223	 * Default actions for events.  Each default function is called with this as its 
 224	 * element.  It should return true if a timeout 
 225	 * should happen after it.  If it returns an element, a timeout will happen
 226	 * and the next event will happen on that element.
 227	 */
 228	defaults : {
 229		focus : function(){
 230			if(!Syn.support.focusChanges){
 231				var element = this,
 232					nodeName = element.nodeName.toLowerCase();
 233				Syn.data(element,"syntheticvalue", element.value)
 234				
 235				if(nodeName == "input"){
 236					
 237					bind(element, "blur", function(){
 238						
 239						if( Syn.data(element,"syntheticvalue") !=  element.value){
 240							
 241							Syn.trigger("change", {}, element);
 242						}
 243						unbind(element,"blur", arguments.callee)
 244					})
 245					
 246				}
 247			}
 248		}
 249	},
 250	changeOnBlur : function(element, prop, value){
 251		
 252		bind(element, "blur", function(){		
 253			if( value !=  element[prop]){
 254				Syn.trigger("change", {}, element);
 255			}
 256			unbind(element,"blur", arguments.callee)
 257		})
 258		
 259	},
 260	/**
 261	 * Returns the closest element of a particular type.
 262	 * @hide
 263	 * @param {Object} el
 264	 * @param {Object} type
 265	 */
 266	closest : function(el, type){
 267		while(el && el.nodeName.toLowerCase() != type.toLowerCase()){
 268			el = el.parentNode
 269		}
 270		return el;
 271	},
 272	/**
 273	 * adds jQuery like data (adds an expando) and data exists FOREVER :)
 274	 * @hide
 275	 * @param {Object} el
 276	 * @param {Object} key
 277	 * @param {Object} value
 278	 */
 279	data : function(el, key, value){
 280		var d;
 281		if(!el[expando]){
 282			el[expando] = id++;
 283		}
 284		if(!data[el[expando]]){
 285			data[el[expando]] = {};
 286		}
 287		d = data[el[expando]]
 288		if(value){
 289			data[el[expando]][key] = value;
 290		}else{
 291			return data[el[expando]][key];
 292		}
 293	},
 294	/**
 295	 * Calls a function on the element and all parents of the element until the function returns
 296	 * false.
 297	 * @hide
 298	 * @param {Object} el
 299	 * @param {Object} func
 300	 */
 301	onParents : function(el, func){
 302		var res;
 303		while(el && res !== false){
 304			res = func(el)
 305			el = el.parentNode
 306		}
 307		return el;
 308	},
 309	//regex to match focusable elements
 310	focusable : /^(a|area|frame|iframe|label|input|select|textarea|button|html|object)$/i,
 311	/**
 312	 * Returns if an element is focusable
 313	 * @hide
 314	 * @param {Object} elem
 315	 */
 316	isFocusable : function(elem){
 317		var attributeNode;
 318		return ( this.focusable.test(elem.nodeName) || (
 319			(attributeNode = elem.getAttributeNode( "tabIndex" )) && attributeNode.specified ) )
 320			&& Syn.isVisible(elem)
 321	},
 322	/**
 323	 * Returns if an element is visible or not
 324	 * @hide
 325	 * @param {Object} elem
 326	 */
 327	isVisible : function(elem){
 328		return (elem.offsetWidth && elem.offsetHeight) || (elem.clientWidth && elem.clientHeight)
 329	},
 330	/**
 331	 * Gets the tabIndex as a number or null
 332	 * @hide
 333	 * @param {Object} elem
 334	 */
 335	tabIndex : function(elem){
 336		var attributeNode = elem.getAttributeNode( "tabIndex" );
 337		return attributeNode && attributeNode.specified && ( parseInt( elem.getAttribute('tabIndex') ) || 0 )
 338	},
 339	bind : bind,
 340	unbind : unbind,
 341	browser: browser,
 342	//some generic helpers
 343	helpers : {
 344		createEventObject : createEventObject,
 345		createBasicStandardEvent : function(type, defaults){
 346			var event;
 347			try {
 348				event = document.createEvent("Events");
 349			} catch(e2) {
 350				event = document.createEvent("UIEvents");
 351			} finally {
 352				event.initEvent(type, true, true);
 353				extend(event, defaults);
 354			}
 355			return event;
 356		},
 357		inArray : function(item, array){
 358			for(var i =0; i < array.length; i++){
 359				if(array[i] == item){
 360					return i;
 361				}
 362			}
 363			return -1;
 364		},
 365		getWindow : function(element){
 366			return element.ownerDocument.defaultView || element.ownerDocument.parentWindow
 367		},
 368		extend:  extend,
 369		scrollOffset : function(win){
 370			var doc = win.document.documentElement,
 371				body = win.document.body;
 372			return {
 373				left :  (doc && doc.scrollLeft || body && body.scrollLeft || 0) + (doc.clientLeft || 0),
 374				top : (doc && doc.scrollTop || body && body.scrollTop || 0) + (doc.clientTop || 0)
 375			}
 376				
 377		},
 378		addOffset : function(options, el){
 379			if(typeof options == 'object' &&
 380			   options.clientX === undefined &&
 381			   options.clientY === undefined &&
 382			   options.pageX   === undefined &&
 383			   options.pageY   === undefined && window.jQuery){
 384				var el = window.jQuery(el)
 385					off = el.offset();
 386				options.pageX = off.left + el.width() /2 ;
 387				options.pageY = off.top + el.height() /2 ;
 388			}
 389		}
 390	},
 391	// place for key data
 392	key : {
 393		ctrlKey : null,
 394		altKey : null,
 395		shiftKey : null,
 396		metaKey : null
 397	},
 398	//triggers an event on an element, returns true if default events should be run
 399	/**
 400	 * Dispatches an event and returns true if default events should be run.
 401	 * @hide
 402	 * @param {Object} event
 403	 * @param {Object} element
 404	 * @param {Object} type
 405	 * @param {Object} autoPrevent
 406	 */
 407	dispatch : (document.documentElement.dispatchEvent ? 
 408				function(event, element, type, autoPrevent){
 409					var preventDefault = event.preventDefault, 
 410						prevents = autoPrevent ? -1 : 0;
 411					
 412					//automatically prevents the default behavior for this event
 413					//this is to protect agianst nasty browser freezing bug in safari
 414					if(autoPrevent){
 415						bind(element, type, function(ev){
 416							ev.preventDefault()
 417							unbind(this, type, arguments.callee)
 418						})
 419					}
 420					
 421					
 422					event.preventDefault = function(){
 423						prevents++;
 424						if(++prevents > 0){
 425							preventDefault.apply(this,[]);
 426						}
 427					}
 428					element.dispatchEvent(event)
 429					return prevents <= 0;
 430				} : 
 431				function(event, element, type){
 432					try {window.event = event;}catch(e) {}
 433					//source element makes sure element is still in the document
 434					return element.sourceIndex <= 0 || element.fireEvent('on'+type, event)
 435				}
 436			),
 437	/**
 438	 * @attribute
 439	 * @hide
 440	 * An object of eventType -> function that create that event.
 441	 */
 442	create :  {
 443		//-------- PAGE EVENTS ---------------------
 444		page : {
 445			event : document.createEvent ? function(type, options, element){
 446					var event = element.ownerDocument.createEvent("Events");
 447					event.initEvent(type, true, true ); 
 448					return event;
 449				} : createEventObject
 450		},
 451		// unique events
 452		focus : {
 453			event : function(type, options, element){
 454				Syn.onParents(element, function(el){
 455					if( Syn.isFocusable(el)){
 456						if(el.nodeName.toLowerCase() != 'html'){
 457							el.focus();
 458						}
 459						return false
 460					}
 461				});
 462				return true;
 463			}
 464		}
 465	},
 466	/**
 467	 * @attribute support
 468	 * Feature detected properties of a browser's event system.
 469	 * Support has the following properties:
 470	 * <ul>
 471	 * 	<li><code>clickChanges</code> - clicking on an option element creates a change event.</li>
 472	 *  <li><code>clickSubmits</code> - clicking on a form button submits the form.</li>
 473	 *  <li><code>mouseupSubmits</code> - a mouseup on a form button submits the form.</li>
 474	 *  <li><code>radioClickChanges</code> - clicking a radio button changes the radio.</li>
 475	 *  <li><code>focusChanges</code> - focus/blur creates a change event.</li>
 476	 *  <li><code>linkHrefJS</code> - An achor's href JavaScript is run.</li>
 477	 *  <li><code>mouseDownUpClicks</code> - A mousedown followed by mouseup creates a click event.</li>
 478	 *  <li><code>tabKeyTabs</code> - A tab key changes tabs.</li>
 479	 *  <li><code>keypressOnAnchorClicks</code> - Keying enter on an anchor triggers a click.</li>
 480	 * </ul>
 481	 */
 482	support : {
 483		clickChanges : false,
 484		clickSubmits : false,
 485		keypressSubmits : false,
 486		mouseupSubmits: false,
 487		radioClickChanges : false,
 488		focusChanges : false,
 489		linkHrefJS : false,
 490		keyCharacters : false,
 491		backspaceWorks : false,
 492		mouseDownUpClicks : false,
 493		tabKeyTabs : false,
 494		keypressOnAnchorClicks : false,
 495		optionClickBubbles : false
 496	},
 497	/**
 498	 * Creates a synthetic event and dispatches it on the element.  
 499	 * This will run any default actions for the element.
 500	 * Typically you want to use Syn, but if you want the return value, use this.
 501	 * @param {String} type
 502	 * @param {Object} options
 503	 * @param {HTMLElement} element
 504	 * @return {Boolean} true if default events were run, false if otherwise.
 505	 */
 506	trigger : function(type, options, element){
 507		options || (options = {});
 508		
 509		var create = Syn.create,
 510			setup = create[type] && create[type].setup,
 511			kind = key.test(type) ? 
 512				'key' : 
 513				( page.test(type) ?
 514					"page" : "mouse" ),
 515				createType = create[type] || {},
 516				createKind = create[kind],
 517				event,
 518				ret,
 519				autoPrevent = options._autoPrevent,
 520				dispatchEl = element;
 521		
 522		//any setup code?
 523		Syn.support.ready && setup && setup(type, options, element);
 524		
 525		
 526		//get kind
 527		
 528		delete options._autoPrevent;
 529			
 530		if(createType.event){
 531			ret = createType.event(type, options, element)
 532		}else{
 533			//convert options
 534			options = createKind.options ? createKind.options(type,options,element) : options;
 535			
 536			if(!Syn.support.changeBubbles && /option/i.test(element.nodeName)){
 537				dispatchEl = element.parentNode; //jQuery expects clicks on select
 538			}
 539			
 540			//create the event
 541			event = createKind.event(type,options,dispatchEl)
 542			
 543			//send the event
 544			ret = Syn.dispatch(event, dispatchEl, type, autoPrevent)
 545		}
 546		
 547		//run default behavior
 548		ret && Syn.support.ready 
 549			&& Syn.defaults[type] 
 550			&& Syn.defaults[type].call(element, options, autoPrevent);
 551		return ret;
 552	},
 553	eventSupported: function( eventName ) { 
 554		var el = document.createElement("div"); 
 555		eventName = "on" + eventName; 
 556
 557		var isSupported = (eventName in el); 
 558		if ( !isSupported ) { 
 559			el.setAttribute(eventName, "return;"); 
 560			isSupported = typeof el[eventName] === "function"; 
 561		} 
 562		el = null; 
 563
 564		return isSupported; 
 565	}
 566	
 567});
 568	var h = Syn.helpers;
 569/**
 570 * @Prototype
 571 */
 572extend(Syn.init.prototype,{
 573	/**
 574	 * @function then
 575	 * <p>
 576	 * Then is used to chain a sequence of actions to be run one after the other.
 577	 * This is useful when many asynchronous actions need to be performed before some
 578	 * final check needs to be made.
 579	 * </p>
 580	 * <p>The following clicks and types into the <code>id='age'</code> element and then checks that only numeric characters can be entered.</p>
 581	 * <h3>Example</h3>
 582	 * @codestart
 583	 * Syn('click',{},'age')
 584	 *   .then('type','I am 12',function(){
 585	 *   equals($('#age').val(),"12")  
 586	 * })
 587	 * @codeend
 588	 * If the element argument is undefined, then the last element is used.
 589	 * 
 590	 * @param {String} type The type of event or action to create: "_click", "_dblclick", "_drag", "_type".
 591	 * @param {Object} options Optiosn to pass to the event.
 592	 * @param {String|HTMLElement} [element] A element's id or an element.  If undefined, defaults to the previous element.
 593	 * @param {Function} [callback] A function to callback after the action has run, but before any future chained actions are run.
 594	 */
 595	then : function(type, options, element, callback){
 596		if(Syn.autoDelay){
 597			this.delay();
 598		}
 599		var args = Syn.args(options,element, callback),
 600			self = this;
 601
 602		
 603		//if stack is empty run right away
 604		
 605		//otherwise ... unshift it
 606		this.queue.unshift(function(el, prevented){
 607			
 608			if(typeof this[type] == "function") {
 609				this.element = args.element || el;
 610				this[type](args.options, this.element, function(defaults, el){
 611					args.callback && args.callback.apply(self, arguments);
 612					self.done.apply(self, arguments)		
 613				})
 614			}else{
 615				this.result = Syn.trigger(type, args.options, args.element);
 616				args.callback && args.callback.call(this, args.element, this.result);
 617				return this;
 618			}
 619		})
 620		return this;
 621	},
 622	/**
 623	 * Delays the next command a set timeout.
 624	 * @param {Number} [timeout]
 625	 * @param {Function} [callback]
 626	 */
 627	delay : function(timeout, callback){
 628		if(typeof timeout == 'function'){
 629			callback = timeout;
 630			timeout = null;
 631		}
 632		timeout = timeout || 600
 633		var self = this;
 634		this.queue.unshift(function(){
 635			setTimeout(function(){
 636				callback && callback.apply(self,[])
 637				self.done.apply(self, arguments)
 638			},timeout)
 639		})
 640		return this;
 641	},
 642	done : function( defaults, el){
 643		el && (this.element = el);;
 644		if(this.queue.length){
 645			this.queue.pop().call(this, this.element, defaults);
 646		}
 647		
 648	},
 649	/**
 650	 * @function click
 651	 * Clicks an element by triggering a mousedown, 
 652	 * mouseup, 
 653	 * and a click event.
 654	 * <h3>Example</h3>
 655	 * @codestart
 656	 * Syn.click({},'create',function(){
 657	 *   //check something
 658	 * })
 659	 * @codeend
 660	 * You can also provide the coordinates of the click.  
 661	 * If jQuery is present, it will set clientX and clientY
 662	 * for you.  Here's how to set it yourself:
 663	 * @codestart
 664	 * Syn.click(
 665	 *     {clientX: 20, clientY: 100},
 666	 *     'create',
 667	 *     function(){
 668	 *       //check something
 669	 *     })
 670	 * @codeend
 671	 * You can also provide pageX and pageY and Syn will convert it for you.
 672	 * @param {Object} options
 673	 * @param {HTMLElement} element
 674	 * @param {Function} callback
 675	 */
 676	"_click" : function(options, element, callback){
 677		Syn.helpers.addOffset(options, element);
 678		Syn.trigger("mousedown", options, element);
 679		
 680		//timeout is b/c IE is stupid and won't call focus handlers
 681		setTimeout(function(){
 682			Syn.trigger("mouseup", options, element)
 683			if(!Syn.support.mouseDownUpClicks){
 684				Syn.trigger("click", options, element)
 685			}else{
 686				//we still have to run the default (presumably)
 687				Syn.defaults.click.call(element)
 688			}
 689			callback(true)
 690		},1)
 691	},
 692	/**
 693	 * Right clicks in browsers that support it (everyone but opera).
 694	 * @param {Object} options
 695	 * @param {Object} element
 696	 * @param {Object} callback
 697	 */
 698	"_rightClick" : function(options, element, callback){
 699		Syn.helpers.addOffset(options, element);
 700		var mouseopts =  extend( extend({},Syn.mouse.browser.mouseup ), options)
 701		
 702		Syn.trigger("mousedown", mouseopts, element);
 703		
 704		//timeout is b/c IE is stupid and won't call focus handlers
 705		setTimeout(function(){
 706			Syn.trigger("mouseup", mouseopts, element)
 707			if (Syn.mouse.browser.contextmenu) {
 708				Syn.trigger("contextmenu", 
 709					extend( extend({},Syn.mouse.browser.contextmenu ), options), 
 710					element)
 711			}
 712			callback(true)
 713		},1)
 714	},
 715	/**
 716	 * @function dblclick
 717	 * Dblclicks an element.  This runs two [Syn.prototype.click click] events followed by
 718	 * a dblclick on the element.
 719	 * <h3>Example</h3>
 720	 * @codestart
 721	 * Syn.dblclick({},'open')
 722	 * @codeend
 723	 * @param {Object} options
 724	 * @param {HTMLElement} element
 725	 * @param {Function} callback
 726	 */
 727	"_dblclick" : function(options, element, callback){
 728		Syn.helpers.addOffset(options);
 729		var self = this;
 730		this["click!"](options, element, function(){
 731			self["click!"](options, element, function(){
 732				Syn.trigger("dblclick", options, element)
 733				callback(true)
 734			})
 735		})
 736	}
 737})
 738
 739var actions = ["click","dblclick","move","drag","key","type",'rightClick'],
 740	makeAction = function(name){
 741		Syn[name] = function(options, element, callback){
 742			return Syn("_"+name, options, element, callback)
 743		}
 744		Syn.init.prototype[name] = function(options, element, callback){
 745			return this.then("_"+name, options, element, callback)
 746		}
 747	}
 748for(var i=0; i < actions.length; i++){
 749	makeAction(actions[i]);
 750}
 751/**
 752 * Used for creating and dispatching synthetic events.
 753 * @codestart
 754 * new MVC.Syn('click').send(MVC.$E('id'))
 755 * @codeend
 756 * @init Sets up a synthetic event.
 757 * @param {String} type type of event, ex: 'click'
 758 * @param {optional:Object} options
 759 */
 760
 761if (window.jQuery) {
 762	jQuery.fn.triggerSyn = function(type, options, callback){
 763		Syn(type, options, this[0], callback)
 764		return this;
 765	};
 766}
 767
 768window.Syn = Syn;
 769	
 770
 771})();
 772
 773// funcunit/synthetic/mouse.js
 774
 775(function($){
 776
 777
 778var h = Syn.helpers;
 779
 780Syn.mouse = {};
 781h.extend(Syn.defaults,{
 782	mousedown : function(options){
 783		Syn.trigger("focus", {}, this)
 784	},
 785	click : function(){
 786		// prevents the access denied issue in IE if the click causes the element to be destroyed
 787		var element = this;
 788		try {
 789			element.nodeType;
 790		} catch(e){
 791			return;
 792		}
 793		//get old values
 794		var href,
 795			checked = Syn.data(element,"checked"),
 796			scope = Syn.helpers.getWindow(element),
 797			nodeName = element.nodeName.toLowerCase();
 798		
 799		if( (href = Syn.data(element,"href") ) ){
 800			element.setAttribute('href',href)
 801		}
 802
 803		
 804		
 805		//run href javascript
 806		if(!Syn.support.linkHrefJS 
 807			&& /^\s*javascript:/.test(element.href)){
 808			//eval js
 809			var code = element.href.replace(/^\s*javascript:/,"")
 810				
 811			//try{
 812			if (code != "//" && code.indexOf("void(0)") == -1) {
 813				if(window.selenium){
 814					eval("with(selenium.browserbot.getCurrentWindow()){"+code+"}")
 815				}else{
 816					eval("with(scope){"+code+"}")
 817				}
 818			}
 819		}
 820		
 821		//submit a form
 822		if(nodeName == "input" 
 823			&& element.type == "submit" 
 824			&& !(Syn.support.clickSubmits)){
 825				
 826			var form =  Syn.closest(element, "form");
 827			if(form){
 828				Syn.trigger("submit",{},form)
 829			}
 830			
 831		}
 832		//follow a link, probably needs to check if in an a.
 833		if(nodeName == "a" 
 834			&& element.href 
 835			&& !/^\s*javascript:/.test(element.href)){
 836				
 837			scope.location.href = element.href;
 838			
 839		}
 840		
 841		//change a checkbox
 842		if(nodeName == "input" 
 843			&& element.type == "checkbox"){
 844			
 845			if(!Syn.support.clickChecks && !Syn.support.changeChecks){
 846				element.checked = !element.checked;
 847			}
 848			if(!Syn.support.clickChanges){
 849				Syn.trigger("change",{},  element );
 850			}
 851		}
 852		
 853		//change a radio button
 854		if(nodeName == "input" && element.type == "radio"){  // need to uncheck others if not checked
 855			
 856			if(!Syn.support.clickChecks && !Syn.support.changeChecks){
 857				//do the checks manually 
 858				if(!element.checked){ //do nothing, no change
 859					element.checked = true;
 860				}
 861			}
 862			if(checked != element.checked && !Syn.support.radioClickChanges){
 863				Syn.trigger("change",{},  element );
 864			}
 865		}
 866		// change options
 867		if(nodeName == "option" && Syn.data(element,"createChange")){
 868			Syn.trigger("change",{}, element.parentNode);//does not bubble
 869			Syn.data(element,"createChange",false)
 870		}
 871	}
 872})
 873	
 874
 875//add create and setup behavior for mosue events
 876h.extend(Syn.create,{
 877	mouse : {
 878		options : function(type, options, element){
 879			var doc = document.documentElement, body = document.body,
 880				center = [options.pageX || 0, options.pageY || 0] 
 881			return h.extend({
 882				bubbles : true,cancelable : true,
 883				view : window,detail : 1,
 884				screenX : 1, screenY : 1,
 885				clientX : options.clientX || center[0] -(doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0), 
 886				clientY : options.clientY || center[1] -(doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0),
 887				ctrlKey : !!Syn.key.ctrlKey, 
 888				altKey : !!Syn.key.altKey, 
 889				shiftKey : !!Syn.key.shiftKey, 
 890				metaKey : !!Syn.key.metaKey,
 891				button : (type == 'contextmenu' ? 2 : 0), 
 892				relatedTarget : document.documentElement
 893			}, options);
 894		},
 895		event : document.createEvent ? 
 896			function(type, defaults, element){  //Everyone Else
 897				var event;
 898				
 899				try {
 900					event = element.ownerDocument.createEvent('MouseEvents');
 901					event.initMouseEvent(type, 
 902						defaults.bubbles, defaults.cancelable, 
 903						defaults.view, 
 904						defaults.detail, 
 905						defaults.screenX, defaults.screenY,defaults.clientX,defaults.clientY,
 906						defaults.ctrlKey,defaults.altKey,defaults.shiftKey,defaults.metaKey,
 907						defaults.button,defaults.relatedTarget);
 908				} catch(e) {
 909					event = h.createBasicStandardEvent(type,defaults)
 910				}
 911				event.synthetic = true;
 912				return event;
 913			} : 
 914			h.createEventObject
 915	},
 916	click : {
 917		setup : function(type, options, element){
 918			try{
 919				Syn.data(element,"checked", element.checked);
 920			}catch(e){}
 921			if( 
 922				element.nodeName.toLowerCase() == "a" 
 923				&& element.href  
 924				&& !/^\s*javascript:/.test(element.href)){
 925				
 926				//save href
 927				Syn.data(element,"href", element.href)
 928				
 929				//remove b/c safari/opera will open a new tab instead of changing the page
 930				element.setAttribute('href','javascript://')
 931			}
 932			//if select or option, save old value and mark to change
 933			
 934			
 935			if(/option/i.test(element.nodeName)){
 936				var child = element.parentNode.firstChild,
 937				i = -1;
 938				while(child){
 939					if(child.nodeType ==1){
 940						i++;
 941						if(child == element) break;
 942					}
 943					child = child.nextSibling;
 944				}
 945				if(i !== element.parentNode.selectedIndex){
 946					//shouldn't this wait on triggering
 947					//change?
 948					element.parentNode.selectedIndex = i;
 949					Syn.data(element,"createChange",true)
 950				}
 951			}
 952		}
 953	},
 954	mousedown : {
 955		setup : function(type,options, element){
 956			var nn = element.nodeName.toLowerCase();
 957			//we have to auto prevent default to prevent freezing error in safari
 958			if(Syn.browser.safari && (nn == "select" || nn == "option" )){
 959				options._autoPrevent = true;
 960			}
 961		}
 962	}
 963});
 964//do support code
 965(function(){
 966	if(!document.body){
 967		setTimeout(arguments.callee,1)
 968		return;
 969	}
 970	var oldSynth = window.__synthTest;
 971	window.__synthTest = function(){
 972		Syn.support.linkHrefJS = true;
 973	}
 974	var div = document.createElement("div"), 
 975		checkbox, 
 976		submit, 
 977		form, 
 978		input, 
 979		select;
 980		
 981	div.innerHTML = "<form id='outer'>"+
 982		"<input name='checkbox' type='checkbox'/>"+
 983		"<input name='radio' type='radio' />"+
 984		"<input type='submit' name='submitter'/>"+
 985		"<input type='input' name='inputter'/>"+
 986		"<input name='one'>"+
 987		"<input name='two'/>"+
 988		"<a href='javascript:__synthTest()' id='synlink'></a>"+
 989		"<select><option></option></select>"+
 990		"</form>";
 991	document.documentElement.appendChild(div);
 992	form = div.firstChild
 993	checkbox = form.childNodes[0];
 994	submit = form.childNodes[2];
 995	select = form.getElementsByTagName('select')[0]
 996	
 997	checkbox.checked = false;
 998	checkbox.onchange = function(){
 999		Syn.support.clickChanges = true;
1000	}
1001
1002	Syn.trigger("click", {}, checkbox)
1003	Syn.support.clickChecks = checkbox.checked;
1004	checkbox.checked = false;
1005	
1006	Syn.trigger("change", {}, checkbox);
1007	
1008	Syn.support.changeChecks = checkbox.checked;
1009	
1010	form.onsubmit = function(ev){
1011		if (ev.preventDefault) 
1012			ev.preventDefault();
1013		Syn.support.clickSubmits = true;
1014		return false;
1015	}
1016	Syn.trigger("click", {}, submit)
1017
1018		
1019	
1020	form.childNodes[1].onchange = function(){
1021		Syn.support.radioClickChanges = true;
1022	}
1023	Syn.trigger("click", {}, form.childNodes[1])
1024	
1025	
1026	Syn.bind(div, 'click', function(){
1027		Syn.support.optionClickBubbles = true;
1028		Syn.unbind(div,'click', arguments.callee)
1029	})
1030	Syn.trigger("click",{},select.firstChild)
1031	
1032	
1033	Syn.support.changeBubbles = Syn.eventSupported('change');
1034	
1035	//test if mousedown followed by mouseup causes click (opera), make sure there are no clicks after this
1036	div.onclick = function(){
1037		Syn.support.mouseDownUpClicks = true;
1038	}
1039	Syn.trigger("mousedown",{},div)
1040	Syn.trigger("mouseup",{},div)
1041	
1042	document.documentElement.removeChild(div);
1043	
1044	//check stuff
1045	window.__synthTest = oldSynth;
1046	//support.ready = true;
1047})();
1048
1049
1050
1051})();
1052
1053// funcunit/synthetic/browsers.js
1054
1055(function($){
1056
1057	Syn.key.browsers = {
1058		webkit : {
1059			'prevent':
1060			 {"keyup":[],"keydown":["char","keypress"],"keypress":["char"]},
1061			'character':
1062			 {"keydown":[0,"key"],"keypress":["char","char"],"keyup":[0,"key"]},
1063			'specialChars':
1064			 {"keydown":[0,"char"],"keyup":[0,"char"]},
1065			'navigation':
1066			 {"keydown":[0,"key"],"keyup":[0,"key"]},
1067			'special':
1068			 {"keydown":[0,"key"],"keyup":[0,"key"]},
1069			'tab':
1070			 {"keydown":[0,"char"],"keyup":[0,"char"]},
1071			'pause-break':
1072			 {"keydown":[0,"key"],"keyup":[0,"key"]},
1073			'caps':
1074			 {"keydown":[0,"key"],"keyup":[0,"key"]},
1075			'escape':
1076			 {"keydown":[0,"key"],"keyup":[0,"key"]},
1077			'num-lock':
1078			 {"keydown":[0,"key"],"keyup":[0,"key"]},
1079			'scroll-lock':
1080			 {"keydown":[0,"key"],"keyup":[0,"key"]},
1081			'print':
1082			 {"keyup":[0,"key"]},
1083			'function':
1084			 {"keydown":[0,"key"],"keyup":[0,"key"]},
1085			'\r':
1086			 {"keydown":[0,"key"],"keypress":["char","key"],"keyup":[0,"key"]}
1087		},
1088		gecko : {
1089			'prevent':
1090			 {"keyup":[],"keydown":["char"],"keypress":["char"]},
1091			'character':
1092			 {"keydown":[0,"key"],"keypress":["char",0],"keyup":[0,"key"]},
1093			'specialChars':
1094			 {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]},
1095			'navigation':
1096			 {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]},
1097			'special':
1098			 {"keydown":[0,"key"],"keyup":[0,"key"]},
1099			'\t':
1100			 {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]},
1101			'pause-break':
1102			 {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]},
1103			'caps':
1104			 {"keydown":[0,"key"],"keyup":[0,"key"]},
1105			'escape':
1106			 {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]},
1107			'num-lock':
1108			 {"keydown":[0,"key"],"keyup":[0,"key"]},
1109			'scroll-lock':
1110			 {"keydown":[0,"key"],"keyup":[0,"key"]},
1111			'print':
1112			 {"keyup":[0,"key"]},
1113			'function':
1114			 {"keydown":[0,"key"],"keyup":[0,"key"]},
1115			'\r':
1116			 {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}
1117		},
1118		msie : {
1119			'prevent':{"keyup":[],"keydown":["char","keypress"],"keypress":["char"]},
1120			'character':{"keydown":[null,"key"],"keypress":[null,"char"],"keyup":[null,"key"]},
1121			'specialChars':{"keydown":[null,"char"],"keyup":[null,"char"]},
1122			'navigation':{"keydown":[null,"key"],"keyup":[null,"key"]},
1123			'special':{"keydown":[null,"key"],"keyup":[null,"key"]},
1124			'tab':{"keydown":[null,"char"],"keyup":[null,"char"]},
1125			'pause-break':{"keydown":[null,"key"],"keyup":[null,"key"]},
1126			'caps':{"keydown":[null,"key"],"keyup":[null,"key"]},
1127			'escape':{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]},
1128			'num-lock':{"keydown":[null,"key"],"keyup":[null,"key"]},
1129			'scroll-lock':{"keydown":[null,"key"],"keyup":[null,"key"]},
1130			'print':{"keyup":[null,"key"]},
1131			'function':{"keydown":[null,"key"],"keyup":[null,"key"]},
1132			'\r':{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}	
1133		},
1134		opera : {
1135			'prevent':
1136			 {"keyup":[],"keydown":[],"keypress":["char"]},
1137			'character':
1138			 {"keydown":[null,"key"],"keypress":[null,"char"],"keyup":[null,"key"]},
1139			'specialChars':
1140			 {"keydown":[null,"char"],"keypress":[null,"char"],"keyup":[null,"char"]},
1141			'navigation':
1142			 {"keydown":[null,"key"],"keypress":[null,"key"]},
1143			'special':
1144			 {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]},
1145			'tab':
1146			 {"keydown":[null,"char"],"keypress":[null,"char"],"keyup":[null,"char"]},
1147			'pause-break':
1148			 {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]},
1149			'caps':
1150			 {"keydown":[null,"key"],"keyup":[null,"key"]},
1151			'escape':
1152			 {"keydown":[null,"key"],"keypress":[null,"key"]},
1153			'num-lock':
1154			 {"keyup":[null,"key"],"keydown":[null,"key"],"keypress":[null,"key"]},
1155			'scroll-lock':
1156			 {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]},
1157			'print':
1158			 {},
1159			'function':
1160			 {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]},
1161			'\r':
1162			 {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}	
1163		}
1164	};
1165	
1166	Syn.mouse.browsers = {
1167		webkit : {"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}},
1168		opera: {},
1169		msie: {"mouseup":{"button":2},"contextmenu":{"button":0}},
1170		chrome : {"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}},
1171		gecko: {"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}}
1172	}
1173	
1174	//set browser
1175	Syn.key.browser = 
1176	(function(){
1177		if(Syn.key.browsers[window.navigator.userAgent]){
1178			return Syn.key.browsers[window.navigator.userAgent];
1179		}
1180		for(var browser in Syn.browser){
1181			if(Syn.browser[browser] && Syn.key.browsers[browser]){
1182				return Syn.key.browsers[browser]
1183			}
1184		}
1185		return Syn.key.browsers.gecko;
1186	})();
1187	
1188	Syn.mouse.browser = 
1189	(function(){
1190		if(Syn.mouse.browsers[window.navigator.userAgent]){
1191			return Syn.mouse.browsers[window.navigator.userAgent];
1192		}
1193		for(var browser in Syn.browser){
1194			if(Syn.browser[browser] && Syn.mouse.browsers[browser]){
1195				return Syn.mouse.browsers[browser]
1196			}
1197		}
1198		return Syn.mouse.browsers.gecko;
1199	})();
1200	
1201
1202})();
1203
1204// funcunit/synthetic/key.js
1205
1206(function($){
1207
1208
1209var h = Syn.helpers,
1210	S = Syn,
1211
1212// gets the selection of an input or textarea
1213getSelection = function(el){
1214	// use selectionStart if we can
1215	if (el.selectionStart !== undefined) {
1216		// this is for opera, so we don't have to focus to type how we think we would
1217		if(document.activeElement 
1218		 	&& document.activeElement != el 
1219			&& el.selectionStart == el.selectionEnd 
1220			&& el.selectionStart == 0){
1221			return {start: el.value.length, end: el.value.length};
1222		}
1223		return  {start: el.selectionStart, end: el.selectionEnd}
1224	}else{
1225		//check if we aren't focused
1226		//if(document.activeElement && document.activeElement != el){
1227			
1228			
1229		//}
1230		try {
1231			//try 2 different methods that work differently (IE breaks depending on type)
1232			if (el.nodeName.toLowerCase() == 'input') {
1233				var real = h.getWindow(el).document.selection.createRange(), r = el.createTextRange();
1234				r.setEndPoint("EndToStart", real);
1235				
1236				var start = r.text.length
1237				return {
1238					start: start,
1239					end: start + real.text.length
1240				}
1241			}
1242			else {
1243				var real = h.getWindow(el).document.selection.createRange(), r = real.duplicate(), r2 = real.duplicate(), r3 = real.duplicate();
1244				r2.collapse();
1245				r3.collapse(false);
1246				r2.moveStart('character', -1)
1247				r3.moveStart('character', -1)
1248				//select all of our element
1249				r.moveToElementText(el)
1250				//now move our endpoint to the end of our real range
1251				r.setEndPoint('EndToEnd', real);
1252				var start = r.text.length - real.text.length, end = r.text.length;
1253				if (start != 0 && r2.text == "") {
1254					start += 2;
1255				}
1256				if (end != 0 && r3.text == "") {
1257					end += 2;
1258				}
1259				//if we aren't at the start, but previous is empty, we are at start of newline
1260				return {
1261					start: start,
1262					end: end
1263				}
1264			}
1265		}catch(e){
1266			return {start: el.value.length, end: el.value.length};
1267		}
1268	} 
1269},
1270// gets all focusable elements
1271getFocusable = function(el){
1272	var document = h.getWindow(el).document,
1273		res = [];
1274
1275	var els = document.getElementsByTagName('*'),
1276		len = els.length;
1277		
1278	for(var i=0;  i< len; i++){
1279		Syn.isFocusable(els[i]) && els[i] != document.documentElement && res.push(els[i])
1280	}
1281	return res;
1282	
1283	
1284};
1285
1286/**
1287 * @add Syn static
1288 */
1289h.extend(Syn,{
1290	/**
1291	 * @attribute
1292	 * A list of the keys and their keycodes codes you can type.
1293	 * You can add type keys with
1294	 * @codestart
1295	 * Syn('key','delete','title');
1296	 * 
1297	 * //or 
1298	 * 
1299	 * Syn('type','One Two Three[left][left][delete]','title')
1300	 * @codeend
1301	 * 
1302	 * The following are a list of keys you can type:
1303	 * @codestart text
1304	 * \b        - backspace
1305	 * \t        - tab
1306	 * \r        - enter
1307	 * ' '       - space
1308	 * a-Z 0-9   - normal characters
1309	 * /!@#$*,.? - All other typeable characters
1310	 * page-up   - scrolls up
1311	 * page-down - scrolls down
1312	 * end       - scrolls to bottom
1313	 * home      - scrolls to top
1314	 * insert    - changes how keys are entered
1315	 * delete    - deletes the next character
1316	 * left      - moves cursor left
1317	 * right     - moves cursor right
1318	 * up        - moves the cursor up
1319	 * down      - moves the cursor down
1320	 * f1-12     - function buttons
1321	 * shift, ctrl, alt - special keys
1322	 * pause-break      - the pause button
1323	 * scroll-lock      - locks scrolling
1324	 * caps      - makes caps
1325	 * escape    - escape button
1326	 * num-lock  - allows numbers on keypad
1327	 * print     - screen capture
1328	 * @codeend
1329	 */
1330	keycodes: {
1331		//backspace
1332		'\b':'8',
1333		
1334		//tab
1335		'\t':'9',
1336		
1337		//enter
1338		'\r':'13',
1339		
1340		//special
1341		'shift':'16','ctrl':'17','alt':'18',
1342		
1343		//weird
1344		'pause-break':'19',
1345		'caps':'20',
1346		'escape':'27',
1347		'num-lock':'144',
1348		'scroll-lock':'145',
1349		'print' : '44',
1350		
1351		//navigation
1352		'page-up':'33','page-down':'34','end':'35','home':'36',
1353		'left':'37','up':'38','right':'39','down':'40','insert':'45','delete':'46',
1354		
1355		//normal characters
1356		' ':'32',
1357		'0':'48','1':'49','2':'50','3':'51','4':'52','5':'53','6':'54','7':'55','8':'56','9':'57',
1358		'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',
1359		'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',
1360		//normal-characters, numpad
1361		'num0':'96','num1':'97','num2':'98','num3':'99','num4':'100','num5':'101','num6':'102','num7':'103','num8':'104','num9':'105',
1362		'*':'106','+':'107','-':'109','.':'110',
1363		//normal-characters, others
1364		'/':'111',
1365		';':'186',
1366		'=':'187',
1367		',':'188',
1368		'-':'189',
1369		'.':'190',
1370		'/':'191',
1371		'`':'192',
1372		'[':'219',
1373		'\\':'220',
1374		']':'221',
1375		"'":'222',
1376		
1377		//ignore these, you shouldn't use them
1378		'left window key':'91','right window key':'92','select key':'93',
1379		
1380		
1381		'f1':'112','f2':'113','f3':'114','f4':'115','f5':'116','f6':'117',
1382		'f7':'118','f8':'119','f9':'120','f10':'121','f11':'122','f12':'123'
1383	},
1384	
1385	// what we can type in
1386	typeable : /input|textarea/i,
1387	
1388	// selects text on an element
1389	selectText: function(el, start, end){
1390		if(el.setSelectionRange){
1391			if(!end){
1392                el.focus();
1393                el.setSelectionRange(start, start);
1394			} else {
1395				el.selectionStart = start;
1396				el.selectionEnd = end;
1397			}
1398		}else if (el.createTextRange) {
1399			//el.focus();
1400			var r = el.createTextRange();
1401			r.moveStart('character', start);
1402			end = end || start;
1403			r.moveEnd('character', end - el.value.length);
1404			
1405			r.select();
1406		} 
1407	},
1408	getText: function(el){
1409		//first check if the el has anything selected ..
1410		if(Syn.typeable.test(el.nodeName)){
1411			var sel = getSelection(el);
1412			return el.value.substring(sel.start, sel.end)
1413		}
1414		//otherwise get from page
1415		var win = Syn.helpers.getWindow(el);
1416		if (win.getSelection) {
1417			return win.getSelection().toString();
1418		}
1419		else  if (win.document.getSelection) {
1420			return win.document.getSelection().toString()
1421		}
1422		else {
1423			return win.document.selection.createRange().text;
1424		}
1425	},
1426	getSelection : getSelection
1427});
1428
1429h.extend(Syn.key,{
1430	// retrieves a description of what events for this character should look like
1431	data : function(key){
1432		//check if it is described directly
1433		if(S.key.browser[key]){
1434			return S.key.browser[key];
1435		}
1436		for(var kind in S.key.kinds){
1437			if(h.inArray(key, S.key.kinds[kind] ) > -1){
1438				return S.key.browser[kind]
1439			}
1440		}
1441		return S.key.browser.character
1442	},
1443	
1444	//returns the special key if special
1445	isSpecial : function(keyCode){
1446		var specials = S.key.kinds.special;
1447		for(var i=0; i < specials.length; i++){
1448			if(Syn.keycodes[ specials[i] ] == keyCode){
1449				return specials[i];
1450			}
1451		}
1452	},
1453	/**
1454	 * @hide
1455	 * gets the options for a key and event type ...
1456	 * @param {Object} key
1457	 * @param {Object} event
1458	 */
1459	options : function(key, event){
1460		var keyData = Syn.key.data(key);
1461		
1462		if(!keyData[event]){
1463			//we shouldn't be creating this event
1464			return null;
1465		}
1466			
1467		var	charCode = keyData[event][0],
1468			keyCode = keyData[event][1],
1469			result = {};
1470			
1471		if(keyCode == 'key'){
1472			result.keyCode = Syn.keycodes[key]
1473		} else if (keyCode == 'char'){
1474			result.keyCode = key.charCodeAt(0)
1475		}else{
1476			result.keyCode = keyCode;
1477		}
1478		
1479		if(charCode == 'char'){
1480			result.charCode = key.charCodeAt(0)
1481		}else if(charCode !== null){
1482			result.charCode = charCode;
1483		}
1484		
1485		
1486		return result
1487	},
1488	//types of event keys
1489	kinds : {
1490		special : ["shift",'ctrl','alt','caps'],
1491		specialChars : ["\b"],
1492		navigation: ["page-up",'page-down','end','home','left','up','right','down','insert','delete'],
1493		'function' : ['f1','f2','f3','f4','f5','f6','f7','f8','f9','f10','f11','f12']
1494	},
1495	//returns the default function
1496	getDefault : function(key){
1497		//check if it is described directly
1498		if(Syn.key.defaults[key]){
1499			return Syn.key.defaults[key];
1500		}
1501		for(var kind in Syn.key.kinds){
1502			if(h.inArray(key, Syn.key.kinds[kind])> -1 && Syn.key.defaults[kind]  ){
1503				return Syn.key.defaults[kind];
1504			}
1505		}
1506		return Syn.key.defaults.character
1507	},
1508	// default behavior when typing
1509	defaults : 	{
1510		'character' : function(options, scope, key, force){
1511			if(/num\d+/.test(key)){
1512				key = key.match(/\d+/)[0]
1513			}
1514			
1515			if(force || (!S.support.keyCharacters && Syn.typeable.test(this.nodeName))){
1516				var current = this.value,
1517					sel = getSelection(this),
1518					before = current.substr(0,sel.start),
1519					after = current.substr(sel.end),
1520					character = key;
1521				
1522				this.value = before+character+after;
1523				//handle IE inserting \r\n
1524				var charLength = character == "\n" && S.support.textareaCarriage ? 2 : character.length;
1525				Syn.selectText(this, before.length + charLength)
1526			}		
1527		},
1528		'c' : function(options, scope, key){
1529			if(Syn.key.ctrlKey){
1530				Syn.key.clipboard = Syn.getText(this)
1531			}else{
1532				Syn.key.defaults.character.call(this, options,scope, key);
1533			}
1534		},
1535		'v' : function(options, scope, key){
1536			if(Syn.key.ctrlKey){
1537				Syn.key.defaults.character.call(this, options,scope, Syn.key.clipboard, true);
1538			}else{
1539				Syn.key.defaults.character.call(this, options,scope, key);
1540			}
1541		},
1542		'a' : function(options, scope, key){
1543			if(Syn.key.ctrlKey){
1544				Syn.selectText(this, 0, this.value.length)
1545			}else{
1546				Syn.key.defaults.character.call(this, options,scope, key);
1547			}
1548		},
1549		'home' : function(){
1550			Syn.onParents(this, function(el){
1551				if(el.scrollHeight != el.clientHeight){
1552					el.scrollTop = 0;
1553					return false;
1554				}
1555			})
1556		},
1557		'end' : function(){
1558			Syn.onParents(this, function(el){
1559				if(el.scrollHeight != el.clientHeight){
1560					el.scrollTop = el.scrollHeight;
1561					return false;
1562				}
1563			})
1564		},
1565		'page-down' : function(){
1566			//find the first parent we can scroll
1567			Syn.onParents(this, function(el){
1568				if(el.scrollHeight != el.clientHeight){
1569					var ch = el.clientHeight
1570					el.scrollTop += ch;
1571					return false;
1572				}
1573			})
1574		},
1575		'page-up' : function(){
1576			Syn.onParents(this, function(el){
1577				if(el.scrollHeight != el.clientHeight){
1578					var ch = el.clientHeight
1579					el.scrollTop -= ch;
1580					return false;
1581				}
1582			})
1583		},
1584		'\b' : function(){
1585			//this assumes we are deleting from the end
1586			if(!S.support.backspaceWorks && Syn.typeable.test(this.nodeName)){
1587				var current = this.value,
1588					sel = getSelection(this),
1589					before = current.substr(0,sel.start),
1590					after = current.substr(sel.end);
1591				if(sel.start == sel.end && sel.start > 0){
1592					//remove a character
1593					this.value = before.substring(0, before.length - 1)+after
1594					Syn.selectText(this, sel.start-1)
1595				}else{
1596					this.value = before+after;
1597					Syn.selectText(this, sel.start)
1598				}
1599				
1600				//set back the selection
1601			}	
1602		},
1603		'delete' : function(){
1604			if(!S.support.backspaceWorks && Syn.typeable.test(this.nodeName)){
1605				var current = this.value,
1606					sel = getSelection(this),
1607					before = current.substr(0,sel.start),
1608					after = current.substr(sel.end);
1609				
1610				if(sel.start == sel.end && sel.start < this.value.length - 1){
1611					//remove a character
1612					this.value = before+after.substring(1)
1613				}else{
1614					this.value = before+after;
1615				}
1616			}		
1617		},
1618		'\r' : function(options, scope){
1619			
1620			var nodeName = this.nodeName.toLowerCase()
1621			// submit a form
1622			if(!S.support.keypressSubmits && nodeName == 'input'){
1623				var form = Syn.closest(this, "form");
1624				if(form){
1625					Syn.trigger("submit", {}, form);
1626				}
1627					
1628			}
1629			//newline in textarea
1630			if(!S.support.keyCharacters && nodeName == 'textarea'){
1631				Syn.key.defaults.character.call(this, options, scope, "\n")
1632			}
1633			// 'click' hyperlinks
1634			if(!S.support.keypressOnAnchorClicks && nodeName == 'a'){
1635				Syn.trigger("click", {}, this);
1636			}
1637		},
1638		// 
1639		// Gets all focusable elements.  If the element (this)
1640		// doesn't have a tabindex, finds the next element after.
1641		// If the element (this) has a tabindex finds the element 
1642		// with the next higher tabindex OR the element with the same
1643		// tabindex after it in the document.
1644		// @return the next element
1645		// 
1646		'\t' : function(options, scope){
1647				// focusable elements
1648			var focusEls = getFocusable(this),
1649				// the current element's tabindex
1650				tabIndex = Syn.tabIndex(this),
1651				// will be set to our guess for the next element
1652				current = null,
1653				// the next index we care about
1654				currentIndex = 1000000000,
1655				// set to true once we found 'this' element
1656				found = false,
1657				i = 0,
1658				el, 
1659				//the tabindex of the tabable element we are looking at
1660				elIndex,
1661				firstNotIndexed;
1662				
1663			for(; i< focusEls.length; i++){
1664				el = focusEls[i];
1665				elIndex = Syn.tabIndex(el) || 0;
1666				if(!firstNotIndexed && elIndex === 0){
1667					firstNotIndexed = el;
1668				}
1669				
1670				if(tabIndex 
1671					&& (found ? elIndex >= tabIndex : elIndex > tabIndex )  
1672					&& elIndex < currentIndex){
1673						currentIndex = elIndex;
1674						current = el;
1675				}
1676				
1677				if(!tabIndex && found && !elIndex){
1678					current = el;
1679					break;
1680				}
1681				
1682				if(this === el){
1683					found= true;
1684				}
1685			}
1686			
1687			//restart if we didn't find anything
1688			if(!current){

Large files files are truncated, but you can click here to view the full file