/wwwroot/vendor/syn.js
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){ 1689…
Large files files are truncated, but you can click here to view the full file