nitro 0.23.0 → 0.24.0

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.
Files changed (76) hide show
  1. data/CHANGELOG +350 -0
  2. data/INSTALL +2 -2
  3. data/ProjectInfo +61 -0
  4. data/README +5 -4
  5. data/Rakefile +5 -4
  6. data/bin/nitrogen +3 -1
  7. data/doc/AUTHORS +27 -3
  8. data/doc/RELEASES +193 -0
  9. data/doc/lhttpd.txt +4 -0
  10. data/lib/nitro.rb +1 -1
  11. data/lib/nitro/adapter/cgi.rb +6 -321
  12. data/lib/nitro/adapter/fastcgi.rb +2 -14
  13. data/lib/nitro/adapter/scgi.rb +237 -71
  14. data/lib/nitro/adapter/webrick.rb +25 -7
  15. data/lib/nitro/caching.rb +1 -0
  16. data/lib/nitro/cgi.rb +296 -0
  17. data/lib/nitro/{cookie.rb → cgi/cookie.rb} +0 -0
  18. data/lib/nitro/cgi/http.rb +62 -0
  19. data/lib/nitro/{request.rb → cgi/request.rb} +4 -1
  20. data/lib/nitro/{response.rb → cgi/response.rb} +0 -0
  21. data/lib/nitro/cgi/stream.rb +43 -0
  22. data/lib/nitro/cgi/utils.rb +38 -0
  23. data/lib/nitro/compiler.rb +23 -11
  24. data/lib/nitro/compiler/css.rb +8 -0
  25. data/lib/nitro/compiler/morphing.rb +66 -0
  26. data/lib/nitro/context.rb +21 -30
  27. data/lib/nitro/controller.rb +23 -100
  28. data/lib/nitro/dispatcher.rb +18 -8
  29. data/lib/nitro/element.rb +6 -2
  30. data/lib/nitro/flash.rb +2 -2
  31. data/lib/nitro/mixin/buffer.rb +2 -2
  32. data/lib/nitro/mixin/form.rb +204 -93
  33. data/lib/nitro/mixin/javascript.rb +170 -11
  34. data/lib/nitro/mixin/markup.rb +1 -0
  35. data/lib/nitro/mixin/pager.rb +7 -4
  36. data/lib/nitro/mixin/rss.rb +2 -0
  37. data/lib/nitro/mixin/table.rb +23 -6
  38. data/lib/nitro/mixin/xhtml.rb +2 -2
  39. data/lib/nitro/render.rb +19 -5
  40. data/lib/nitro/scaffold.rb +12 -6
  41. data/lib/nitro/server.rb +4 -6
  42. data/lib/nitro/server/runner.rb +2 -2
  43. data/lib/nitro/session.rb +8 -1
  44. data/lib/nitro/session/file.rb +40 -0
  45. data/lib/part/admin.rb +2 -0
  46. data/lib/part/admin/controller.rb +7 -3
  47. data/lib/part/admin/skin.rb +8 -1
  48. data/lib/part/admin/template/index.xhtml +39 -1
  49. data/proto/public/error.xhtml +5 -3
  50. data/proto/public/js/behaviour.js +254 -254
  51. data/proto/public/js/controls.js +427 -165
  52. data/proto/public/js/dragdrop.js +255 -276
  53. data/proto/public/js/effects.js +476 -277
  54. data/proto/public/js/prototype.js +561 -127
  55. data/proto/public/js/scaffold.js +74 -0
  56. data/proto/public/js/scriptaculous.js +44 -0
  57. data/proto/public/js/util.js +548 -0
  58. data/proto/public/scaffold/list.xhtml +4 -1
  59. data/proto/scgi.rb +333 -0
  60. data/script/scgi_ctl +221 -0
  61. data/script/scgi_service +120 -0
  62. data/test/nitro/adapter/raw_post1.bin +0 -0
  63. data/test/nitro/{tc_cookie.rb → cgi/tc_cookie.rb} +1 -1
  64. data/test/nitro/{tc_request.rb → cgi/tc_request.rb} +1 -1
  65. data/test/nitro/mixin/tc_xhtml.rb +1 -1
  66. data/test/nitro/{adapter/tc_cgi.rb → tc_cgi.rb} +12 -12
  67. data/test/nitro/tc_controller.rb +9 -5
  68. metadata +159 -169
  69. data/benchmark/bench.rb +0 -5
  70. data/benchmark/simple-webrick-n-200.txt +0 -44
  71. data/benchmark/static-webrick-n-200.txt +0 -43
  72. data/benchmark/tiny-lhttpd-n-200-c-5.txt +0 -43
  73. data/benchmark/tiny-webrick-n-200-c-5.txt +0 -44
  74. data/benchmark/tiny-webrick-n-200.txt +0 -44
  75. data/benchmark/tiny2-webrick-n-200.txt +0 -44
  76. data/examples/README +0 -7
