funcunit
Version:
<!-- @hide title
1,681 lines (1,556 loc) • 144 kB
JavaScript
/*
* FuncUnit - 2.0.3
* http://funcunit.com
* Copyright (c) 2013 Bitovi
* Tue, 08 Oct 2013 01:26:18 GMT
* Licensed MIT */
/*
* Syn - 3.3.1
*
* Copyright (c) 2013 Bitovi
* Tue, 08 Oct 2013 00:20:41 GMT
* Licensed MIT */
!function(window) {
// ## synthetic.js
var __m2 = (function(){
//allow for configuration of Syn
var opts = window.Syn ? window.Syn : {};
var extend = function( d, s ) {
var p;
for (p in s) {
d[p] = s[p];
}
return d;
},
// only uses browser detection for key events
browser = {
msie: !! (window.attachEvent && !window.opera),
opera: !! window.opera,
webkit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
safari: navigator.userAgent.indexOf('AppleWebKit/') > -1 && navigator.userAgent.indexOf('Chrome/') === -1,
gecko: navigator.userAgent.indexOf('Gecko') > -1,
mobilesafari: !! navigator.userAgent.match(/Apple.*Mobile.*Safari/),
rhino: navigator.userAgent.match(/Rhino/) && true
},
createEventObject = function( type, options, element ) {
var event = element.ownerDocument.createEventObject();
return extend(event, options);
},
data = {},
id = 1,
expando = "_synthetic" + new Date().getTime(),
bind, unbind, key = /keypress|keyup|keydown/,
page = /load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll/,
//this is maintained so we can click on html and blur the active element
activeElement,
/**
* @class Syn
* @download funcunit/dist/syn.js
* @test funcunit/synthetic/qunit.html
* Syn is used to simulate user actions. It creates synthetic events and
* performs their default behaviors.
*
* <h2>Basic Use</h2>
* The following clicks an input element with <code>id='description'</code>
* and then types <code>'Hello World'</code>.
*
@codestart
Syn.click({},'description')
.type("Hello World")
@codeend
* <h2>User Actions and Events</h2>
* <p>Syn is typically used to simulate user actions as opposed to triggering events. Typing characters
* is an example of a user action. The keypress that represents an <code>'a'</code>
* character being typed is an example of an event.
* </p>
* <p>
* While triggering events is supported, it's much more useful to simulate actual user behavior. The
* following actions are supported by Syn:
* </p>
* <ul>
* <li><code>[Syn.prototype.click click]</code> - a mousedown, focus, mouseup, and click.</li>
* <li><code>[Syn.prototype.dblclick dblclick]</code> - two <code>click!</code> events followed by a <code>dblclick</code>.</li>
* <li><code>[Syn.prototype.key key]</code> - types a single character (keydown, keypress, keyup).</li>
* <li><code>[Syn.prototype.type type]</code> - types multiple characters into an element.</li>
* <li><code>[Syn.prototype.move move]</code> - moves the mouse from one position to another (triggering mouseover / mouseouts).</li>
* <li><code>[Syn.prototype.drag drag]</code> - a mousedown, followed by mousemoves, and a mouseup.</li>
* </ul>
* All actions run asynchronously.
* Click on the links above for more
* information on how to use the specific action.
* <h2>Asynchronous Callbacks</h2>
* Actions don't complete immediately. This is almost
* entirely because <code>focus()</code>
* doesn't run immediately in IE.
* If you provide a callback function to Syn, it will
* be called after the action is completed.
* <br/>The following checks that "Hello World" was entered correctly:
@codestart
Syn.click({},'description')
.type("Hello World", function(){
ok("Hello World" == document.getElementById('description').value)
})
@codeend
<h2>Asynchronous Chaining</h2>
<p>You might have noticed the [Syn.prototype.then then] method. It provides chaining
so you can do a sequence of events with a single (final) callback.
</p><p>
If an element isn't provided to then, it uses the previous Syn's element.
</p>
The following does a lot of stuff before checking the result:
@codestart
Syn.type('ice water','title')
.type('ice and water','description')
.click({},'create')
.drag({to: 'favorites'},'newRecipe',
function(){
ok($('#newRecipe').parents('#favorites').length);
})
@codeend
<h2>jQuery Helper</h2>
If jQuery is present, Syn adds a triggerSyn helper you can use like:
@codestart
$("#description").triggerSyn("type","Hello World");
@codeend
* <h2>Key Event Recording</h2>
* <p>Every browser has very different rules for dispatching key events.
* As there is no way to feature detect how a browser handles key events,
* synthetic uses a description of how the browser behaves generated
* by a recording application. </p>
* <p>
* If you want to support a browser not currently supported, you can
* record that browser's key event description and add it to
* <code>Syn.key.browsers</code> by it's navigator agent.
* </p>
@codestart
Syn.key.browsers["Envjs\ Resig/20070309 PilotFish/1.2.0.10\1.6"] = {
'prevent':
{"keyup":[],"keydown":["char","keypress"],"keypress":["char"]},
'character':
{ ... }
}
@codeend
* <h2>Limitations</h2>
* Syn fully supports IE 6+, FF 3+, Chrome, Safari, Opera 10+.
* With FF 1+, drag / move events are only partially supported. They will
* not trigger mouseover / mouseout events.<br/>
* Safari crashes when a mousedown is triggered on a select. Syn will not
* create this event.
* <h2>Contributing to Syn</h2>
* Have we missed something? We happily accept patches. The following are
* important objects and properties of Syn:
* <ul>
* <li><code>Syn.create</code> - contains methods to setup, convert options, and create an event of a specific type.</li>
* <li><code>Syn.defaults</code> - default behavior by event type (except for keys).</li>
* <li><code>Syn.key.defaults</code> - default behavior by key.</li>
* <li><code>Syn.keycodes</code> - supported keys you can type.</li>
* </ul>
* <h2>Roll Your Own Functional Test Framework</h2>
* <p>Syn is really the foundation of JavaScriptMVC's functional testing framework - [FuncUnit].
* But, we've purposely made Syn work without any dependencies in the hopes that other frameworks or
* testing solutions can use it as well.
* </p>
* @constructor
* Creates a synthetic event on the element.
* @param {Object} type
* @param {Object} options
* @param {Object} element
* @param {Object} callback
* @return Syn
*/
Syn = function( type, options, element, callback ) {
return (new Syn.init(type, options, element, callback));
};
Syn.config = opts;
bind = function( el, ev, f ) {
return el.addEventListener ? el.addEventListener(ev, f, false) : el.attachEvent("on" + ev, f);
};
unbind = function( el, ev, f ) {
return el.addEventListener ? el.removeEventListener(ev, f, false) : el.detachEvent("on" + ev, f);
};
/**
* @Static
*/
extend(Syn, {
/**
* Creates a new synthetic event instance
* @hide
* @param {Object} type
* @param {Object} options
* @param {Object} element
* @param {Object} callback
*/
init: function( type, options, element, callback ) {
var args = Syn.args(options, element, callback),
self = this;
this.queue = [];
this.element = args.element;
//run event
if ( typeof this[type] === "function" ) {
this[type](args.options, args.element, function( defaults, el ) {
args.callback && args.callback.apply(self, arguments);
self.done.apply(self, arguments);
});
} else {
this.result = Syn.trigger(type, args.options, args.element);
args.callback && args.callback.call(this, args.element, this.result);
}
},
jquery: function( el, fast ) {
if ( window.FuncUnit && window.FuncUnit.jQuery ) {
return window.FuncUnit.jQuery;
}
if ( el ) {
return Syn.helpers.getWindow(el).jQuery || window.jQuery;
}
else {
return window.jQuery;
}
},
/**
* Returns an object with the args for a Syn.
* @hide
* @return {Object}
*/
args: function() {
var res = {},
i = 0;
for ( ; i < arguments.length; i++ ) {
if ( typeof arguments[i] === 'function' ) {
res.callback = arguments[i];
} else if ( arguments[i] && arguments[i].jquery ) {
res.element = arguments[i][0];
} else if ( arguments[i] && arguments[i].nodeName ) {
res.element = arguments[i];
} else if ( res.options && typeof arguments[i] === 'string' ) { //we can get by id
res.element = document.getElementById(arguments[i]);
}
else if ( arguments[i] ) {
res.options = arguments[i];
}
}
return res;
},
click: function( options, element, callback ) {
Syn('click!', options, element, callback);
},
/**
* @attribute defaults
* Default actions for events. Each default function is called with this as its
* element. It should return true if a timeout
* should happen after it. If it returns an element, a timeout will happen
* and the next event will happen on that element.
*/
defaults: {
focus: function() {
if (!Syn.support.focusChanges ) {
var element = this,
nodeName = element.nodeName.toLowerCase();
Syn.data(element, "syntheticvalue", element.value);
//TODO, this should be textarea too
//and this might be for only text style inputs ... hmmmmm ....
if ( nodeName === "input" || nodeName === "textarea" ) {
bind(element, "blur", function() {
if ( Syn.data(element, "syntheticvalue") != element.value ) {
Syn.trigger("change", {}, element);
}
unbind(element, "blur", arguments.callee);
});
}
}
},
submit: function() {
Syn.onParents(this, function( el ) {
if ( el.nodeName.toLowerCase() === 'form' ) {
el.submit();
return false;
}
});
}
},
changeOnBlur: function( element, prop, value ) {
bind(element, "blur", function() {
if ( value !== element[prop] ) {
Syn.trigger("change", {}, element);
}
unbind(element, "blur", arguments.callee);
});
},
/**
* Returns the closest element of a particular type.
* @hide
* @param {Object} el
* @param {Object} type
*/
closest: function( el, type ) {
while ( el && el.nodeName.toLowerCase() !== type.toLowerCase() ) {
el = el.parentNode;
}
return el;
},
/**
* adds jQuery like data (adds an expando) and data exists FOREVER :)
* @hide
* @param {Object} el
* @param {Object} key
* @param {Object} value
*/
data: function( el, key, value ) {
var d;
if (!el[expando] ) {
el[expando] = id++;
}
if (!data[el[expando]] ) {
data[el[expando]] = {};
}
d = data[el[expando]];
if ( value ) {
data[el[expando]][key] = value;
} else {
return data[el[expando]][key];
}
},
/**
* Calls a function on the element and all parents of the element until the function returns
* false.
* @hide
* @param {Object} el
* @param {Object} func
*/
onParents: function( el, func ) {
var res;
while ( el && res !== false ) {
res = func(el);
el = el.parentNode;
}
return el;
},
//regex to match focusable elements
focusable: /^(a|area|frame|iframe|label|input|select|textarea|button|html|object)$/i,
/**
* Returns if an element is focusable
* @hide
* @param {Object} elem
*/
isFocusable: function( elem ) {
var attributeNode;
// IE8 Standards doesn't like this on some elements
if(elem.getAttributeNode){
attributeNode = elem.getAttributeNode("tabIndex")
}
return this.focusable.test(elem.nodeName) ||
(attributeNode && attributeNode.specified) &&
Syn.isVisible(elem);
},
/**
* Returns if an element is visible or not
* @hide
* @param {Object} elem
*/
isVisible: function( elem ) {
return (elem.offsetWidth && elem.offsetHeight) || (elem.clientWidth && elem.clientHeight);
},
/**
* Gets the tabIndex as a number or null
* @hide
* @param {Object} elem
*/
tabIndex: function( elem ) {
var attributeNode = elem.getAttributeNode("tabIndex");
return attributeNode && attributeNode.specified && (parseInt(elem.getAttribute('tabIndex')) || 0);
},
bind: bind,
unbind: unbind,
browser: browser,
//some generic helpers
helpers: {
createEventObject: createEventObject,
createBasicStandardEvent: function( type, defaults, doc ) {
var event;
try {
event = doc.createEvent("Events");
} catch (e2) {
event = doc.createEvent("UIEvents");
} finally {
event.initEvent(type, true, true);
extend(event, defaults);
}
return event;
},
inArray: function( item, array ) {
var i =0;
for ( ; i < array.length; i++ ) {
if ( array[i] === item ) {
return i;
}
}
return -1;
},
getWindow: function( element ) {
if(element.ownerDocument){
return element.ownerDocument.defaultView || element.ownerDocument.parentWindow;
}
},
extend: extend,
scrollOffset: function( win , set) {
var doc = win.document.documentElement,
body = win.document.body;
if(set){
window.scrollTo(set.left, set.top);
} else {
return {
left: (doc && doc.scrollLeft || body && body.scrollLeft || 0) + (doc.clientLeft || 0),
top: (doc && doc.scrollTop || body && body.scrollTop || 0) + (doc.clientTop || 0)
};
}
},
scrollDimensions: function(win){
var doc = win.document.documentElement,
body = win.document.body,
docWidth = doc.clientWidth,
docHeight = doc.clientHeight,
compat = win.document.compatMode === "CSS1Compat";
return {
height: compat && docHeight ||
body.clientHeight || docHeight,
width: compat && docWidth ||
body.clientWidth || docWidth
};
},
addOffset: function( options, el ) {
var jq = Syn.jquery(el),
off;
if ( typeof options === 'object' && options.clientX === undefined && options.clientY === undefined && options.pageX === undefined && options.pageY === undefined && jq ) {
el = jq(el);
off = el.offset();
options.pageX = off.left + el.width() / 2;
options.pageY = off.top + el.height() / 2;
}
}
},
// place for key data
key: {
ctrlKey: null,
altKey: null,
shiftKey: null,
metaKey: null
},
//triggers an event on an element, returns true if default events should be run
/**
* Dispatches an event and returns true if default events should be run.
* @hide
* @param {Object} event
* @param {Object} element
* @param {Object} type
* @param {Object} autoPrevent
*/
dispatch: function( event, element, type, autoPrevent ) {
// dispatchEvent doesn't always work in IE (mostly in a popup)
if ( element.dispatchEvent && event ) {
var preventDefault = event.preventDefault,
prevents = autoPrevent ? -1 : 0;
//automatically prevents the default behavior for this event
//this is to protect agianst nasty browser freezing bug in safari
if ( autoPrevent ) {
bind(element, type, function( ev ) {
ev.preventDefault();
unbind(this, type, arguments.callee);
});
}
event.preventDefault = function() {
prevents++;
if (++prevents > 0 ) {
preventDefault.apply(this, []);
}
};
element.dispatchEvent(event);
return prevents <= 0;
} else {
try {
window.event = event;
} catch (e) {}
//source element makes sure element is still in the document
return element.sourceIndex <= 0 || (element.fireEvent && element.fireEvent('on' + type, event));
}
},
/**
* @attribute
* @hide
* An object of eventType -> function that create that event.
*/
create: {
//-------- PAGE EVENTS ---------------------
page: {
event: function( type, options, element ) {
var doc = Syn.helpers.getWindow(element).document || document,
event;
if ( doc.createEvent ) {
event = doc.createEvent("Events");
event.initEvent(type, true, true);
return event;
}
else {
try {
event = createEventObject(type, options, element);
}
catch (e) {}
return event;
}
}
},
// unique events
focus: {
event: function( type, options, element ) {
Syn.onParents(element, function( el ) {
if ( Syn.isFocusable(el) ) {
if ( el.nodeName.toLowerCase() !== 'html' ) {
el.focus();
activeElement = el;
}
else if ( activeElement ) {
// TODO: The HTML element isn't focasable in IE, but it is
// in FF. We should detect this and do a true focus instead
// of just a blur
var doc = Syn.helpers.getWindow(element).document;
if ( doc !== window.document ) {
return false;
} else if ( doc.activeElement ) {
doc.activeElement.blur();
activeElement = null;
} else {
activeElement.blur();
activeElement = null;
}
}
return false;
}
});
return true;
}
}
},
/**
* @attribute support
*
* Feature detected properties of a browser's event system.
* Support has the following properties:
*
* - `backspaceWorks` - typing a backspace removes a character
* - `clickChanges` - clicking on an option element creates a change event.
* - `clickSubmits` - clicking on a form button submits the form.
* - `focusChanges` - focus/blur creates a change event.
* - `keypressOnAnchorClicks` - Keying enter on an anchor triggers a click.
* - `keypressSubmits` - enter key submits
* - `keyCharacters` - typing a character shows up
* - `keysOnNotFocused` - enters keys when not focused.
* - `linkHrefJS` - An achor's href JavaScript is run.
* - `mouseDownUpClicks` - A mousedown followed by mouseup creates a click event.
* - `mouseupSubmits` - a mouseup on a form button submits the form.
* - `radioClickChanges` - clicking a radio button changes the radio.
* - `tabKeyTabs` - A tab key changes tabs.
* - `textareaCarriage` - a new line in a textarea creates a carriage return.
*
*
*/
support: {
clickChanges: false,
clickSubmits: false,
keypressSubmits: false,
mouseupSubmits: false,
radioClickChanges: false,
focusChanges: false,
linkHrefJS: false,
keyCharacters: false,
backspaceWorks: false,
mouseDownUpClicks: false,
tabKeyTabs: false,
keypressOnAnchorClicks: false,
optionClickBubbles: false,
ready: 0
},
/**
* Creates a synthetic event and dispatches it on the element.
* This will run any default actions for the element.
* Typically you want to use Syn, but if you want the return value, use this.
* @param {String} type
* @param {Object} options
* @param {HTMLElement} element
* @return {Boolean} true if default events were run, false if otherwise.
*/
trigger: function( type, options, element ) {
options || (options = {});
var create = Syn.create,
setup = create[type] && create[type].setup,
kind = key.test(type) ? 'key' : (page.test(type) ? "page" : "mouse"),
createType = create[type] || {},
createKind = create[kind],
event, ret, autoPrevent, dispatchEl = element;
//any setup code?
Syn.support.ready === 2 && setup && setup(type, options, element);
autoPrevent = options._autoPrevent;
//get kind
delete options._autoPrevent;
if ( createType.event ) {
ret = createType.event(type, options, element);
} else {
//convert options
options = createKind.options ? createKind.options(type, options, element) : options;
if (!Syn.support.changeBubbles && /option/i.test(element.nodeName) ) {
dispatchEl = element.parentNode; //jQuery expects clicks on select
}
//create the event
event = createKind.event(type, options, dispatchEl);
//send the event
ret = Syn.dispatch(event, dispatchEl, type, autoPrevent);
}
ret && Syn.support.ready === 2 && Syn.defaults[type] && Syn.defaults[type].call(element, options, autoPrevent);
return ret;
},
eventSupported: function( eventName ) {
var el = document.createElement("div");
eventName = "on" + eventName;
var isSupported = (eventName in el);
if (!isSupported ) {
el.setAttribute(eventName, "return;");
isSupported = typeof el[eventName] === "function";
}
el = null;
return isSupported;
}
});
/**
* @Prototype
*/
extend(Syn.init.prototype, {
/**
* @function then
* <p>
* Then is used to chain a sequence of actions to be run one after the other.
* This is useful when many asynchronous actions need to be performed before some
* final check needs to be made.
* </p>
* <p>The following clicks and types into the <code>id='age'</code> element and then checks that only numeric characters can be entered.</p>
* <h3>Example</h3>
* @codestart
* Syn('click',{},'age')
* .then('type','I am 12',function(){
* equals($('#age').val(),"12")
* })
* @codeend
* If the element argument is undefined, then the last element is used.
*
* @param {String} type The type of event or action to create: "_click", "_dblclick", "_drag", "_type".
* @param {Object} options Optiosn to pass to the event.
* @param {String|HTMLElement} [element] A element's id or an element. If undefined, defaults to the previous element.
* @param {Function} [callback] A function to callback after the action has run, but before any future chained actions are run.
*/
then: function( type, options, element, callback ) {
if ( Syn.autoDelay ) {
this.delay();
}
var args = Syn.args(options, element, callback),
self = this;
//if stack is empty run right away
//otherwise ... unshift it
this.queue.unshift(function( el, prevented ) {
if ( typeof this[type] === "function" ) {
this.element = args.element || el;
this[type](args.options, this.element, function( defaults, el ) {
args.callback && args.callback.apply(self, arguments);
self.done.apply(self, arguments);
});
} else {
this.result = Syn.trigger(type, args.options, args.element);
args.callback && args.callback.call(this, args.element, this.result);
return this;
}
})
return this;
},
/**
* Delays the next command a set timeout.
* @param {Number} [timeout]
* @param {Function} [callback]
*/
delay: function( timeout, callback ) {
if ( typeof timeout === 'function' ) {
callback = timeout;
timeout = null;
}
timeout = timeout || 600;
var self = this;
this.queue.unshift(function() {
setTimeout(function() {
callback && callback.apply(self, [])
self.done.apply(self, arguments);
}, timeout);
});
return this;
},
done: function( defaults, el ) {
el && (this.element = el);
if ( this.queue.length ) {
this.queue.pop().call(this, this.element, defaults);
}
},
/**
* @function click
* Clicks an element by triggering a mousedown,
* mouseup,
* and a click event.
* <h3>Example</h3>
* @codestart
* Syn.click({},'create',function(){
* //check something
* })
* @codeend
* You can also provide the coordinates of the click.
* If jQuery is present, it will set clientX and clientY
* for you. Here's how to set it yourself:
* @codestart
* Syn.click(
* {clientX: 20, clientY: 100},
* 'create',
* function(){
* //check something
* })
* @codeend
* You can also provide pageX and pageY and Syn will convert it for you.
* @param {Object} options
* @param {HTMLElement} element
* @param {Function} callback
*/
"_click": function( options, element, callback, force ) {
Syn.helpers.addOffset(options, element);
Syn.trigger("mousedown", options, element);
//timeout is b/c IE is stupid and won't call focus handlers
setTimeout(function() {
Syn.trigger("mouseup", options, element);
if (!Syn.support.mouseDownUpClicks || force ) {
Syn.trigger("click", options, element);
callback(true);
} else {
//we still have to run the default (presumably)
Syn.create.click.setup('click', options, element);
Syn.defaults.click.call(element);
//must give time for callback
setTimeout(function() {
callback(true);
}, 1);
}
}, 1);
},
/**
* Right clicks in browsers that support it (everyone but opera).
* @param {Object} options
* @param {Object} element
* @param {Object} callback
*/
"_rightClick": function( options, element, callback ) {
Syn.helpers.addOffset(options, element);
var mouseopts = extend(extend({}, Syn.mouse.browser.right.mouseup), options);
Syn.trigger("mousedown", mouseopts, element);
//timeout is b/c IE is stupid and won't call focus handlers
setTimeout(function() {
Syn.trigger("mouseup", mouseopts, element);
if ( Syn.mouse.browser.right.contextmenu ) {
Syn.trigger("contextmenu", extend(extend({}, Syn.mouse.browser.right.contextmenu), options), element);
}
callback(true);
}, 1);
},
/**
* @function dblclick
* Dblclicks an element. This runs two [Syn.prototype.click click] events followed by
* a dblclick on the element.
* <h3>Example</h3>
* @codestart
* Syn.dblclick({},'open')
* @codeend
* @param {Object} options
* @param {HTMLElement} element
* @param {Function} callback
*/
"_dblclick": function( options, element, callback ) {
Syn.helpers.addOffset(options, element);
var self = this;
this._click(options, element, function() {
setTimeout(function() {
self._click(options, element, function() {
Syn.trigger("dblclick", options, element);
callback(true);
}, true);
}, 2);
});
}
});
var actions = ["click", "dblclick", "move", "drag", "key", "type", 'rightClick'],
makeAction = function( name ) {
Syn[name] = function( options, element, callback ) {
return Syn("_" + name, options, element, callback);
};
Syn.init.prototype[name] = function( options, element, callback ) {
return this.then("_" + name, options, element, callback);
};
},
i = 0;
for ( ; i < actions.length; i++ ) {
makeAction(actions[i]);
}
return Syn;
})();
// ## mouse.js
var __m3 = (function(Syn) {
//handles mosue events
var h = Syn.helpers,
getWin = h.getWindow;
Syn.mouse = {};
h.extend(Syn.defaults, {
mousedown: function( options ) {
Syn.trigger("focus", {}, this)
},
click: function() {
// prevents the access denied issue in IE if the click causes the element to be destroyed
var element = this, href, type, radioChanged, nodeName, scope;
try {
href = element.href;
type = element.type;
createChange = Syn.data(element, "createChange");
radioChanged = Syn.data(element, "radioChanged");
scope = getWin(element);
nodeName = element.nodeName.toLowerCase();
} catch (e) {
return;
}
//get old values
//this code was for restoring the href attribute to prevent popup opening
//if ((href = Syn.data(element, "href"))) {
// element.setAttribute('href', href)
//}
//run href javascript
if (!Syn.support.linkHrefJS && /^\s*javascript:/.test(href) ) {
//eval js
var code = href.replace(/^\s*javascript:/, "")
//try{
if ( code != "//" && code.indexOf("void(0)") == -1 ) {
if ( window.selenium ) {
eval("with(selenium.browserbot.getCurrentWindow()){" + code + "}")
} else {
eval("with(scope){" + code + "}")
}
}
}
//submit a form
if (!(Syn.support.clickSubmits) && (nodeName == "input" && type == "submit") || nodeName == 'button' ) {
var form = Syn.closest(element, "form");
if ( form ) {
Syn.trigger("submit", {}, form)
}
}
//follow a link, probably needs to check if in an a.
if ( nodeName == "a" && element.href && !/^\s*javascript:/.test(href) ) {
scope.location.href = href;
}
//change a checkbox
if ( nodeName == "input" && type == "checkbox" ) {
//if(!Syn.support.clickChecks && !Syn.support.changeChecks){
// element.checked = !element.checked;
//}
if (!Syn.support.clickChanges ) {
Syn.trigger("change", {}, element);
}
}
//change a radio button
if ( nodeName == "input" && type == "radio" ) { // need to uncheck others if not checked
if ( radioChanged && !Syn.support.radioClickChanges ) {
Syn.trigger("change", {}, element);
}
}
// change options
if ( nodeName == "option" && createChange ) {
Syn.trigger("change", {}, element.parentNode); //does not bubble
Syn.data(element, "createChange", false)
}
}
})
//add create and setup behavior for mosue events
h.extend(Syn.create, {
mouse: {
options: function( type, options, element ) {
var doc = document.documentElement,
body = document.body,
center = [options.pageX || 0, options.pageY || 0],
//browser might not be loaded yet (doing support code)
left = Syn.mouse.browser && Syn.mouse.browser.left[type],
right = Syn.mouse.browser && Syn.mouse.browser.right[type];
return h.extend({
bubbles: true,
cancelable: true,
view: window,
detail: 1,
screenX: 1,
screenY: 1,
clientX: options.clientX || center[0] - (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0),
clientY: options.clientY || center[1] - (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0),
ctrlKey: !! Syn.key.ctrlKey,
altKey: !! Syn.key.altKey,
shiftKey: !! Syn.key.shiftKey,
metaKey: !! Syn.key.metaKey,
button: left && left.button != null ? left.button : right && right.button || (type == 'contextmenu' ? 2 : 0),
relatedTarget: document.documentElement
}, options);
},
event: function( type, defaults, element ) { //Everyone Else
var doc = getWin(element).document || document
if ( doc.createEvent ) {
var event;
try {
event = doc.createEvent('MouseEvents');
event.initMouseEvent(type, defaults.bubbles, defaults.cancelable, defaults.view, defaults.detail, defaults.screenX, defaults.screenY, defaults.clientX, defaults.clientY, defaults.ctrlKey, defaults.altKey, defaults.shiftKey, defaults.metaKey, defaults.button, defaults.relatedTarget);
} catch (e) {
event = h.createBasicStandardEvent(type, defaults, doc)
}
event.synthetic = true;
return event;
} else {
var event;
try {
event = h.createEventObject(type, defaults, element)
}
catch (e) {}
return event;
}
}
},
click: {
setup: function( type, options, element ) {
var nodeName = element.nodeName.toLowerCase(),
type;
//we need to manually 'check' in browser that can't check
//so checked has the right value
if (!Syn.support.clickChecks && !Syn.support.changeChecks && nodeName === "input" ) {
type = element.type.toLowerCase(); //pretty sure lowercase isn't needed
if ( type === 'checkbox' ) {
element.checked = !element.checked;
}
if ( type === "radio" ) {
//do the checks manually
if (!element.checked ) { //do nothing, no change
try {
Syn.data(element, "radioChanged", true);
} catch (e) {}
element.checked = true;
}
}
}
if ( nodeName == "a" && element.href && !/^\s*javascript:/.test(element.href) ) {
//save href
Syn.data(element, "href", element.href)
//remove b/c safari/opera will open a new tab instead of changing the page
// this has been removed because newer versions don't have this problem
//element.setAttribute('href', 'javascript://')
//however this breaks scripts using the href
//we need to listen to this and prevent the default behavior
//and run the default behavior ourselves. Boo!
}
//if select or option, save old value and mark to change
if (/option/i.test(element.nodeName) ) {
var child = element.parentNode.firstChild,
i = -1;
while ( child ) {
if ( child.nodeType == 1 ) {
i++;
if ( child == element ) break;
}
child = child.nextSibling;
}
if ( i !== element.parentNode.selectedIndex ) {
//shouldn't this wait on triggering
//change?
element.parentNode.selectedIndex = i;
Syn.data(element, "createChange", true)
}
}
}
},
mousedown: {
setup: function( type, options, element ) {
var nn = element.nodeName.toLowerCase();
//we have to auto prevent default to prevent freezing error in safari
if ( Syn.browser.safari && (nn == "select" || nn == "option") ) {
options._autoPrevent = true;
}
}
}
});
//do support code
(function() {
if (!document.body ) {
setTimeout(arguments.callee, 1)
return;
}
var oldSynth = window.__synthTest;
window.__synthTest = function() {
Syn.support.linkHrefJS = true;
}
var div = document.createElement("div"),
checkbox, submit, form, input, select;
div.innerHTML = "<form id='outer'>" + "<input name='checkbox' type='checkbox'/>" + "<input name='radio' type='radio' />" + "<input type='submit' name='submitter'/>" + "<input type='input' name='inputter'/>" + "<input name='one'>" + "<input name='two'/>" + "<a href='javascript:__synthTest()' id='synlink'></a>" + "<select><option></option></select>" + "</form>";
document.documentElement.appendChild(div);
form = div.firstChild
checkbox = form.childNodes[0];
submit = form.childNodes[2];
select = form.getElementsByTagName('select')[0]
checkbox.checked = false;
checkbox.onchange = function() {
Syn.support.clickChanges = true;
}
Syn.trigger("click", {}, checkbox)
Syn.support.clickChecks = checkbox.checked;
checkbox.checked = false;
Syn.trigger("change", {}, checkbox);
Syn.support.changeChecks = checkbox.checked;
form.onsubmit = function( ev ) {
if ( ev.preventDefault ) ev.preventDefault();
Syn.support.clickSubmits = true;
return false;
}
Syn.trigger("click", {}, submit)
form.childNodes[1].onchange = function() {
Syn.support.radioClickChanges = true;
}
Syn.trigger("click", {}, form.childNodes[1])
Syn.bind(div, 'click', function() {
Syn.support.optionClickBubbles = true;
Syn.unbind(div, 'click', arguments.callee)
})
Syn.trigger("click", {}, select.firstChild)
Syn.support.changeBubbles = Syn.eventSupported('change');
//test if mousedown followed by mouseup causes click (opera), make sure there are no clicks after this
var clicksCount = 0
div.onclick = function() {
Syn.support.mouseDownUpClicks = true;
//we should use this to check for opera potentially, but would
//be difficult to remove element correctly
//Syn.support.mouseDownUpRepeatClicks = (2 == (++clicksCount))
}
Syn.trigger("mousedown", {}, div)
Syn.trigger("mouseup", {}, div)
//setTimeout(function(){
// Syn.trigger("mousedown",{},div)
// Syn.trigger("mouseup",{},div)
//},1)
document.documentElement.removeChild(div);
//check stuff
window.__synthTest = oldSynth;
Syn.support.ready++;
})();
return Syn;
})(__m2);
// ## browsers.js
var __m4 = (function(Syn) {
Syn.key.browsers = {
webkit : {
'prevent':
{"keyup":[],"keydown":["char","keypress"],"keypress":["char"]},
'character':
{"keydown":[0,"key"],"keypress":["char","char"],"keyup":[0,"key"]},
'specialChars':
{"keydown":[0,"char"],"keyup":[0,"char"]},
'navigation':
{"keydown":[0,"key"],"keyup":[0,"key"]},
'special':
{"keydown":[0,"key"],"keyup":[0,"key"]},
'tab':
{"keydown":[0,"char"],"keyup":[0,"char"]},
'pause-break':
{"keydown":[0,"key"],"keyup":[0,"key"]},
'caps':
{"keydown":[0,"key"],"keyup":[0,"key"]},
'escape':
{"keydown":[0,"key"],"keyup":[0,"key"]},
'num-lock':
{"keydown":[0,"key"],"keyup":[0,"key"]},
'scroll-lock':
{"keydown":[0,"key"],"keyup":[0,"key"]},
'print':
{"keyup":[0,"key"]},
'function':
{"keydown":[0,"key"],"keyup":[0,"key"]},
'\r':
{"keydown":[0,"key"],"keypress":["char","key"],"keyup":[0,"key"]}
},
gecko : {
'prevent':
{"keyup":[],"keydown":["char"],"keypress":["char"]},
'character':
{"keydown":[0,"key"],"keypress":["char",0],"keyup":[0,"key"]},
'specialChars':
{"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]},
'navigation':
{"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]},
'special':
{"keydown":[0,"key"],"keyup":[0,"key"]},
'\t':
{"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]},
'pause-break':
{"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]},
'caps':
{"keydown":[0,"key"],"keyup":[0,"key"]},
'escape':
{"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]},
'num-lock':
{"keydown":[0,"key"],"keyup":[0,"key"]},
'scroll-lock':
{"keydown":[0,"key"],"keyup":[0,"key"]},
'print':
{"keyup":[0,"key"]},
'function':
{"keydown":[0,"key"],"keyup":[0,"key"]},
'\r':
{"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}
},
msie : {
'prevent':{"keyup":[],"keydown":["char","keypress"],"keypress":["char"]},
'character':{"keydown":[null,"key"],"keypress":[null,"char"],"keyup":[null,"key"]},
'specialChars':{"keydown":[null,"char"],"keyup":[null,"char"]},
'navigation':{"keydown":[null,"key"],"keyup":[null,"key"]},
'special':{"keydown":[null,"key"],"keyup":[null,"key"]},
'tab':{"keydown":[null,"char"],"keyup":[null,"char"]},
'pause-break':{"keydown":[null,"key"],"keyup":[null,"key"]},
'caps':{"keydown":[null,"key"],"keyup":[null,"key"]},
'escape':{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]},
'num-lock':{"keydown":[null,"key"],"keyup":[null,"key"]},
'scroll-lock':{"keydown":[null,"key"],"keyup":[null,"key"]},
'print':{"keyup":[null,"key"]},
'function':{"keydown":[null,"key"],"keyup":[null,"key"]},
'\r':{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}
},
opera : {
'prevent':
{"keyup":[],"keydown":[],"keypress":["char"]},
'character':
{"keydown":[null,"key"],"keypress":[null,"char"],"keyup":[null,"key"]},
'specialChars':
{"keydown":[null,"char"],"keypress":[null,"char"],"keyup":[null,"char"]},
'navigation':
{"keydown":[null,"key"],"keypress":[null,"key"]},
'special':
{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]},
'tab':
{"keydown":[null,"char"],"keypress":[null,"char"],"keyup":[null,"char"]},
'pause-break':
{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]},
'caps':
{"keydown":[null,"key"],"keyup":[null,"key"]},
'escape':
{"keydown":[null,"key"],"keypress":[null,"key"]},
'num-lock':
{"keyup":[null,"key"],"keydown":[null,"key"],"keypress":[null,"key"]},
'scroll-lock':
{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]},
'print':
{},
'function':
{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]},
'\r':
{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}
}
};
Syn.mouse.browsers = {
webkit : {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}},
"left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}},
opera: {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3}},
"left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}},
msie: { "right":{"mousedown":{"button":2},"mouseup":{"button":2},"contextmenu":{"button":0}},
"left":{"mousedown":{"button":1},"mouseup":{"button":1},"click":{"button":0}}},
chrome : {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}},
"left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}},
gecko: {"left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}},
"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}}}
}
//set browser
Syn.key.browser =
(function(){
if(Syn.key.browsers[window.navigator.userAgent]){
return Syn.key.browsers[window.navigator.userAgent];
}
for(var browser in Syn.browser){
if(Syn.browser[browser] && Syn.key.browsers[browser]){
return Syn.key.browsers[browser]
}
}
return Syn.key.browsers.gecko;
})();
Syn.mouse.browser =
(function(){
if(Syn.mouse.browsers[window.navigator.userAgent]){
return Syn.mouse.browsers[window.navigator.userAgent];
}
for(var browser in Syn.browser){
if(Syn.browser[browser] && Syn.mouse.browsers[browser]){
return Syn.mouse.browsers[browser]
}
}
return Syn.mouse.browsers.gecko;
})();
return Syn;
})(__m2, __m3);
// ## key.js
var __m5 = (function(Syn) {
var h = Syn.helpers,
// gets the selection of an input or textarea
getSelection = function( el ) {
// use selectionStart if we can
if ( el.selectionStart !== undefined ) {
// this is for opera, so we don't have to focus to type how we think we would
if ( document.activeElement && document.activeElement != el && el.selectionStart == el.selectionEnd && el.selectionStart == 0 ) {
return {
start: el.value.length,
end: el.value.length
};
}
return {
start: el.selectionStart,
end: el.selectionEnd
}
} else {
//check if we aren't focused
try {
//try 2 different methods that work differently (IE breaks depending on type)
if ( el.nodeName.toLowerCase() == 'input' ) {
var real = h.getWindow(el).document.selection.createRange(),
r = el.createTextRange();
r.setEndPoint("EndToStart", real);
var start = r.text.length
return {
start: start,
end: start + real.text.length
}
}
else {
var real = h.getWindow(el).document.selection.createRange(),
r = real.duplicate(),
r2 = real.duplicate(),
r3 = real.duplicate();
r2.collapse();
r3.collapse(false);
r2.moveStart('character', -1)
r3.moveStart('character', -1)
//select all of our element
r.moveToElementText(el)
//now move our endpoint to the end of our real range
r.setEndPoint('EndToEnd', real);
var start = r.text.length - real.text.length,
end = r.text.length;
if ( start != 0 && r2.text == "" ) {
start += 2;
}
if ( end != 0 && r3.text == "" ) {
end += 2;
}
//if we aren't at the start, but previous is empty, we are at start of newline
return {
start: start,
end: end
}
}
} catch (e) {
return {
start: el.value.length,
end: el.value.length
};
}
}
},
// gets all focusable elements
getFocusable = function( el ) {
var document = h.getWindow(el).document,
res = [];
var els = document.getElementsByTagName('*'),
len = els.length;
for ( var i = 0; i < len; i++ ) {
Syn.isFocusable(els[i]) && els[i] != document.documentElement && res.push(els[i])
}
return res;
};
/**
* @add Syn static
*/
h.extend(Syn, {
/**
* @attribute
* A list of the keys and their keycodes codes you can type.
* You can add type keys with
* @codestart
* Syn('key','delete','title');
*
* //or
*
* Syn('type','One Two Three[left][left][delete]','title')
* @codeend
*
* The following are a list of keys you can type:
* @codestart text
* \b - backspace
* \t - tab
* \r - enter
* ' ' - space
* a-Z 0-9 - normal characters
* /!@#$*,.? - All other typeable characters
* page-up - scrolls up
* page-down - scrolls down
* end - scrolls to bottom
* home - scrolls to top
* insert - changes how keys are entered
* delete - deletes the next character
* left - moves cursor left
* right - moves cursor right
* up - moves the cursor up
* down - moves the cursor down
* f1-12 - function buttons
* shift, ctrl, alt - special keys
* pause-break - the pause button
* scroll-lock - locks scrolling
* caps - makes caps
* escape - escape button
* num-lock - allows numbers on keypad
* print - screen capture
* @codeend
*/
keycodes: {
//backspace
'\b': 8,
//tab
'\t': 9,
//enter
'\r': 13,
//special
'shift': 16,
'ctrl': 17,
'alt': 18,
//weird
'pause-break': 19,
'caps': 20,
'escape': 27,
'num-lock': 144,
'scroll-lock': 145,
'print': 44,
//navigation
'page-up': 33,
'page-down': 34,
'end': 35,
'home': 36,
'left': 37,
'up': 38,
'right': 39,
'down': 40,
'insert': 45,
'delete': 46,
//normal characters
' ': 32,
'0': 48,
'1': 49,
'2': 50,
'3': 51,
'4': 52,
'5': 53,
'6': 54,
'7': 55,
'8': 56,
'9': 57,
'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,
'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,
//normal-characters, numpad
'num0': 96,
'num1': 97,
'num2': 98,
'num3': 99,
'num4': 100,
'num5': 101,
'num6': 102,
'num7': 103,
'num8': 104,
'num9': 105,
'*': 106,
'+': 107,
'-': 109,
'.': 110,
//normal-characters, others
'/': 111,
';': 186,
'=': 187,
',': 188,
'-': 189,
'.': 190,
'/': 191,
'`': 192,
'[': 219,
'\\': 220,
']': 221,
"'": 222,
//ignore these, you shouldn't use them
'left window key': 91,
'right window key': 92,
'select key': 93,
'f1': 112,
'f2': 113,
'f3': 114,
'f4': 115,
'f5': 116,
'f6': 117,
'f7': 118,
'f8': 119,
'f9': 120,
'f10': 121,
'f11': 122,
'f12': 123
},
// what we can type in
typeable: /input|textarea/i,
// selects text on an element
selectText: function( el, start, end ) {
if ( el.setSelectionRange ) {
if (!end ) {
el.focus();
el.setSelectionRange(start, start);
} else {
el.selectionStart = start;
el.selectionEnd = end;
}
} else if ( el.createTextRange ) {
//el.focus();
var r = el.createTextRange();
r.moveStart('character', start);
end = end || start;
r.moveEnd('character', end - el.value.length);
r.select();
}
},
getText: function( el ) {
//first check if the el has anything selected ..
if ( Syn.typeable.test(el.nodeName) ) {
var sel = getSelection(el);
return el.value.substring(sel.start, sel.end)
}
//otherwise get from page
var win = Syn.helpers.getWindow(el);
if ( win.getSelection ) {
return win.getSelection().toString();
}
else if ( win.document.getSelection ) {
return win.document.getSelection().toString()
}
else {
return win.document.selection.createRange().text;
}
},
getSelection: getSelection
});
h.extend(Syn.key, {
// retrieves a description of what events for this character should look like
data: function( key ) {
//check if it is described directly
if ( Syn.key.browser[key] ) {
return Syn.key.browser[key];
}
for ( var kind in Syn.key.kinds ) {
if ( h.inArray(key, Syn.key.kinds[kind]) > -1 ) {
return Syn.key.browser[kind]
}
}
return Syn.key.browser.character
},
//returns the special key if special
isSpecial: function( keyCode ) {
var specials = Syn.key.kinds.special;
for ( var i = 0; i < specials.length; i++ ) {
if ( Syn.keycodes[specials[i]] == keyCode ) {
return specials[i];
}
}
},
/**
* @hide
* gets the options for a key and event type ...
* @param {Object} key
* @param {Object} event
*/
options: function( key, event ) {
var keyData = Syn.key.data(key);
if (!keyData[event] ) {
//we shouldn't be creating this event
return null;
}
var charCode = keyData[event][0],
keyCode = keyData[event][1],
result = {};
if ( keyCode == 'key' ) {
result.keyCode = Syn.keycodes[key]
} else if ( keyCode == 'char' ) {
result.keyCode = key.charCodeAt(0)
} else {
result.keyCode = keyCode;
}
if ( charCode == 'char' ) {
result.charCode = key.charCodeAt(0)
} else if ( charCode !== null ) {
result.charCode = charCode;
}
// all current browsers have which property to normalize keyCode/charCode
if(result.keyCode){
result.which = result.keyCode;
} else {
result.which = result.charCode;
}
return result
},
//types of event keys
kinds: {
special: ["shift", 'ctrl', 'alt', 'caps'],
specialChars: ["\b"],
navigation: ["page-up", 'page-down', 'end', 'home', 'left', 'up', 'right', 'down', 'insert', 'delete'],
'function': ['f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12']
},
//returns the default function
// some keys have default functions
// some 'kinds' of keys have default functions
getDefault: function( key ) {
//check if it is described directly
if ( Syn.key.defaults[key] ) {
return Syn.key.defaults[key];
}
for ( var kind in Syn.key.kinds ) {
if ( h.inArray(key, Syn.key.kinds[kind]) > -1 && Syn.key.defaults[kind] ) {
return Syn.key.defaults[kind];
}
}
return Syn.key.defaults.character
},
// default behavior when typing
defaults: {
'charac