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