@@ -0,0 +1,74 @@
1
+
2
+ Behaviour.register({
3
+ '.naction_remove_hash': function(el) {
4
+ el.onclick = function(){
5
+ //remove a hash list item
6
+ parentEl = el
7
+ while(parentEl.tagName != 'LI'){
8
+ parentEl = parentEl.parentNode
9
+ }
10
+ Element.remove(parentEl);
11
+ return false;
12
+ }
13
+ },
14
+ '.naction_add_hash': function(el) {
15
+ el.onclick = function() {
16
+ //add a hash list item
17
+ ulEl = $(el.id+'_list');
18
+ newkey = $(el.id+'_key').value;
19
+ if(newkey.length > 0){
20
+ new Ajax.Request('../hash_tag_item/'+el.id+'/'+newkey+'/'+$(el.id+'_value').value, {
21
+ asynchronous:true,
22
+ onComplete: function(t){
23
+ ulEl.innerHTML += t.responseText;
24
+ Behaviour.apply();
25
+ }
26
+ });
27
+ }
28
+ return false;
29
+ }
30
+ },
31
+ '.naction_remove_rel': function(el) {
32
+ el.onclick = function() {
33
+ //remove rel from list and add option to pulldown
34
+ parentEl = el
35
+ while(parentEl.tagName != 'LI'){
36
+ parentEl = parentEl.parentNode
37
+ }
38
+ rel_oid = parentEl.getElementsByTagName('input')[0].value;
39
+ rel_name = parentEl.getElementsByTagName('input')[0].name.substr(0, parentEl.getElementsByTagName('input')[0].name.length-2);
40
+ rel_display = parentEl.getElementsByTagName('span')[0].innerHTML;
41
+ newOpt = document.createElement('option');
42
+ newOpt.value = rel_oid;
43
+ newOpt.appendChild(document.createTextNode(rel_display));
44
+ $(rel_name+'_selector').appendChild(newOpt);
45
+ Element.remove(parentEl);
46
+ return false;
47
+ }
48
+ },
49
+ '.naction_add_rel': function(el) {
50
+ el.onclick = function() {
51
+ //add a relation list item and remove item from options
52
+ ulEl = $(el.id+'_list');
53
+ selectorEl = $(el.id+'_selector');
54
+ // TODO: moz allows a select box with 0 options in it? ... disabled must be set with js
55
+ selectedElement = selectorEl.options[selectorEl.selectedIndex]
56
+ sURL = '../relation_item/'+el.id+'/'+selectedElement.innerHTML+'/'+selectedElement.value
57
+ new Ajax.Request(sURL, {
58
+ asynchronous:true,
59
+ onComplete: function(t){
60
+ ulEl.innerHTML += t.responseText;
61
+ Behaviour.apply();
62
+ },
63
+ onFailure: function(t){
64
+ // TODO: put the rel back in the option list
65
+ // chrisfarms: UNTESTED... selectedEl might not be availible?
66
+ // prob wont work in ffox. since moz doesnt like innerHTML used like this with options
67
+ selectorEl.innerHTML += '<option value="'+selectedElement.value+'">'+selectedElement.innerHTML+'</option>'
68
+ }
69
+ });
70
+ Element.remove(selectedElement);
71
+ return false;
72
+ }
73
+ }
74
+ });
@@ -0,0 +1,44 @@
1
+ // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining
4
+ // a copy of this software and associated documentation files (the
5
+ // "Software"), to deal in the Software without restriction, including
6
+ // without limitation the rights to use, copy, modify, merge, publish,
7
+ // distribute, sublicense, and/or sell copies of the Software, and to
8
+ // permit persons to whom the Software is furnished to do so, subject to
9
+ // the following conditions:
10
+ //
11
+ // The above copyright notice and this permission notice shall be
12
+ // included in all copies or substantial portions of the Software.
13
+ //
14
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ var Scriptaculous = {
23
+ Version: '1.5_rc3',
24
+ require: function(libraryName) {
25
+ // inserting via DOM fails in Safari 2.0, so brute force approach
26
+ document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
27
+ },
28
+ load: function() {
29
+ var scriptTags = document.getElementsByTagName("script");
30
+ for(var i=0;i<scriptTags.length;i++) {
31
+ if(scriptTags[i].src && scriptTags[i].src.match(/scriptaculous\.js(\?.*)?$/)) {
32
+ var path = scriptTags[i].src.replace(/scriptaculous\.js(\?.*)?$/,'');
33
+ this.require(path + 'util.js');
34
+ this.require(path + 'effects.js');
35
+ this.require(path + 'dragdrop.js');
36
+ this.require(path + 'controls.js');
37
+ this.require(path + 'slider.js');
38
+ break;
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ Scriptaculous.load();
@@ -0,0 +1,548 @@
1
+ // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
+ //
3
+ // See scriptaculous.js for full license.
4
+
5
+
6
+ Object.debug = function(obj) {
7
+ var info = [];
8
+
9
+ if(typeof obj in ["string","number"]) {
10
+ return obj;
11
+ } else {
12
+ for(property in obj)
13
+ if(typeof obj[property]!="function")
14
+ info.push(property + ' => ' +
15
+ (typeof obj[property] == "string" ?
16
+ '"' + obj[property] + '"' :
17
+ obj[property]));
18
+ }
19
+
20
+ return ("'" + obj + "' #" + typeof obj +
21
+ ": {" + info.join(", ") + "}");
22
+ }
23
+
24
+
25
+ String.prototype.toArray = function() {
26
+ var results = [];
27
+ for (var i = 0; i < this.length; i++)
28
+ results.push(this.charAt(i));
29
+ return results;
30
+ }
31
+
32
+ /*--------------------------------------------------------------------------*/
33
+
34
+ var Builder = {
35
+ NODEMAP: {
36
+ AREA: 'map',
37
+ CAPTION: 'table',
38
+ COL: 'table',
39
+ COLGROUP: 'table',
40
+ LEGEND: 'fieldset',
41
+ OPTGROUP: 'select',
42
+ OPTION: 'select',
43
+ PARAM: 'object',
44
+ TBODY: 'table',
45
+ TD: 'table',
46
+ TFOOT: 'table',
47
+ TH: 'table',
48
+ THEAD: 'table',
49
+ TR: 'table'
50
+ },
51
+ // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
52
+ // due to a Firefox bug
53
+ node: function(elementName) {
54
+ elementName = elementName.toUpperCase();
55
+
56
+ // try innerHTML approach
57
+ var parentTag = this.NODEMAP[elementName] || 'div';
58
+ var parentElement = document.createElement(parentTag);
59
+ parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
60
+ var element = parentElement.firstChild || null;
61
+
62
+ // see if browser added wrapping tags
63
+ if(element && (element.tagName != elementName))
64
+ element = element.getElementsByTagName(elementName)[0];
65
+
66
+ // fallback to createElement approach
67
+ if(!element) element = document.createElement(elementName);
68
+
69
+ // abort if nothing could be created
70
+ if(!element) return;
71
+
72
+ // attributes (or text)
73
+ if(arguments[1])
74
+ if(this._isStringOrNumber(arguments[1]) ||
75
+ (arguments[1] instanceof Array)) {
76
+ this._children(element, arguments[1]);
77
+ } else {
78
+ var attrs = this._attributes(arguments[1]);
79
+ if(attrs.length) {
80
+ parentElement.innerHTML = "<" +elementName + " " +
81
+ attrs + "></" + elementName + ">";
82
+ element = parentElement.firstChild || null;
83
+ // workaround firefox 1.0.X bug
84
+ if(!element) {
85
+ element = document.createElement(elementName);
86
+ for(attr in arguments[1])
87
+ element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
88
+ }
89
+ if(element.tagName != elementName)
90
+ element = parentElement.getElementsByTagName(elementName)[0];
91
+ }
92
+ }
93
+
94
+ // text, or array of children
95
+ if(arguments[2])
96
+ this._children(element, arguments[2]);
97
+
98
+ return element;
99
+ },
100
+ _text: function(text) {
101
+ return document.createTextNode(text);
102
+ },
103
+ _attributes: function(attributes) {
104
+ var attrs = [];
105
+ for(attribute in attributes)
106
+ attrs.push((attribute=='className' ? 'class' : attribute) +
107
+ '="' + attributes[attribute].toString().escapeHTML() + '"');
108
+ return attrs.join(" ");
109
+ },
110
+ _children: function(element, children) {
111
+ if(typeof children=='object') { // array can hold nodes and text
112
+ children.flatten().each( function(e) {
113
+ if(typeof e=='object')
114
+ element.appendChild(e)
115
+ else
116
+ if(Builder._isStringOrNumber(e))
117
+ element.appendChild(Builder._text(e));
118
+ });
119
+ } else
120
+ if(Builder._isStringOrNumber(children))
121
+ element.appendChild(Builder._text(children));
122
+ },
123
+ _isStringOrNumber: function(param) {
124
+ return(typeof param=='string' || typeof param=='number');
125
+ }
126
+ }
127
+
128
+ /* ------------- element ext -------------- */
129
+
130
+ // adapted from http://dhtmlkitchen.com/learn/js/setstyle/index4.jsp
131
+ // note: Safari return null on elements with display:none; see http://bugzilla.opendarwin.org/show_bug.cgi?id=4125
132
+ // instead of "auto" values returns null so it's easier to use with || constructs
133
+
134
+ String.prototype.camelize = function() {
135
+ var oStringList = this.split('-');
136
+ if(oStringList.length == 1)
137
+ return oStringList[0];
138
+ var ret = this.indexOf("-") == 0 ?
139
+ oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) : oStringList[0];
140
+ for(var i = 1, len = oStringList.length; i < len; i++){
141
+ var s = oStringList[i];
142
+ ret += s.charAt(0).toUpperCase() + s.substring(1)
143
+ }
144
+ return ret;
145
+ }
146
+
147
+ Element.getStyle = function(element, style) {
148
+ element = $(element);
149
+ var value = element.style[style.camelize()];
150
+ if(!value)
151
+ if(document.defaultView && document.defaultView.getComputedStyle) {
152
+ var css = document.defaultView.getComputedStyle(element, null);
153
+ value = (css!=null) ? css.getPropertyValue(style) : null;
154
+ } else if(element.currentStyle) {
155
+ value = element.currentStyle[style.camelize()];
156
+ }
157
+
158
+ // If top, left, bottom, or right values have been queried, return "auto" for consistency resaons
159
+ // if position is "static", as Opera (and others?) returns the pixel values relative to root element
160
+ // (or positioning context?)
161
+ if (window.opera && (style == "left" || style == "top" || style == "right" || style == "bottom"))
162
+ if (Element.getStyle(element, "position") == "static") value = "auto";
163
+
164
+ if(value=='auto') value = null;
165
+ return value;
166
+ }
167
+
168
+ // converts rgb() and #xxx to #xxxxxx format,
169
+ // returns self (or first argument) if not convertable
170
+ String.prototype.parseColor = function() {
171
+ color = "#";
172
+ if(this.slice(0,4) == "rgb(") {
173
+ var cols = this.slice(4,this.length-1).split(',');
174
+ var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
175
+ } else {
176
+ if(this.slice(0,1) == '#') {
177
+ if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
178
+ if(this.length==7) color = this.toLowerCase();
179
+ }
180
+ }
181
+ return(color.length==7 ? color : (arguments[0] || this));
182
+ }
183
+
184
+ Element.makePositioned = function(element) {
185
+ element = $(element);
186
+ var pos = Element.getStyle(element, 'position');
187
+ if(pos =='static' || !pos) {
188
+ element._madePositioned = true;
189
+ element.style.position = "relative";
190
+ // Opera returns the offset relative to the positioning context, when an element is position relative
191
+ // but top and left have not been defined
192
+ if (window.opera){
193
+ element.style.top = 0;
194
+ element.style.left = 0;
195
+ }
196
+ }
197
+ }
198
+
199
+ Element.undoPositioned = function(element) {
200
+ element = $(element);
201
+ if(typeof element._madePositioned != "undefined"){
202
+ element._madePositioned = undefined;
203
+ element.style.position = "";
204
+ element.style.top = "";
205
+ element.style.left = "";
206
+ element.style.bottom = "";
207
+ element.style.right = "";
208
+ }
209
+ }
210
+
211
+ Element.makeClipping = function(element) {
212
+ element = $(element);
213
+ if (typeof element._overflow != 'undefined') return;
214
+ element._overflow = element.style.overflow;
215
+ if((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') element.style.overflow = 'hidden';
216
+ }
217
+
218
+ Element.undoClipping = function(element) {
219
+ element = $(element);
220
+ if (typeof element._overflow == 'undefined') return;
221
+ element.style.overflow = element._overflow;
222
+ element._overflow = undefined;
223
+ }
224
+
225
+ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
226
+ var children = $(element).childNodes;
227
+ var text = "";
228
+ var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i");
229
+
230
+ for (var i = 0; i < children.length; i++) {
231
+ if(children[i].nodeType==3) {
232
+ text+=children[i].nodeValue;
233
+ } else {
234
+ if((!children[i].className.match(classtest)) && children[i].hasChildNodes())
235
+ text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
236
+ }
237
+ }
238
+
239
+ return text;
240
+ }
241
+
242
+ Element.setContentZoom = function(element, percent) {
243
+ element = $(element);
244
+ element.style.fontSize = (percent/100) + "em";
245
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
246
+ }
247
+
248
+ Element.getOpacity = function(element){
249
+ var opacity;
250
+ if (opacity = Element.getStyle(element, "opacity"))
251
+ return parseFloat(opacity);
252
+ if (opacity = (Element.getStyle(element, "filter") || '').match(/alpha\(opacity=(.*)\)/))
253
+ if(opacity[1]) return parseFloat(opacity[1]) / 100;
254
+ return 1.0;
255
+ }
256
+
257
+ Element.setOpacity = function(element, value){
258
+ element= $(element);
259
+ var els = element.style;
260
+ if (value == 1){
261
+ els.opacity = '0.999999';
262
+ if(/MSIE/.test(navigator.userAgent))
263
+ els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'');
264
+ } else {
265
+ if(value < 0.00001) value = 0;
266
+ els.opacity = value;
267
+ if(/MSIE/.test(navigator.userAgent))
268
+ els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
269
+ "alpha(opacity="+value*100+")";
270
+ }
271
+ }
272
+
273
+ Element.getInlineOpacity = function(element){
274
+ element= $(element);
275
+ var op;
276
+ op = element.style.opacity;
277
+ if (typeof op != "undefined" && op != "") return op;
278
+ return "";
279
+ }
280
+
281
+ Element.setInlineOpacity = function(element, value){
282
+ element= $(element);
283
+ var els = element.style;
284
+ els.opacity = value;
285
+ }
286
+
287
+ Element.getDimensions = function(element){
288
+ element = $(element);
289
+ // All *Width and *Height properties give 0 on elements with display "none",
290
+ // so enable the element temporarily
291
+ if (Element.getStyle(element,'display') == "none"){
292
+ var els = element.style;
293
+ var originalVisibility = els.visibility;
294
+ var originalPosition = els.position;
295
+ els.visibility = "hidden";
296
+ els.position = "absolute";
297
+ els.display = "";
298
+ var originalWidth = element.clientWidth;
299
+ var originalHeight = element.clientHeight;
300
+ els.display = "none";
301
+ els.position = originalPosition;
302
+ els.visibility = originalVisibility;
303
+ return {width: originalWidth, height: originalHeight};
304
+ }
305
+
306
+ return {width: element.offsetWidth, height: element.offsetHeight};
307
+ }
308
+
309
+ /*--------------------------------------------------------------------------*/
310
+
311
+ Position.positionedOffset = function(element) {
312
+ var valueT = 0, valueL = 0;
313
+ do {
314
+ valueT += element.offsetTop || 0;
315
+ valueL += element.offsetLeft || 0;
316
+ element = element.offsetParent;
317
+ if (element) {
318
+ p = Element.getStyle(element,'position');
319
+ if(p == 'relative' || p == 'absolute') break;
320
+ }
321
+ } while (element);
322
+ return [valueL, valueT];
323
+ }
324
+
325
+ // Safari returns margins on body which is incorrect if the child is absolutely positioned.
326
+ // for performance reasons, we create a specialized version of Position.cumulativeOffset for
327
+ // KHTML/WebKit only
328
+
329
+ if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
330
+ Position.cumulativeOffset = function(element) {
331
+ var valueT = 0, valueL = 0;
332
+ do {
333
+ valueT += element.offsetTop || 0;
334
+ valueL += element.offsetLeft || 0;
335
+
336
+ if (element.offsetParent==document.body)
337
+ if (Element.getStyle(element,'position')=='absolute') break;
338
+
339
+ element = element.offsetParent;
340
+ } while (element);
341
+ return [valueL, valueT];
342
+ }
343
+ }
344
+
345
+ Position.page = function(forElement) {
346
+ var valueT = 0, valueL = 0;
347
+
348
+ var element = forElement;
349
+ do {
350
+ valueT += element.offsetTop || 0;
351
+ valueL += element.offsetLeft || 0;
352
+
353
+ // Safari fix
354
+ if (element.offsetParent==document.body)
355
+ if (Element.getStyle(element,'position')=='absolute') break;
356
+
357
+ } while (element = element.offsetParent);
358
+
359
+ element = forElement;
360
+ do {
361
+ valueT -= element.scrollTop || 0;
362
+ valueL -= element.scrollLeft || 0;
363
+ } while (element = element.parentNode);
364
+
365
+ return [valueL, valueT];
366
+ }
367
+
368
+ // elements with display:none don't return an offsetParent,
369
+ // fall back to manual calculation
370
+ Position.offsetParent = function(element) {
371
+ if(element.offsetParent) return element.offsetParent;
372
+ if(element == document.body) return element;
373
+
374
+ while ((element = element.parentNode) && element != document.body)
375
+ if (Element.getStyle(element,'position')!='static')
376
+ return element;
377
+
378
+ return document.body;
379
+ }
380
+
381
+ Position.clone = function(source, target) {
382
+ var options = Object.extend({
383
+ setLeft: true,
384
+ setTop: true,
385
+ setWidth: true,
386
+ setHeight: true,
387
+ offsetTop: 0,
388
+ offsetLeft: 0
389
+ }, arguments[2] || {})
390
+
391
+ // find page position of source
392
+ source = $(source);
393
+ var p = Position.page(source);
394
+
395
+ // find coordinate system to use
396
+ target = $(target);
397
+ var delta = [0, 0];
398
+ var parent = null;
399
+ // delta [0,0] will do fine with position: fixed elements,
400
+ // position:absolute needs offsetParent deltas
401
+ if (Element.getStyle(target,'position') == 'absolute') {
402
+ parent = Position.offsetParent(target);
403
+ delta = Position.page(parent);
404
+ }
405
+
406
+ // correct by body offsets (fixes Safari)
407
+ if (parent==document.body) {
408
+ delta[0] -= document.body.offsetLeft;
409
+ delta[1] -= document.body.offsetTop;
410
+ }
411
+
412
+ // set position
413
+ if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + "px";
414
+ if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + "px";
415
+ if(options.setWidth) target.style.width = source.offsetWidth + "px";
416
+ if(options.setHeight) target.style.height = source.offsetHeight + "px";
417
+ }
418
+
419
+ Position.absolutize = function(element) {
420
+ element = $(element);
421
+ if(element.style.position=='absolute') return;
422
+ Position.prepare();
423
+
424
+ var offsets = Position.positionedOffset(element);
425
+ var top = offsets[1];
426
+ var left = offsets[0];
427
+ var width = element.clientWidth;
428
+ var height = element.clientHeight;
429
+
430
+ element._originalLeft = left - parseFloat(element.style.left || 0);
431
+ element._originalTop = top - parseFloat(element.style.top || 0);
432
+ element._originalWidth = element.style.width;
433
+ element._originalHeight = element.style.height;
434
+
435
+ element.style.position = 'absolute';
436
+ element.style.top = top + 'px';;
437
+ element.style.left = left + 'px';;
438
+ element.style.width = width + 'px';;
439
+ element.style.height = height + 'px';;
440
+ }
441
+
442
+ Position.relativize = function(element) {
443
+ element = $(element);
444
+ if(element.style.position=='relative') return;
445
+ Position.prepare();
446
+
447
+ element.style.position = 'relative';
448
+ var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
449
+ var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
450
+
451
+ element.style.top = top + 'px';
452
+ element.style.left = left + 'px';
453
+ element.style.height = element._originalHeight;
454
+ element.style.width = element._originalWidth;
455
+ }
456
+
457
+ /*--------------------------------------------------------------------------*/
458
+
459
+ Element.Class = {
460
+ // Element.toggleClass(element, className) toggles the class being on/off
461
+ // Element.toggleClass(element, className1, className2) toggles between both classes,
462
+ // defaulting to className1 if neither exist
463
+ toggle: function(element, className) {
464
+ if(Element.Class.has(element, className)) {
465
+ Element.Class.remove(element, className);
466
+ if(arguments.length == 3) Element.Class.add(element, arguments[2]);
467
+ } else {
468
+ Element.Class.add(element, className);
469
+ if(arguments.length == 3) Element.Class.remove(element, arguments[2]);
470
+ }
471
+ },
472
+
473
+ // gets space-delimited classnames of an element as an array
474
+ get: function(element) {
475
+ return $(element).className.split(' ');
476
+ },
477
+
478
+ // functions adapted from original functions by Gavin Kistner
479
+ remove: function(element) {
480
+ element = $(element);
481
+ var removeClasses = arguments;
482
+ $R(1,arguments.length-1).each( function(index) {
483
+ element.className =
484
+ element.className.split(' ').reject(
485
+ function(klass) { return (klass == removeClasses[index]) } ).join(' ');
486
+ });
487
+ },
488
+
489
+ add: function(element) {
490
+ element = $(element);
491
+ for(var i = 1; i < arguments.length; i++) {
492
+ Element.Class.remove(element, arguments[i]);
493
+ element.className += (element.className.length > 0 ? ' ' : '') + arguments[i];
494
+ }
495
+ },
496
+
497
+ // returns true if all given classes exist in said element
498
+ has: function(element) {
499
+ element = $(element);
500
+ if(!element || !element.className) return false;
501
+ var regEx;
502
+ for(var i = 1; i < arguments.length; i++) {
503
+ if((typeof arguments[i] == 'object') &&
504
+ (arguments[i].constructor == Array)) {
505
+ for(var j = 0; j < arguments[i].length; j++) {
506
+ regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
507
+ if(!regEx.test(element.className)) return false;
508
+ }
509
+ } else {
510
+ regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
511
+ if(!regEx.test(element.className)) return false;
512
+ }
513
+ }
514
+ return true;
515
+ },
516
+
517
+ // expects arrays of strings and/or strings as optional paramters
518
+ // Element.Class.has_any(element, ['classA','classB','classC'], 'classD')
519
+ has_any: function(element) {
520
+ element = $(element);
521
+ if(!element || !element.className) return false;
522
+ var regEx;
523
+ for(var i = 1; i < arguments.length; i++) {
524
+ if((typeof arguments[i] == 'object') &&
525
+ (arguments[i].constructor == Array)) {
526
+ for(var j = 0; j < arguments[i].length; j++) {
527
+ regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
528
+ if(regEx.test(element.className)) return true;
529
+ }
530
+ } else {
531
+ regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
532
+ if(regEx.test(element.className)) return true;
533
+ }
534
+ }
535
+ return false;
536
+ },
537
+
538
+ childrenWith: function(element, className) {
539
+ var children = $(element).getElementsByTagName('*');
540
+ var elements = new Array();
541
+
542
+ for (var i = 0; i < children.length; i++)
543
+ if (Element.Class.has(children[i], className))
544
+ elements.push(children[i]);
545
+
546
+ return elements;
547
+ }
548
+ }