syn-rails 3.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,284 @@
1
+ (function(){
2
+ //handles mouse events
3
+
4
+ var h = Syn.helpers,
5
+ getWin = h.getWindow;
6
+
7
+ Syn.mouse = {};
8
+ h.extend(Syn.defaults, {
9
+ mousedown: function( options ) {
10
+ Syn.trigger("focus", {}, this)
11
+ },
12
+ click: function() {
13
+ // prevents the access denied issue in IE if the click causes the element to be destroyed
14
+ var element = this;
15
+ try {
16
+ element.nodeType;
17
+ } catch (e) {
18
+ return;
19
+ }
20
+ //get old values
21
+ var href, radioChanged = Syn.data(element, "radioChanged"),
22
+ scope = getWin(element),
23
+ nodeName = element.nodeName.toLowerCase();
24
+
25
+ //this code was for restoring the href attribute to prevent popup opening
26
+ //if ((href = Syn.data(element, "href"))) {
27
+ // element.setAttribute('href', href)
28
+ //}
29
+
30
+ //run href javascript
31
+ if (!Syn.support.linkHrefJS && /^\s*javascript:/.test(element.href) ) {
32
+ //eval js
33
+ var code = element.href.replace(/^\s*javascript:/, "")
34
+
35
+ //try{
36
+ if ( code != "//" && code.indexOf("void(0)") == -1 ) {
37
+ if ( window.selenium ) {
38
+ eval("with(selenium.browserbot.getCurrentWindow()){" + code + "}")
39
+ } else {
40
+ eval("with(scope){" + code + "}")
41
+ }
42
+ }
43
+ }
44
+
45
+ //submit a form
46
+ if (!(Syn.support.clickSubmits) && (nodeName == "input" && element.type == "submit") || nodeName == 'button' ) {
47
+
48
+ var form = Syn.closest(element, "form");
49
+ if ( form ) {
50
+ Syn.trigger("submit", {}, form)
51
+ }
52
+
53
+ }
54
+ //follow a link, probably needs to check if in an a.
55
+ if ( nodeName == "a" && element.href && !/^\s*javascript:/.test(element.href) ) {
56
+
57
+ scope.location.href = element.href;
58
+
59
+ }
60
+
61
+ //change a checkbox
62
+ if ( nodeName == "input" && element.type == "checkbox" ) {
63
+
64
+ //if(!Syn.support.clickChecks && !Syn.support.changeChecks){
65
+ // element.checked = !element.checked;
66
+ //}
67
+ if (!Syn.support.clickChanges ) {
68
+ Syn.trigger("change", {}, element);
69
+ }
70
+ }
71
+
72
+ //change a radio button
73
+ if ( nodeName == "input" && element.type == "radio" ) { // need to uncheck others if not checked
74
+ if ( radioChanged && !Syn.support.radioClickChanges ) {
75
+ Syn.trigger("change", {}, element);
76
+ }
77
+ }
78
+ // change options
79
+ if ( nodeName == "option" && Syn.data(element, "createChange") ) {
80
+ Syn.trigger("change", {}, element.parentNode); //does not bubble
81
+ Syn.data(element, "createChange", false)
82
+ }
83
+ }
84
+ })
85
+
86
+ //add create and setup behavior for mosue events
87
+ h.extend(Syn.create, {
88
+ mouse: {
89
+ options: function( type, options, element ) {
90
+ var doc = document.documentElement,
91
+ body = document.body,
92
+ center = [options.pageX || 0, options.pageY || 0],
93
+ //browser might not be loaded yet (doing support code)
94
+ left = Syn.mouse.browser && Syn.mouse.browser.left[type],
95
+ right = Syn.mouse.browser && Syn.mouse.browser.right[type];
96
+ return h.extend({
97
+ bubbles: true,
98
+ cancelable: true,
99
+ view: window,
100
+ detail: 1,
101
+ screenX: 1,
102
+ screenY: 1,
103
+ clientX: options.clientX || center[0] - (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0),
104
+ clientY: options.clientY || center[1] - (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0),
105
+ ctrlKey: !! Syn.key.ctrlKey,
106
+ altKey: !! Syn.key.altKey,
107
+ shiftKey: !! Syn.key.shiftKey,
108
+ metaKey: !! Syn.key.metaKey,
109
+ button: left && left.button != null ? left.button : right && right.button || (type == 'contextmenu' ? 2 : 0),
110
+ relatedTarget: document.documentElement
111
+ }, options);
112
+ },
113
+ event: function( type, defaults, element ) { //Everyone Else
114
+ var doc = getWin(element).document || document
115
+ if ( doc.createEvent ) {
116
+ var event;
117
+
118
+ try {
119
+ event = doc.createEvent('MouseEvents');
120
+ 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);
121
+ } catch (e) {
122
+ event = h.createBasicStandardEvent(type, defaults, doc)
123
+ }
124
+ event.synthetic = true;
125
+ return event;
126
+ } else {
127
+ var event;
128
+ try {
129
+ event = h.createEventObject(type, defaults, element)
130
+ }
131
+ catch (e) {}
132
+
133
+ return event;
134
+ }
135
+
136
+ }
137
+ },
138
+ click: {
139
+ setup: function( type, options, element ) {
140
+ var nodeName = element.nodeName.toLowerCase(),
141
+ type;
142
+
143
+ //we need to manually 'check' in browser that can't check
144
+ //so checked has the right value
145
+ if (!Syn.support.clickChecks && !Syn.support.changeChecks && nodeName === "input" ) {
146
+ type = element.type.toLowerCase(); //pretty sure lowercase isn't needed
147
+ if ( type === 'checkbox' ) {
148
+ element.checked = !element.checked;
149
+ }
150
+ if ( type === "radio" ) {
151
+ //do the checks manually
152
+ if (!element.checked ) { //do nothing, no change
153
+ try {
154
+ Syn.data(element, "radioChanged", true);
155
+ } catch (e) {}
156
+ element.checked = true;
157
+ }
158
+ }
159
+ }
160
+
161
+ if ( nodeName == "a" && element.href && !/^\s*javascript:/.test(element.href) ) {
162
+
163
+ //save href
164
+ Syn.data(element, "href", element.href)
165
+
166
+ //remove b/c safari/opera will open a new tab instead of changing the page
167
+ // this has been removed because newer versions don't have this problem
168
+ //element.setAttribute('href', 'javascript://')
169
+ //however this breaks scripts using the href
170
+ //we need to listen to this and prevent the default behavior
171
+ //and run the default behavior ourselves. Boo!
172
+ }
173
+ //if select or option, save old value and mark to change
174
+ if (/option/i.test(element.nodeName) ) {
175
+ var child = element.parentNode.firstChild,
176
+ i = -1;
177
+ while ( child ) {
178
+ if ( child.nodeType == 1 ) {
179
+ i++;
180
+ if ( child == element ) break;
181
+ }
182
+ child = child.nextSibling;
183
+ }
184
+ if ( i !== element.parentNode.selectedIndex ) {
185
+ //shouldn't this wait on triggering
186
+ //change?
187
+ element.parentNode.selectedIndex = i;
188
+ Syn.data(element, "createChange", true)
189
+ }
190
+ }
191
+
192
+ }
193
+ },
194
+ mousedown: {
195
+ setup: function( type, options, element ) {
196
+ var nn = element.nodeName.toLowerCase();
197
+ //we have to auto prevent default to prevent freezing error in safari
198
+ if ( Syn.browser.safari && (nn == "select" || nn == "option") ) {
199
+ options._autoPrevent = true;
200
+ }
201
+ }
202
+ }
203
+ });
204
+ //do support code
205
+ (function() {
206
+ if (!document.body ) {
207
+ setTimeout(arguments.callee, 1)
208
+ return;
209
+ }
210
+ var oldSynth = window.__synthTest;
211
+ window.__synthTest = function() {
212
+ Syn.support.linkHrefJS = true;
213
+ }
214
+ var div = document.createElement("div"),
215
+ checkbox, submit, form, input, select;
216
+
217
+ 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>";
218
+ document.documentElement.appendChild(div);
219
+ form = div.firstChild
220
+ checkbox = form.childNodes[0];
221
+ submit = form.childNodes[2];
222
+ select = form.getElementsByTagName('select')[0]
223
+
224
+ checkbox.checked = false;
225
+ checkbox.onchange = function() {
226
+ Syn.support.clickChanges = true;
227
+ }
228
+
229
+ Syn.trigger("click", {}, checkbox)
230
+ Syn.support.clickChecks = checkbox.checked;
231
+
232
+ checkbox.checked = false;
233
+
234
+ Syn.trigger("change", {}, checkbox);
235
+
236
+ Syn.support.changeChecks = checkbox.checked;
237
+
238
+ form.onsubmit = function( ev ) {
239
+ if ( ev.preventDefault ) ev.preventDefault();
240
+ Syn.support.clickSubmits = true;
241
+ return false;
242
+ }
243
+ Syn.trigger("click", {}, submit)
244
+
245
+
246
+
247
+ form.childNodes[1].onchange = function() {
248
+ Syn.support.radioClickChanges = true;
249
+ }
250
+ Syn.trigger("click", {}, form.childNodes[1])
251
+
252
+
253
+ Syn.bind(div, 'click', function() {
254
+ Syn.support.optionClickBubbles = true;
255
+ Syn.unbind(div, 'click', arguments.callee)
256
+ })
257
+ Syn.trigger("click", {}, select.firstChild)
258
+
259
+
260
+ Syn.support.changeBubbles = Syn.eventSupported('change');
261
+
262
+ //test if mousedown followed by mouseup causes click (opera), make sure there are no clicks after this
263
+ var clicksCount = 0
264
+ div.onclick = function() {
265
+ Syn.support.mouseDownUpClicks = true;
266
+ //we should use this to check for opera potentially, but would
267
+ //be difficult to remove element correctly
268
+ //Syn.support.mouseDownUpRepeatClicks = (2 == (++clicksCount))
269
+ }
270
+ Syn.trigger("mousedown", {}, div)
271
+ Syn.trigger("mouseup", {}, div)
272
+
273
+ //setTimeout(function(){
274
+ // Syn.trigger("mousedown",{},div)
275
+ // Syn.trigger("mouseup",{},div)
276
+ //},1)
277
+
278
+ document.documentElement.removeChild(div);
279
+
280
+ //check stuff
281
+ window.__synthTest = oldSynth;
282
+ Syn.support.ready++;
283
+ })();
284
+ })(true);
@@ -0,0 +1,830 @@
1
+ (function(){
2
+ var extend = function( d, s ) {
3
+ var p;
4
+ for (p in s) {
5
+ d[p] = s[p];
6
+ }
7
+ return d;
8
+ },
9
+ // only uses browser detection for key events
10
+ browser = {
11
+ msie: !! (window.attachEvent && !window.opera),
12
+ opera: !! window.opera,
13
+ webkit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
14
+ safari: navigator.userAgent.indexOf('AppleWebKit/') > -1 && navigator.userAgent.indexOf('Chrome/') === -1,
15
+ gecko: navigator.userAgent.indexOf('Gecko') > -1,
16
+ mobilesafari: !! navigator.userAgent.match(/Apple.*Mobile.*Safari/),
17
+ rhino: navigator.userAgent.match(/Rhino/) && true
18
+ },
19
+ createEventObject = function( type, options, element ) {
20
+ var event = element.ownerDocument.createEventObject();
21
+ return extend(event, options);
22
+ },
23
+ data = {},
24
+ id = 1,
25
+ expando = "_synthetic" + new Date().getTime(),
26
+ bind, unbind, key = /keypress|keyup|keydown/,
27
+ page = /load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll/,
28
+ //this is maintained so we can click on html and blur the active element
29
+ activeElement,
30
+
31
+ /**
32
+ * @class Syn
33
+ * @download funcunit/dist/syn.js
34
+ * @test funcunit/synthetic/qunit.html
35
+ * Syn is used to simulate user actions. It creates synthetic events and
36
+ * performs their default behaviors.
37
+ *
38
+ * <h2>Basic Use</h2>
39
+ * The following clicks an input element with <code>id='description'</code>
40
+ * and then types <code>'Hello World'</code>.
41
+ *
42
+ @codestart
43
+ Syn.click({},'description')
44
+ .type("Hello World")
45
+ @codeend
46
+ * <h2>User Actions and Events</h2>
47
+ * <p>Syn is typically used to simulate user actions as opposed to triggering events. Typing characters
48
+ * is an example of a user action. The keypress that represents an <code>'a'</code>
49
+ * character being typed is an example of an event.
50
+ * </p>
51
+ * <p>
52
+ * While triggering events is supported, it's much more useful to simulate actual user behavior. The
53
+ * following actions are supported by Syn:
54
+ * </p>
55
+ * <ul>
56
+ * <li><code>[Syn.prototype.click click]</code> - a mousedown, focus, mouseup, and click.</li>
57
+ * <li><code>[Syn.prototype.dblclick dblclick]</code> - two <code>click!</code> events followed by a <code>dblclick</code>.</li>
58
+ * <li><code>[Syn.prototype.key key]</code> - types a single character (keydown, keypress, keyup).</li>
59
+ * <li><code>[Syn.prototype.type type]</code> - types multiple characters into an element.</li>
60
+ * <li><code>[Syn.prototype.move move]</code> - moves the mouse from one position to another (triggering mouseover / mouseouts).</li>
61
+ * <li><code>[Syn.prototype.drag drag]</code> - a mousedown, followed by mousemoves, and a mouseup.</li>
62
+ * </ul>
63
+ * All actions run asynchronously.
64
+ * Click on the links above for more
65
+ * information on how to use the specific action.
66
+ * <h2>Asynchronous Callbacks</h2>
67
+ * Actions don't complete immediately. This is almost
68
+ * entirely because <code>focus()</code>
69
+ * doesn't run immediately in IE.
70
+ * If you provide a callback function to Syn, it will
71
+ * be called after the action is completed.
72
+ * <br/>The following checks that "Hello World" was entered correctly:
73
+ @codestart
74
+ Syn.click({},'description')
75
+ .type("Hello World", function(){
76
+
77
+ ok("Hello World" == document.getElementById('description').value)
78
+ })
79
+ @codeend
80
+ <h2>Asynchronous Chaining</h2>
81
+ <p>You might have noticed the [Syn.prototype.then then] method. It provides chaining
82
+ so you can do a sequence of events with a single (final) callback.
83
+ </p><p>
84
+ If an element isn't provided to then, it uses the previous Syn's element.
85
+ </p>
86
+ The following does a lot of stuff before checking the result:
87
+ @codestart
88
+ Syn.type('ice water','title')
89
+ .type('ice and water','description')
90
+ .click({},'create')
91
+ .drag({to: 'favorites'},'newRecipe',
92
+ function(){
93
+ ok($('#newRecipe').parents('#favorites').length);
94
+ })
95
+ @codeend
96
+
97
+ <h2>jQuery Helper</h2>
98
+ If jQuery is present, Syn adds a triggerSyn helper you can use like:
99
+ @codestart
100
+ $("#description").triggerSyn("type","Hello World");
101
+ @codeend
102
+ * <h2>Key Event Recording</h2>
103
+ * <p>Every browser has very different rules for dispatching key events.
104
+ * As there is no way to feature detect how a browser handles key events,
105
+ * synthetic uses a description of how the browser behaves generated
106
+ * by a recording application. </p>
107
+ * <p>
108
+ * If you want to support a browser not currently supported, you can
109
+ * record that browser's key event description and add it to
110
+ * <code>Syn.key.browsers</code> by it's navigator agent.
111
+ * </p>
112
+ @codestart
113
+ Syn.key.browsers["Envjs\ Resig/20070309 PilotFish/1.2.0.10\1.6"] = {
114
+ 'prevent':
115
+ {"keyup":[],"keydown":["char","keypress"],"keypress":["char"]},
116
+ 'character':
117
+ { ... }
118
+ }
119
+ @codeend
120
+ * <h2>Limitations</h2>
121
+ * Syn fully supports IE 6+, FF 3+, Chrome, Safari, Opera 10+.
122
+ * With FF 1+, drag / move events are only partially supported. They will
123
+ * not trigger mouseover / mouseout events.<br/>
124
+ * Safari crashes when a mousedown is triggered on a select. Syn will not
125
+ * create this event.
126
+ * <h2>Contributing to Syn</h2>
127
+ * Have we missed something? We happily accept patches. The following are
128
+ * important objects and properties of Syn:
129
+ * <ul>
130
+ * <li><code>Syn.create</code> - contains methods to setup, convert options, and create an event of a specific type.</li>
131
+ * <li><code>Syn.defaults</code> - default behavior by event type (except for keys).</li>
132
+ * <li><code>Syn.key.defaults</code> - default behavior by key.</li>
133
+ * <li><code>Syn.keycodes</code> - supported keys you can type.</li>
134
+ * </ul>
135
+ * <h2>Roll Your Own Functional Test Framework</h2>
136
+ * <p>Syn is really the foundation of JavaScriptMVC's functional testing framework - [FuncUnit].
137
+ * But, we've purposely made Syn work without any dependencies in the hopes that other frameworks or
138
+ * testing solutions can use it as well.
139
+ * </p>
140
+ * @constructor
141
+ * Creates a synthetic event on the element.
142
+ * @param {Object} type
143
+ * @param {Object} options
144
+ * @param {Object} element
145
+ * @param {Object} callback
146
+ * @return Syn
147
+ */
148
+ Syn = function( type, options, element, callback ) {
149
+ return (new Syn.init(type, options, element, callback));
150
+ };
151
+
152
+ bind = function( el, ev, f ) {
153
+ return el.addEventListener ? el.addEventListener(ev, f, false) : el.attachEvent("on" + ev, f);
154
+ };
155
+ unbind = function( el, ev, f ) {
156
+ return el.addEventListener ? el.removeEventListener(ev, f, false) : el.detachEvent("on" + ev, f);
157
+ };
158
+
159
+ /**
160
+ * @Static
161
+ */
162
+ extend(Syn, {
163
+ /**
164
+ * Creates a new synthetic event instance
165
+ * @hide
166
+ * @param {Object} type
167
+ * @param {Object} options
168
+ * @param {Object} element
169
+ * @param {Object} callback
170
+ */
171
+ init: function( type, options, element, callback ) {
172
+ var args = Syn.args(options, element, callback),
173
+ self = this;
174
+ this.queue = [];
175
+ this.element = args.element;
176
+
177
+ //run event
178
+ if ( typeof this[type] === "function" ) {
179
+ this[type](args.options, args.element, function( defaults, el ) {
180
+ args.callback && args.callback.apply(self, arguments);
181
+ self.done.apply(self, arguments);
182
+ });
183
+ } else {
184
+ this.result = Syn.trigger(type, args.options, args.element);
185
+ args.callback && args.callback.call(this, args.element, this.result);
186
+ }
187
+ },
188
+ jquery: function( el, fast ) {
189
+ if ( window.FuncUnit && window.FuncUnit.jquery ) {
190
+ return window.FuncUnit.jquery;
191
+ }
192
+ if ( el ) {
193
+ return Syn.helpers.getWindow(el).jQuery || window.jQuery;
194
+ }
195
+ else {
196
+ return window.jQuery;
197
+ }
198
+ },
199
+ /**
200
+ * Returns an object with the args for a Syn.
201
+ * @hide
202
+ * @return {Object}
203
+ */
204
+ args: function() {
205
+ var res = {},
206
+ i = 0;
207
+ for ( ; i < arguments.length; i++ ) {
208
+ if ( typeof arguments[i] === 'function' ) {
209
+ res.callback = arguments[i];
210
+ } else if ( arguments[i] && arguments[i].jquery ) {
211
+ res.element = arguments[i][0];
212
+ } else if ( arguments[i] && arguments[i].nodeName ) {
213
+ res.element = arguments[i];
214
+ } else if ( res.options && typeof arguments[i] === 'string' ) { //we can get by id
215
+ res.element = document.getElementById(arguments[i]);
216
+ }
217
+ else if ( arguments[i] ) {
218
+ res.options = arguments[i];
219
+ }
220
+ }
221
+ return res;
222
+ },
223
+ click: function( options, element, callback ) {
224
+ Syn('click!', options, element, callback);
225
+ },
226
+ /**
227
+ * @attribute defaults
228
+ * Default actions for events. Each default function is called with this as its
229
+ * element. It should return true if a timeout
230
+ * should happen after it. If it returns an element, a timeout will happen
231
+ * and the next event will happen on that element.
232
+ */
233
+ defaults: {
234
+ focus: function() {
235
+ if (!Syn.support.focusChanges ) {
236
+ var element = this,
237
+ nodeName = element.nodeName.toLowerCase();
238
+ Syn.data(element, "syntheticvalue", element.value);
239
+
240
+ //TODO, this should be textarea too
241
+ //and this might be for only text style inputs ... hmmmmm ....
242
+ if ( nodeName === "input" || nodeName === "textarea" ) {
243
+ bind(element, "blur", function() {
244
+ if ( Syn.data(element, "syntheticvalue") != element.value ) {
245
+
246
+ Syn.trigger("change", {}, element);
247
+ }
248
+ unbind(element, "blur", arguments.callee);
249
+ });
250
+
251
+ }
252
+ }
253
+ },
254
+ submit: function() {
255
+ Syn.onParents(this, function( el ) {
256
+ if ( el.nodeName.toLowerCase() === 'form' ) {
257
+ el.submit();
258
+ return false;
259
+ }
260
+ });
261
+ }
262
+ },
263
+ changeOnBlur: function( element, prop, value ) {
264
+
265
+ bind(element, "blur", function() {
266
+ if ( value !== element[prop] ) {
267
+ Syn.trigger("change", {}, element);
268
+ }
269
+ unbind(element, "blur", arguments.callee);
270
+ });
271
+
272
+ },
273
+ /**
274
+ * Returns the closest element of a particular type.
275
+ * @hide
276
+ * @param {Object} el
277
+ * @param {Object} type
278
+ */
279
+ closest: function( el, type ) {
280
+ while ( el && el.nodeName.toLowerCase() !== type.toLowerCase() ) {
281
+ el = el.parentNode;
282
+ }
283
+ return el;
284
+ },
285
+ /**
286
+ * adds jQuery like data (adds an expando) and data exists FOREVER :)
287
+ * @hide
288
+ * @param {Object} el
289
+ * @param {Object} key
290
+ * @param {Object} value
291
+ */
292
+ data: function( el, key, value ) {
293
+ var d;
294
+ if (!el[expando] ) {
295
+ el[expando] = id++;
296
+ }
297
+ if (!data[el[expando]] ) {
298
+ data[el[expando]] = {};
299
+ }
300
+ d = data[el[expando]];
301
+ if ( value ) {
302
+ data[el[expando]][key] = value;
303
+ } else {
304
+ return data[el[expando]][key];
305
+ }
306
+ },
307
+ /**
308
+ * Calls a function on the element and all parents of the element until the function returns
309
+ * false.
310
+ * @hide
311
+ * @param {Object} el
312
+ * @param {Object} func
313
+ */
314
+ onParents: function( el, func ) {
315
+ var res;
316
+ while ( el && res !== false ) {
317
+ res = func(el);
318
+ el = el.parentNode;
319
+ }
320
+ return el;
321
+ },
322
+ //regex to match focusable elements
323
+ focusable: /^(a|area|frame|iframe|label|input|select|textarea|button|html|object)$/i,
324
+ /**
325
+ * Returns if an element is focusable
326
+ * @hide
327
+ * @param {Object} elem
328
+ */
329
+ isFocusable: function( elem ) {
330
+ var attributeNode;
331
+ return (this.focusable.test(elem.nodeName) ||
332
+ ((attributeNode = elem.getAttributeNode("tabIndex"))
333
+ && attributeNode.specified)) && Syn.isVisible(elem);
334
+ },
335
+ /**
336
+ * Returns if an element is visible or not
337
+ * @hide
338
+ * @param {Object} elem
339
+ */
340
+ isVisible: function( elem ) {
341
+ return (elem.offsetWidth && elem.offsetHeight) || (elem.clientWidth && elem.clientHeight);
342
+ },
343
+ /**
344
+ * Gets the tabIndex as a number or null
345
+ * @hide
346
+ * @param {Object} elem
347
+ */
348
+ tabIndex: function( elem ) {
349
+ var attributeNode = elem.getAttributeNode("tabIndex");
350
+ return attributeNode && attributeNode.specified && (parseInt(elem.getAttribute('tabIndex')) || 0);
351
+ },
352
+ bind: bind,
353
+ unbind: unbind,
354
+ browser: browser,
355
+ //some generic helpers
356
+ helpers: {
357
+ createEventObject: createEventObject,
358
+ createBasicStandardEvent: function( type, defaults, doc ) {
359
+ var event;
360
+ try {
361
+ event = doc.createEvent("Events");
362
+ } catch (e2) {
363
+ event = doc.createEvent("UIEvents");
364
+ } finally {
365
+ event.initEvent(type, true, true);
366
+ extend(event, defaults);
367
+ }
368
+ return event;
369
+ },
370
+ inArray: function( item, array ) {
371
+ var i =0;
372
+ for ( ; i < array.length; i++ ) {
373
+ if ( array[i] === item ) {
374
+ return i;
375
+ }
376
+ }
377
+ return -1;
378
+ },
379
+ getWindow: function( element ) {
380
+ return element.ownerDocument.defaultView || element.ownerDocument.parentWindow;
381
+ },
382
+ extend: extend,
383
+ scrollOffset: function( win , set) {
384
+ var doc = win.document.documentElement,
385
+ body = win.document.body;
386
+ if(set){
387
+ window.scrollTo(set.left, set.top);
388
+
389
+ } else {
390
+ return {
391
+ left: (doc && doc.scrollLeft || body && body.scrollLeft || 0) + (doc.clientLeft || 0),
392
+ top: (doc && doc.scrollTop || body && body.scrollTop || 0) + (doc.clientTop || 0)
393
+ };
394
+ }
395
+
396
+ },
397
+ scrollDimensions: function(win){
398
+ var doc = win.document.documentElement,
399
+ body = win.document.body,
400
+ docWidth = doc.clientWidth,
401
+ docHeight = doc.clientHeight,
402
+ compat = win.document.compatMode === "CSS1Compat";
403
+
404
+ return {
405
+ height: compat && docHeight ||
406
+ body.clientHeight || docHeight,
407
+ width: compat && docWidth ||
408
+ body.clientWidth || docWidth
409
+ };
410
+ },
411
+ addOffset: function( options, el ) {
412
+ var jq = Syn.jquery(el),
413
+ off;
414
+ if ( typeof options === 'object' && options.clientX === undefined && options.clientY === undefined && options.pageX === undefined && options.pageY === undefined && jq ) {
415
+ el = jq(el);
416
+ off = el.offset();
417
+ options.pageX = off.left + el.width() / 2;
418
+ options.pageY = off.top + el.height() / 2;
419
+ }
420
+ }
421
+ },
422
+ // place for key data
423
+ key: {
424
+ ctrlKey: null,
425
+ altKey: null,
426
+ shiftKey: null,
427
+ metaKey: null
428
+ },
429
+ //triggers an event on an element, returns true if default events should be run
430
+ /**
431
+ * Dispatches an event and returns true if default events should be run.
432
+ * @hide
433
+ * @param {Object} event
434
+ * @param {Object} element
435
+ * @param {Object} type
436
+ * @param {Object} autoPrevent
437
+ */
438
+ dispatch: function( event, element, type, autoPrevent ) {
439
+
440
+ // dispatchEvent doesn't always work in IE (mostly in a popup)
441
+ if ( element.dispatchEvent && event ) {
442
+ var preventDefault = event.preventDefault,
443
+ prevents = autoPrevent ? -1 : 0;
444
+
445
+ //automatically prevents the default behavior for this event
446
+ //this is to protect agianst nasty browser freezing bug in safari
447
+ if ( autoPrevent ) {
448
+ bind(element, type, function( ev ) {
449
+ ev.preventDefault();
450
+ unbind(this, type, arguments.callee);
451
+ });
452
+ }
453
+
454
+
455
+ event.preventDefault = function() {
456
+ prevents++;
457
+ if (++prevents > 0 ) {
458
+ preventDefault.apply(this, []);
459
+ }
460
+ };
461
+ element.dispatchEvent(event);
462
+ return prevents <= 0;
463
+ } else {
464
+ try {
465
+ window.event = event;
466
+ } catch (e) {}
467
+ //source element makes sure element is still in the document
468
+ return element.sourceIndex <= 0 || (element.fireEvent && element.fireEvent('on' + type, event));
469
+ }
470
+ },
471
+ /**
472
+ * @attribute
473
+ * @hide
474
+ * An object of eventType -> function that create that event.
475
+ */
476
+ create: {
477
+ //-------- PAGE EVENTS ---------------------
478
+ page: {
479
+ event: function( type, options, element ) {
480
+ var doc = Syn.helpers.getWindow(element).document || document,
481
+ event;
482
+ if ( doc.createEvent ) {
483
+ event = doc.createEvent("Events");
484
+
485
+ event.initEvent(type, true, true);
486
+ return event;
487
+ }
488
+ else {
489
+ try {
490
+ event = createEventObject(type, options, element);
491
+ }
492
+ catch (e) {}
493
+ return event;
494
+ }
495
+ }
496
+ },
497
+ // unique events
498
+ focus: {
499
+ event: function( type, options, element ) {
500
+ Syn.onParents(element, function( el ) {
501
+ if ( Syn.isFocusable(el) ) {
502
+ if ( el.nodeName.toLowerCase() !== 'html' ) {
503
+ el.focus();
504
+ activeElement = el;
505
+ }
506
+ else if ( activeElement ) {
507
+ // TODO: The HTML element isn't focasable in IE, but it is
508
+ // in FF. We should detect this and do a true focus instead
509
+ // of just a blur
510
+ var doc = Syn.helpers.getWindow(element).document;
511
+ if ( doc !== window.document ) {
512
+ return false;
513
+ } else if ( doc.activeElement ) {
514
+ doc.activeElement.blur();
515
+ activeElement = null;
516
+ } else {
517
+ activeElement.blur();
518
+ activeElement = null;
519
+ }
520
+
521
+
522
+ }
523
+ return false;
524
+ }
525
+ });
526
+ return true;
527
+ }
528
+ }
529
+ },
530
+ /**
531
+ * @attribute support
532
+ * Feature detected properties of a browser's event system.
533
+ * Support has the following properties:
534
+ * <ul>
535
+ * <li><code>clickChanges</code> - clicking on an option element creates a change event.</li>
536
+ * <li><code>clickSubmits</code> - clicking on a form button submits the form.</li>
537
+ * <li><code>mouseupSubmits</code> - a mouseup on a form button submits the form.</li>
538
+ * <li><code>radioClickChanges</code> - clicking a radio button changes the radio.</li>
539
+ * <li><code>focusChanges</code> - focus/blur creates a change event.</li>
540
+ * <li><code>linkHrefJS</code> - An achor's href JavaScript is run.</li>
541
+ * <li><code>mouseDownUpClicks</code> - A mousedown followed by mouseup creates a click event.</li>
542
+ * <li><code>tabKeyTabs</code> - A tab key changes tabs.</li>
543
+ * <li><code>keypressOnAnchorClicks</code> - Keying enter on an anchor triggers a click.</li>
544
+ * </ul>
545
+ */
546
+ support: {
547
+ clickChanges: false,
548
+ clickSubmits: false,
549
+ keypressSubmits: false,
550
+ mouseupSubmits: false,
551
+ radioClickChanges: false,
552
+ focusChanges: false,
553
+ linkHrefJS: false,
554
+ keyCharacters: false,
555
+ backspaceWorks: false,
556
+ mouseDownUpClicks: false,
557
+ tabKeyTabs: false,
558
+ keypressOnAnchorClicks: false,
559
+ optionClickBubbles: false,
560
+ ready: 0
561
+ },
562
+ /**
563
+ * Creates a synthetic event and dispatches it on the element.
564
+ * This will run any default actions for the element.
565
+ * Typically you want to use Syn, but if you want the return value, use this.
566
+ * @param {String} type
567
+ * @param {Object} options
568
+ * @param {HTMLElement} element
569
+ * @return {Boolean} true if default events were run, false if otherwise.
570
+ */
571
+ trigger: function( type, options, element ) {
572
+ options || (options = {});
573
+
574
+ var create = Syn.create,
575
+ setup = create[type] && create[type].setup,
576
+ kind = key.test(type) ? 'key' : (page.test(type) ? "page" : "mouse"),
577
+ createType = create[type] || {},
578
+ createKind = create[kind],
579
+ event, ret, autoPrevent, dispatchEl = element;
580
+
581
+ //any setup code?
582
+ Syn.support.ready === 2 && setup && setup(type, options, element);
583
+
584
+ autoPrevent = options._autoPrevent;
585
+ //get kind
586
+ delete options._autoPrevent;
587
+
588
+ if ( createType.event ) {
589
+ ret = createType.event(type, options, element);
590
+ } else {
591
+ //convert options
592
+ options = createKind.options ? createKind.options(type, options, element) : options;
593
+
594
+ if (!Syn.support.changeBubbles && /option/i.test(element.nodeName) ) {
595
+ dispatchEl = element.parentNode; //jQuery expects clicks on select
596
+ }
597
+
598
+ //create the event
599
+ event = createKind.event(type, options, dispatchEl);
600
+
601
+ //send the event
602
+ ret = Syn.dispatch(event, dispatchEl, type, autoPrevent);
603
+ }
604
+
605
+ //run default behavior
606
+ ret && Syn.support.ready === 2 && Syn.defaults[type] && Syn.defaults[type].call(element, options, autoPrevent);
607
+ return ret;
608
+ },
609
+ eventSupported: function( eventName ) {
610
+ var el = document.createElement("div");
611
+ eventName = "on" + eventName;
612
+
613
+ var isSupported = (eventName in el);
614
+ if (!isSupported ) {
615
+ el.setAttribute(eventName, "return;");
616
+ isSupported = typeof el[eventName] === "function";
617
+ }
618
+ el = null;
619
+
620
+ return isSupported;
621
+ }
622
+
623
+ });
624
+ /**
625
+ * @Prototype
626
+ */
627
+ extend(Syn.init.prototype, {
628
+ /**
629
+ * @function then
630
+ * <p>
631
+ * Then is used to chain a sequence of actions to be run one after the other.
632
+ * This is useful when many asynchronous actions need to be performed before some
633
+ * final check needs to be made.
634
+ * </p>
635
+ * <p>The following clicks and types into the <code>id='age'</code> element and then checks that only numeric characters can be entered.</p>
636
+ * <h3>Example</h3>
637
+ * @codestart
638
+ * Syn('click',{},'age')
639
+ * .then('type','I am 12',function(){
640
+ * equals($('#age').val(),"12")
641
+ * })
642
+ * @codeend
643
+ * If the element argument is undefined, then the last element is used.
644
+ *
645
+ * @param {String} type The type of event or action to create: "_click", "_dblclick", "_drag", "_type".
646
+ * @param {Object} options Optiosn to pass to the event.
647
+ * @param {String|HTMLElement} [element] A element's id or an element. If undefined, defaults to the previous element.
648
+ * @param {Function} [callback] A function to callback after the action has run, but before any future chained actions are run.
649
+ */
650
+ then: function( type, options, element, callback ) {
651
+ if ( Syn.autoDelay ) {
652
+ this.delay();
653
+ }
654
+ var args = Syn.args(options, element, callback),
655
+ self = this;
656
+
657
+
658
+ //if stack is empty run right away
659
+ //otherwise ... unshift it
660
+ this.queue.unshift(function( el, prevented ) {
661
+
662
+ if ( typeof this[type] === "function" ) {
663
+ this.element = args.element || el;
664
+ this[type](args.options, this.element, function( defaults, el ) {
665
+ args.callback && args.callback.apply(self, arguments);
666
+ self.done.apply(self, arguments);
667
+ });
668
+ } else {
669
+ this.result = Syn.trigger(type, args.options, args.element);
670
+ args.callback && args.callback.call(this, args.element, this.result);
671
+ return this;
672
+ }
673
+ })
674
+ return this;
675
+ },
676
+ /**
677
+ * Delays the next command a set timeout.
678
+ * @param {Number} [timeout]
679
+ * @param {Function} [callback]
680
+ */
681
+ delay: function( timeout, callback ) {
682
+ if ( typeof timeout === 'function' ) {
683
+ callback = timeout;
684
+ timeout = null;
685
+ }
686
+ timeout = timeout || 600;
687
+ var self = this;
688
+ this.queue.unshift(function() {
689
+ setTimeout(function() {
690
+ callback && callback.apply(self, [])
691
+ self.done.apply(self, arguments);
692
+ }, timeout);
693
+ });
694
+ return this;
695
+ },
696
+ done: function( defaults, el ) {
697
+ el && (this.element = el);
698
+ if ( this.queue.length ) {
699
+ this.queue.pop().call(this, this.element, defaults);
700
+ }
701
+
702
+ },
703
+ /**
704
+ * @function click
705
+ * Clicks an element by triggering a mousedown,
706
+ * mouseup,
707
+ * and a click event.
708
+ * <h3>Example</h3>
709
+ * @codestart
710
+ * Syn.click({},'create',function(){
711
+ * //check something
712
+ * })
713
+ * @codeend
714
+ * You can also provide the coordinates of the click.
715
+ * If jQuery is present, it will set clientX and clientY
716
+ * for you. Here's how to set it yourself:
717
+ * @codestart
718
+ * Syn.click(
719
+ * {clientX: 20, clientY: 100},
720
+ * 'create',
721
+ * function(){
722
+ * //check something
723
+ * })
724
+ * @codeend
725
+ * You can also provide pageX and pageY and Syn will convert it for you.
726
+ * @param {Object} options
727
+ * @param {HTMLElement} element
728
+ * @param {Function} callback
729
+ */
730
+ "_click": function( options, element, callback, force ) {
731
+ Syn.helpers.addOffset(options, element);
732
+ Syn.trigger("mousedown", options, element);
733
+
734
+ //timeout is b/c IE is stupid and won't call focus handlers
735
+ setTimeout(function() {
736
+ Syn.trigger("mouseup", options, element);
737
+ if (!Syn.support.mouseDownUpClicks || force ) {
738
+ Syn.trigger("click", options, element);
739
+ callback(true);
740
+ } else {
741
+ //we still have to run the default (presumably)
742
+ Syn.create.click.setup('click', options, element);
743
+ Syn.defaults.click.call(element);
744
+ //must give time for callback
745
+ setTimeout(function() {
746
+ callback(true);
747
+ }, 1);
748
+ }
749
+
750
+ }, 1);
751
+ },
752
+ /**
753
+ * Right clicks in browsers that support it (everyone but opera).
754
+ * @param {Object} options
755
+ * @param {Object} element
756
+ * @param {Object} callback
757
+ */
758
+ "_rightClick": function( options, element, callback ) {
759
+ Syn.helpers.addOffset(options, element);
760
+ var mouseopts = extend(extend({}, Syn.mouse.browser.right.mouseup), options);
761
+
762
+ Syn.trigger("mousedown", mouseopts, element);
763
+
764
+ //timeout is b/c IE is stupid and won't call focus handlers
765
+ setTimeout(function() {
766
+ Syn.trigger("mouseup", mouseopts, element);
767
+ if ( Syn.mouse.browser.right.contextmenu ) {
768
+ Syn.trigger("contextmenu", extend(extend({}, Syn.mouse.browser.right.contextmenu), options), element);
769
+ }
770
+ callback(true);
771
+ }, 1);
772
+ },
773
+ /**
774
+ * @function dblclick
775
+ * Dblclicks an element. This runs two [Syn.prototype.click click] events followed by
776
+ * a dblclick on the element.
777
+ * <h3>Example</h3>
778
+ * @codestart
779
+ * Syn.dblclick({},'open')
780
+ * @codeend
781
+ * @param {Object} options
782
+ * @param {HTMLElement} element
783
+ * @param {Function} callback
784
+ */
785
+ "_dblclick": function( options, element, callback ) {
786
+ Syn.helpers.addOffset(options, element);
787
+ var self = this;
788
+ this._click(options, element, function() {
789
+ setTimeout(function() {
790
+ self._click(options, element, function() {
791
+ Syn.trigger("dblclick", options, element);
792
+ callback(true);
793
+ }, true);
794
+ }, 2);
795
+
796
+ });
797
+ }
798
+ });
799
+
800
+ var actions = ["click", "dblclick", "move", "drag", "key", "type", 'rightClick'],
801
+ makeAction = function( name ) {
802
+ Syn[name] = function( options, element, callback ) {
803
+ return Syn("_" + name, options, element, callback);
804
+ };
805
+ Syn.init.prototype[name] = function( options, element, callback ) {
806
+ return this.then("_" + name, options, element, callback);
807
+ };
808
+ },
809
+ i = 0;
810
+ for ( ; i < actions.length; i++ ) {
811
+ makeAction(actions[i]);
812
+ }
813
+ /**
814
+ * Used for creating and dispatching synthetic events.
815
+ * @codestart
816
+ * new MVC.Syn('click').send(MVC.$E('id'))
817
+ * @codeend
818
+ * @constructor Sets up a synthetic event.
819
+ * @param {String} type type of event, ex: 'click'
820
+ * @param {optional:Object} options
821
+ */
822
+ if ( (window.FuncUnit && window.FuncUnit.jQuery) || window.jQuery ) {
823
+ ((window.FuncUnit && window.FuncUnit.jQuery) || window.jQuery).fn.triggerSyn = function( type, options, callback ) {
824
+ Syn(type, options, this[0], callback);
825
+ return this;
826
+ };
827
+ }
828
+
829
+ window.Syn = Syn;
830
+ })(true);