ruby-openid 2.2.3 → 2.3.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 (99) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +1 -2
  4. data/examples/rails_openid/Gemfile +41 -0
  5. data/examples/rails_openid/README.rdoc +261 -0
  6. data/examples/rails_openid/Rakefile +4 -7
  7. data/examples/rails_openid/app/assets/images/rails.png +0 -0
  8. data/examples/rails_openid/app/assets/javascripts/application.js +15 -0
  9. data/examples/rails_openid/app/assets/stylesheets/application.css +13 -0
  10. data/examples/rails_openid/app/controllers/application_controller.rb +3 -0
  11. data/examples/rails_openid/app/controllers/consumer_controller.rb +1 -0
  12. data/examples/rails_openid/app/helpers/application_helper.rb +0 -1
  13. data/examples/rails_openid/app/views/consumer/{index.rhtml → index.html.erb} +0 -0
  14. data/examples/rails_openid/app/views/layouts/{server.rhtml → server.html.erb} +4 -2
  15. data/examples/rails_openid/app/views/login/{index.rhtml → index.html.erb} +0 -0
  16. data/examples/rails_openid/app/views/server/{decide.rhtml → decide.html.erb} +1 -0
  17. data/examples/rails_openid/config.ru +4 -0
  18. data/examples/rails_openid/config/application.rb +62 -0
  19. data/examples/rails_openid/config/boot.rb +4 -17
  20. data/examples/rails_openid/config/database.yml +15 -64
  21. data/examples/rails_openid/config/environment.rb +4 -53
  22. data/examples/rails_openid/config/environments/development.rb +32 -14
  23. data/examples/rails_openid/config/environments/production.rb +61 -13
  24. data/examples/rails_openid/config/environments/test.rb +33 -15
  25. data/examples/rails_openid/config/initializers/backtrace_silencers.rb +7 -0
  26. data/examples/rails_openid/config/initializers/inflections.rb +15 -0
  27. data/examples/rails_openid/config/initializers/mime_types.rb +5 -0
  28. data/examples/rails_openid/config/initializers/rails_root.rb +1 -0
  29. data/examples/rails_openid/config/initializers/secret_token.rb +7 -0
  30. data/examples/rails_openid/config/initializers/session_store.rb +8 -0
  31. data/examples/rails_openid/config/initializers/wrap_parameters.rb +14 -0
  32. data/examples/rails_openid/config/locales/en.yml +5 -0
  33. data/examples/rails_openid/config/routes.rb +65 -18
  34. data/examples/rails_openid/db/development.sqlite3 +0 -0
  35. data/examples/rails_openid/db/seeds.rb +7 -0
  36. data/examples/rails_openid/doc/README_FOR_APP +1 -1
  37. data/examples/rails_openid/log/development.log +2052 -0
  38. data/examples/rails_openid/public/404.html +23 -5
  39. data/examples/rails_openid/public/422.html +26 -0
  40. data/examples/rails_openid/public/500.html +22 -5
  41. data/examples/rails_openid/public/javascripts/application.js +2 -0
  42. data/examples/rails_openid/public/javascripts/controls.js +586 -373
  43. data/examples/rails_openid/public/javascripts/dragdrop.js +575 -186
  44. data/examples/rails_openid/public/javascripts/effects.js +763 -489
  45. data/examples/rails_openid/public/javascripts/prototype.js +3420 -885
  46. data/examples/rails_openid/public/robots.txt +5 -1
  47. data/examples/rails_openid/script/rails +6 -0
  48. data/examples/rails_openid/test/performance/browsing_test.rb +12 -0
  49. data/examples/rails_openid/test/test_helper.rb +7 -22
  50. data/lib/openid/association.rb +1 -1
  51. data/lib/openid/consumer/checkid_request.rb +1 -1
  52. data/lib/openid/consumer/discovery.rb +1 -1
  53. data/lib/openid/consumer/html_parse.rb +3 -1
  54. data/lib/openid/consumer/idres.rb +1 -1
  55. data/lib/openid/extensions/ax.rb +2 -3
  56. data/lib/openid/extensions/ui.rb +3 -3
  57. data/lib/openid/extras.rb +2 -2
  58. data/lib/openid/server.rb +2 -2
  59. data/lib/openid/store/memory.rb +1 -2
  60. data/lib/openid/store/nonce.rb +1 -1
  61. data/lib/openid/trustroot.rb +1 -1
  62. data/lib/openid/util.rb +2 -2
  63. data/lib/openid/version.rb +1 -1
  64. data/lib/openid/yadis/xrds.rb +1 -1
  65. data/test/test_accept.rb +20 -21
  66. data/test/test_association.rb +4 -8
  67. data/test/test_associationmanager.rb +1 -1
  68. data/test/test_ax.rb +0 -1
  69. data/test/test_checkid_request.rb +7 -8
  70. data/test/test_dh.rb +1 -1
  71. data/test/test_discover.rb +7 -8
  72. data/test/test_extension.rb +1 -1
  73. data/test/test_fetchers.rb +7 -11
  74. data/test/test_filters.rb +0 -4
  75. data/test/test_idres.rb +5 -5
  76. data/test/test_kvpost.rb +0 -1
  77. data/test/test_message.rb +10 -11
  78. data/test/test_parsehtml.rb +0 -1
  79. data/test/test_server.rb +11 -30
  80. data/test/test_stores.rb +2 -2
  81. data/test/test_trustroot.rb +1 -1
  82. data/test/test_urinorm.rb +1 -1
  83. data/test/test_xrds.rb +1 -1
  84. data/test/test_yadis_discovery.rb +0 -2
  85. metadata +34 -33
  86. data/examples/rails_openid/app/controllers/application.rb +0 -4
  87. data/examples/rails_openid/script/about +0 -3
  88. data/examples/rails_openid/script/breakpointer +0 -3
  89. data/examples/rails_openid/script/console +0 -3
  90. data/examples/rails_openid/script/destroy +0 -3
  91. data/examples/rails_openid/script/generate +0 -3
  92. data/examples/rails_openid/script/performance/benchmarker +0 -3
  93. data/examples/rails_openid/script/performance/profiler +0 -3
  94. data/examples/rails_openid/script/plugin +0 -3
  95. data/examples/rails_openid/script/process/reaper +0 -3
  96. data/examples/rails_openid/script/process/spawner +0 -3
  97. data/examples/rails_openid/script/process/spinner +0 -3
  98. data/examples/rails_openid/script/runner +0 -3
  99. data/examples/rails_openid/script/server +0 -3
@@ -1,8 +1,11 @@
1
- // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
- //
3
- // See scriptaculous.js for full license.
1
+ // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
+ // (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
3
+ //
4
+ // script.aculo.us is freely distributable under the terms of an MIT-style license.
5
+ // For details, see the script.aculo.us web site: http://script.aculo.us/
4
6
 
5
- /*--------------------------------------------------------------------------*/
7
+ if(Object.isUndefined(Effect))
8
+ throw("dragdrop.js requires including script.aculo.us' effects.js library");
6
9
 
7
10
  var Droppables = {
8
11
  drops: [],
@@ -15,21 +18,21 @@ var Droppables = {
15
18
  element = $(element);
16
19
  var options = Object.extend({
17
20
  greedy: true,
18
- hoverclass: null
19
- }, arguments[1] || {});
21
+ hoverclass: null,
22
+ tree: false
23
+ }, arguments[1] || { });
20
24
 
21
25
  // cache containers
22
26
  if(options.containment) {
23
27
  options._containers = [];
24
28
  var containment = options.containment;
25
- if((typeof containment == 'object') &&
26
- (containment.constructor == Array)) {
29
+ if(Object.isArray(containment)) {
27
30
  containment.each( function(c) { options._containers.push($(c)) });
28
31
  } else {
29
32
  options._containers.push($(containment));
30
33
  }
31
34
  }
32
-
35
+
33
36
  if(options.accept) options.accept = [options.accept].flatten();
34
37
 
35
38
  Element.makePositioned(element); // fix IE
@@ -38,9 +41,24 @@ var Droppables = {
38
41
  this.drops.push(options);
39
42
  },
40
43
 
44
+ findDeepestChild: function(drops) {
45
+ deepest = drops[0];
46
+
47
+ for (i = 1; i < drops.length; ++i)
48
+ if (Element.isParent(drops[i].element, deepest.element))
49
+ deepest = drops[i];
50
+
51
+ return deepest;
52
+ },
53
+
41
54
  isContained: function(element, drop) {
42
- var parentNode = element.parentNode;
43
- return drop._containers.detect(function(c) { return parentNode == c });
55
+ var containmentNode;
56
+ if(drop.tree) {
57
+ containmentNode = element.treeNode;
58
+ } else {
59
+ containmentNode = element.parentNode;
60
+ }
61
+ return drop._containers.detect(function(c) { return containmentNode == c });
44
62
  },
45
63
 
46
64
  isAffected: function(point, element, drop) {
@@ -49,7 +67,7 @@ var Droppables = {
49
67
  ((!drop._containers) ||
50
68
  this.isContained(element, drop)) &&
51
69
  ((!drop.accept) ||
52
- (Element.classNames(element).detect(
70
+ (Element.classNames(element).detect(
53
71
  function(v) { return drop.accept.include(v) } ) )) &&
54
72
  Position.within(drop.element, point[0], point[1]) );
55
73
  },
@@ -68,18 +86,24 @@ var Droppables = {
68
86
 
69
87
  show: function(point, element) {
70
88
  if(!this.drops.length) return;
71
-
72
- if(this.last_active) this.deactivate(this.last_active);
89
+ var drop, affected = [];
90
+
73
91
  this.drops.each( function(drop) {
74
- if(Droppables.isAffected(point, element, drop)) {
75
- if(drop.onHover)
76
- drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
77
- if(drop.greedy) {
78
- Droppables.activate(drop);
79
- throw $break;
80
- }
81
- }
92
+ if(Droppables.isAffected(point, element, drop))
93
+ affected.push(drop);
82
94
  });
95
+
96
+ if(affected.length>0)
97
+ drop = Droppables.findDeepestChild(affected);
98
+
99
+ if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
100
+ if (drop) {
101
+ Position.within(drop.element, point[0], point[1]);
102
+ if(drop.onHover)
103
+ drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
104
+
105
+ if (drop != this.last_active) Droppables.activate(drop);
106
+ }
83
107
  },
84
108
 
85
109
  fire: function(event, element) {
@@ -87,33 +111,35 @@ var Droppables = {
87
111
  Position.prepare();
88
112
 
89
113
  if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
90
- if (this.last_active.onDrop)
114
+ if (this.last_active.onDrop) {
91
115
  this.last_active.onDrop(element, this.last_active.element, event);
116
+ return true;
117
+ }
92
118
  },
93
119
 
94
120
  reset: function() {
95
121
  if(this.last_active)
96
122
  this.deactivate(this.last_active);
97
123
  }
98
- }
124
+ };
99
125
 
100
126
  var Draggables = {
101
127
  drags: [],
102
128
  observers: [],
103
-
129
+
104
130
  register: function(draggable) {
105
131
  if(this.drags.length == 0) {
106
132
  this.eventMouseUp = this.endDrag.bindAsEventListener(this);
107
133
  this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
108
134
  this.eventKeypress = this.keyPress.bindAsEventListener(this);
109
-
135
+
110
136
  Event.observe(document, "mouseup", this.eventMouseUp);
111
137
  Event.observe(document, "mousemove", this.eventMouseMove);
112
138
  Event.observe(document, "keypress", this.eventKeypress);
113
139
  }
114
140
  this.drags.push(draggable);
115
141
  },
116
-
142
+
117
143
  unregister: function(draggable) {
118
144
  this.drags = this.drags.reject(function(d) { return d==draggable });
119
145
  if(this.drags.length == 0) {
@@ -122,16 +148,24 @@ var Draggables = {
122
148
  Event.stopObserving(document, "keypress", this.eventKeypress);
123
149
  }
124
150
  },
125
-
151
+
126
152
  activate: function(draggable) {
127
- window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
128
- this.activeDraggable = draggable;
153
+ if(draggable.options.delay) {
154
+ this._timeout = setTimeout(function() {
155
+ Draggables._timeout = null;
156
+ window.focus();
157
+ Draggables.activeDraggable = draggable;
158
+ }.bind(this), draggable.options.delay);
159
+ } else {
160
+ window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
161
+ this.activeDraggable = draggable;
162
+ }
129
163
  },
130
-
131
- deactivate: function(draggbale) {
164
+
165
+ deactivate: function() {
132
166
  this.activeDraggable = null;
133
167
  },
134
-
168
+
135
169
  updateDrag: function(event) {
136
170
  if(!this.activeDraggable) return;
137
171
  var pointer = [Event.pointerX(event), Event.pointerY(event)];
@@ -139,37 +173,44 @@ var Draggables = {
139
173
  // the same coordinates, prevent needless redrawing (moz bug?)
140
174
  if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
141
175
  this._lastPointer = pointer;
176
+
142
177
  this.activeDraggable.updateDrag(event, pointer);
143
178
  },
144
-
179
+
145
180
  endDrag: function(event) {
181
+ if(this._timeout) {
182
+ clearTimeout(this._timeout);
183
+ this._timeout = null;
184
+ }
146
185
  if(!this.activeDraggable) return;
147
186
  this._lastPointer = null;
148
187
  this.activeDraggable.endDrag(event);
188
+ this.activeDraggable = null;
149
189
  },
150
-
190
+
151
191
  keyPress: function(event) {
152
192
  if(this.activeDraggable)
153
193
  this.activeDraggable.keyPress(event);
154
194
  },
155
-
195
+
156
196
  addObserver: function(observer) {
157
197
  this.observers.push(observer);
158
198
  this._cacheObserverCallbacks();
159
199
  },
160
-
200
+
161
201
  removeObserver: function(element) { // element instead of observer fixes mem leaks
162
202
  this.observers = this.observers.reject( function(o) { return o.element==element });
163
203
  this._cacheObserverCallbacks();
164
204
  },
165
-
205
+
166
206
  notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
167
207
  if(this[eventName+'Count'] > 0)
168
208
  this.observers.each( function(o) {
169
209
  if(o[eventName]) o[eventName](eventName, draggable, event);
170
210
  });
211
+ if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
171
212
  },
172
-
213
+
173
214
  _cacheObserverCallbacks: function() {
174
215
  ['onStart','onEnd','onDrag'].each( function(eventName) {
175
216
  Draggables[eventName+'Count'] = Draggables.observers.select(
@@ -177,134 +218,214 @@ var Draggables = {
177
218
  ).length;
178
219
  });
179
220
  }
180
- }
221
+ };
181
222
 
182
223
  /*--------------------------------------------------------------------------*/
183
224
 
184
- var Draggable = Class.create();
185
- Draggable.prototype = {
225
+ var Draggable = Class.create({
186
226
  initialize: function(element) {
187
- var options = Object.extend({
227
+ var defaults = {
188
228
  handle: false,
189
- starteffect: function(element) {
190
- new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
191
- },
192
229
  reverteffect: function(element, top_offset, left_offset) {
193
230
  var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
194
- element._revert = new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur});
231
+ new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
232
+ queue: {scope:'_draggable', position:'end'}
233
+ });
195
234
  },
196
- endeffect: function(element) {
197
- new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
235
+ endeffect: function(element) {
236
+ var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
237
+ new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
238
+ queue: {scope:'_draggable', position:'end'},
239
+ afterFinish: function(){
240
+ Draggable._dragging[element] = false
241
+ }
242
+ });
198
243
  },
199
244
  zindex: 1000,
200
245
  revert: false,
201
- snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] }
202
- }, arguments[1] || {});
246
+ quiet: false,
247
+ scroll: false,
248
+ scrollSensitivity: 20,
249
+ scrollSpeed: 15,
250
+ snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
251
+ delay: 0
252
+ };
253
+
254
+ if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
255
+ Object.extend(defaults, {
256
+ starteffect: function(element) {
257
+ element._opacity = Element.getOpacity(element);
258
+ Draggable._dragging[element] = true;
259
+ new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
260
+ }
261
+ });
262
+
263
+ var options = Object.extend(defaults, arguments[1] || { });
203
264
 
204
265
  this.element = $(element);
205
-
206
- if(options.handle && (typeof options.handle == 'string'))
207
- this.handle = Element.childrenWithClassName(this.element, options.handle)[0];
266
+
267
+ if(options.handle && Object.isString(options.handle))
268
+ this.handle = this.element.down('.'+options.handle, 0);
269
+
208
270
  if(!this.handle) this.handle = $(options.handle);
209
271
  if(!this.handle) this.handle = this.element;
210
272
 
211
- Element.makePositioned(this.element); // fix IE
273
+ if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
274
+ options.scroll = $(options.scroll);
275
+ this._isScrollChild = Element.childOf(this.element, options.scroll);
276
+ }
277
+
278
+ Element.makePositioned(this.element); // fix IE
212
279
 
213
- this.delta = this.currentDelta();
214
280
  this.options = options;
215
- this.dragging = false;
281
+ this.dragging = false;
216
282
 
217
283
  this.eventMouseDown = this.initDrag.bindAsEventListener(this);
218
284
  Event.observe(this.handle, "mousedown", this.eventMouseDown);
219
-
285
+
220
286
  Draggables.register(this);
221
287
  },
222
-
288
+
223
289
  destroy: function() {
224
290
  Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
225
291
  Draggables.unregister(this);
226
292
  },
227
-
293
+
228
294
  currentDelta: function() {
229
295
  return([
230
- parseInt(this.element.style.left || '0'),
231
- parseInt(this.element.style.top || '0')]);
296
+ parseInt(Element.getStyle(this.element,'left') || '0'),
297
+ parseInt(Element.getStyle(this.element,'top') || '0')]);
232
298
  },
233
-
299
+
234
300
  initDrag: function(event) {
235
- if(Event.isLeftClick(event)) {
301
+ if(!Object.isUndefined(Draggable._dragging[this.element]) &&
302
+ Draggable._dragging[this.element]) return;
303
+ if(Event.isLeftClick(event)) {
236
304
  // abort on form elements, fixes a Firefox issue
237
305
  var src = Event.element(event);
238
- if(src.tagName && (
239
- src.tagName=='INPUT' ||
240
- src.tagName=='SELECT' ||
241
- src.tagName=='BUTTON' ||
242
- src.tagName=='TEXTAREA')) return;
243
-
244
- if(this.element._revert) {
245
- this.element._revert.cancel();
246
- this.element._revert = null;
247
- }
248
-
306
+ if((tag_name = src.tagName.toUpperCase()) && (
307
+ tag_name=='INPUT' ||
308
+ tag_name=='SELECT' ||
309
+ tag_name=='OPTION' ||
310
+ tag_name=='BUTTON' ||
311
+ tag_name=='TEXTAREA')) return;
312
+
249
313
  var pointer = [Event.pointerX(event), Event.pointerY(event)];
250
314
  var pos = Position.cumulativeOffset(this.element);
251
315
  this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
252
-
316
+
253
317
  Draggables.activate(this);
254
318
  Event.stop(event);
255
319
  }
256
320
  },
257
-
321
+
258
322
  startDrag: function(event) {
259
323
  this.dragging = true;
260
-
324
+ if(!this.delta)
325
+ this.delta = this.currentDelta();
326
+
261
327
  if(this.options.zindex) {
262
328
  this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
263
329
  this.element.style.zIndex = this.options.zindex;
264
330
  }
265
-
331
+
266
332
  if(this.options.ghosting) {
267
333
  this._clone = this.element.cloneNode(true);
268
- Position.absolutize(this.element);
334
+ this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
335
+ if (!this._originallyAbsolute)
336
+ Position.absolutize(this.element);
269
337
  this.element.parentNode.insertBefore(this._clone, this.element);
270
338
  }
271
-
339
+
340
+ if(this.options.scroll) {
341
+ if (this.options.scroll == window) {
342
+ var where = this._getWindowScroll(this.options.scroll);
343
+ this.originalScrollLeft = where.left;
344
+ this.originalScrollTop = where.top;
345
+ } else {
346
+ this.originalScrollLeft = this.options.scroll.scrollLeft;
347
+ this.originalScrollTop = this.options.scroll.scrollTop;
348
+ }
349
+ }
350
+
272
351
  Draggables.notify('onStart', this, event);
352
+
273
353
  if(this.options.starteffect) this.options.starteffect(this.element);
274
354
  },
275
-
355
+
276
356
  updateDrag: function(event, pointer) {
277
357
  if(!this.dragging) this.startDrag(event);
278
- Position.prepare();
279
- Droppables.show(pointer, this.element);
358
+
359
+ if(!this.options.quiet){
360
+ Position.prepare();
361
+ Droppables.show(pointer, this.element);
362
+ }
363
+
280
364
  Draggables.notify('onDrag', this, event);
365
+
281
366
  this.draw(pointer);
282
367
  if(this.options.change) this.options.change(this);
283
-
368
+
369
+ if(this.options.scroll) {
370
+ this.stopScrolling();
371
+
372
+ var p;
373
+ if (this.options.scroll == window) {
374
+ with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
375
+ } else {
376
+ p = Position.page(this.options.scroll);
377
+ p[0] += this.options.scroll.scrollLeft + Position.deltaX;
378
+ p[1] += this.options.scroll.scrollTop + Position.deltaY;
379
+ p.push(p[0]+this.options.scroll.offsetWidth);
380
+ p.push(p[1]+this.options.scroll.offsetHeight);
381
+ }
382
+ var speed = [0,0];
383
+ if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
384
+ if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
385
+ if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
386
+ if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
387
+ this.startScrolling(speed);
388
+ }
389
+
284
390
  // fix AppleWebKit rendering
285
- if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
391
+ if(Prototype.Browser.WebKit) window.scrollBy(0,0);
392
+
286
393
  Event.stop(event);
287
394
  },
288
-
395
+
289
396
  finishDrag: function(event, success) {
290
397
  this.dragging = false;
291
398
 
399
+ if(this.options.quiet){
400
+ Position.prepare();
401
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
402
+ Droppables.show(pointer, this.element);
403
+ }
404
+
292
405
  if(this.options.ghosting) {
293
- Position.relativize(this.element);
406
+ if (!this._originallyAbsolute)
407
+ Position.relativize(this.element);
408
+ delete this._originallyAbsolute;
294
409
  Element.remove(this._clone);
295
410
  this._clone = null;
296
411
  }
297
412
 
298
- if(success) Droppables.fire(event, this.element);
413
+ var dropped = false;
414
+ if(success) {
415
+ dropped = Droppables.fire(event, this.element);
416
+ if (!dropped) dropped = false;
417
+ }
418
+ if(dropped && this.options.onDropped) this.options.onDropped(this.element);
299
419
  Draggables.notify('onEnd', this, event);
300
420
 
301
421
  var revert = this.options.revert;
302
- if(revert && typeof revert == 'function') revert = revert(this.element);
303
-
422
+ if(revert && Object.isFunction(revert)) revert = revert(this.element);
423
+
304
424
  var d = this.currentDelta();
305
425
  if(revert && this.options.reverteffect) {
306
- this.options.reverteffect(this.element,
307
- d[1]-this.delta[1], d[0]-this.delta[0]);
426
+ if (dropped == 0 || revert != 'failure')
427
+ this.options.reverteffect(this.element,
428
+ d[1]-this.delta[1], d[0]-this.delta[0]);
308
429
  } else {
309
430
  this.delta = d;
310
431
  }
@@ -312,111 +433,223 @@ Draggable.prototype = {
312
433
  if(this.options.zindex)
313
434
  this.element.style.zIndex = this.originalZ;
314
435
 
315
- if(this.options.endeffect)
436
+ if(this.options.endeffect)
316
437
  this.options.endeffect(this.element);
317
438
 
318
439
  Draggables.deactivate(this);
319
440
  Droppables.reset();
320
441
  },
321
-
442
+
322
443
  keyPress: function(event) {
323
- if(!event.keyCode==Event.KEY_ESC) return;
444
+ if(event.keyCode!=Event.KEY_ESC) return;
324
445
  this.finishDrag(event, false);
325
446
  Event.stop(event);
326
447
  },
327
-
448
+
328
449
  endDrag: function(event) {
329
450
  if(!this.dragging) return;
451
+ this.stopScrolling();
330
452
  this.finishDrag(event, true);
331
453
  Event.stop(event);
332
454
  },
333
-
455
+
334
456
  draw: function(point) {
335
457
  var pos = Position.cumulativeOffset(this.element);
458
+ if(this.options.ghosting) {
459
+ var r = Position.realOffset(this.element);
460
+ pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
461
+ }
462
+
336
463
  var d = this.currentDelta();
337
464
  pos[0] -= d[0]; pos[1] -= d[1];
338
-
339
- var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this));
340
-
465
+
466
+ if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
467
+ pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
468
+ pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
469
+ }
470
+
471
+ var p = [0,1].map(function(i){
472
+ return (point[i]-pos[i]-this.offset[i])
473
+ }.bind(this));
474
+
341
475
  if(this.options.snap) {
342
- if(typeof this.options.snap == 'function') {
343
- p = this.options.snap(p[0],p[1]);
476
+ if(Object.isFunction(this.options.snap)) {
477
+ p = this.options.snap(p[0],p[1],this);
344
478
  } else {
345
- if(this.options.snap instanceof Array) {
479
+ if(Object.isArray(this.options.snap)) {
346
480
  p = p.map( function(v, i) {
347
- return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
481
+ return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
348
482
  } else {
349
483
  p = p.map( function(v) {
350
- return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
484
+ return (v/this.options.snap).round()*this.options.snap }.bind(this));
351
485
  }
352
486
  }}
353
-
487
+
354
488
  var style = this.element.style;
355
489
  if((!this.options.constraint) || (this.options.constraint=='horizontal'))
356
490
  style.left = p[0] + "px";
357
491
  if((!this.options.constraint) || (this.options.constraint=='vertical'))
358
492
  style.top = p[1] + "px";
493
+
359
494
  if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
495
+ },
496
+
497
+ stopScrolling: function() {
498
+ if(this.scrollInterval) {
499
+ clearInterval(this.scrollInterval);
500
+ this.scrollInterval = null;
501
+ Draggables._lastScrollPointer = null;
502
+ }
503
+ },
504
+
505
+ startScrolling: function(speed) {
506
+ if(!(speed[0] || speed[1])) return;
507
+ this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
508
+ this.lastScrolled = new Date();
509
+ this.scrollInterval = setInterval(this.scroll.bind(this), 10);
510
+ },
511
+
512
+ scroll: function() {
513
+ var current = new Date();
514
+ var delta = current - this.lastScrolled;
515
+ this.lastScrolled = current;
516
+ if(this.options.scroll == window) {
517
+ with (this._getWindowScroll(this.options.scroll)) {
518
+ if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
519
+ var d = delta / 1000;
520
+ this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
521
+ }
522
+ }
523
+ } else {
524
+ this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
525
+ this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
526
+ }
527
+
528
+ Position.prepare();
529
+ Droppables.show(Draggables._lastPointer, this.element);
530
+ Draggables.notify('onDrag', this);
531
+ if (this._isScrollChild) {
532
+ Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
533
+ Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
534
+ Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
535
+ if (Draggables._lastScrollPointer[0] < 0)
536
+ Draggables._lastScrollPointer[0] = 0;
537
+ if (Draggables._lastScrollPointer[1] < 0)
538
+ Draggables._lastScrollPointer[1] = 0;
539
+ this.draw(Draggables._lastScrollPointer);
540
+ }
541
+
542
+ if(this.options.change) this.options.change(this);
543
+ },
544
+
545
+ _getWindowScroll: function(w) {
546
+ var T, L, W, H;
547
+ with (w.document) {
548
+ if (w.document.documentElement && documentElement.scrollTop) {
549
+ T = documentElement.scrollTop;
550
+ L = documentElement.scrollLeft;
551
+ } else if (w.document.body) {
552
+ T = body.scrollTop;
553
+ L = body.scrollLeft;
554
+ }
555
+ if (w.innerWidth) {
556
+ W = w.innerWidth;
557
+ H = w.innerHeight;
558
+ } else if (w.document.documentElement && documentElement.clientWidth) {
559
+ W = documentElement.clientWidth;
560
+ H = documentElement.clientHeight;
561
+ } else {
562
+ W = body.offsetWidth;
563
+ H = body.offsetHeight;
564
+ }
565
+ }
566
+ return { top: T, left: L, width: W, height: H };
360
567
  }
361
- }
568
+ });
569
+
570
+ Draggable._dragging = { };
362
571
 
363
572
  /*--------------------------------------------------------------------------*/
364
573
 
365
- var SortableObserver = Class.create();
366
- SortableObserver.prototype = {
574
+ var SortableObserver = Class.create({
367
575
  initialize: function(element, observer) {
368
576
  this.element = $(element);
369
577
  this.observer = observer;
370
578
  this.lastValue = Sortable.serialize(this.element);
371
579
  },
372
-
580
+
373
581
  onStart: function() {
374
582
  this.lastValue = Sortable.serialize(this.element);
375
583
  },
376
-
584
+
377
585
  onEnd: function() {
378
586
  Sortable.unmark();
379
587
  if(this.lastValue != Sortable.serialize(this.element))
380
588
  this.observer(this.element)
381
589
  }
382
- }
590
+ });
383
591
 
384
592
  var Sortable = {
385
- sortables: new Array(),
386
-
387
- options: function(element){
388
- element = $(element);
389
- return this.sortables.detect(function(s) { return s.element == element });
593
+ SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
594
+
595
+ sortables: { },
596
+
597
+ _findRootElement: function(element) {
598
+ while (element.tagName.toUpperCase() != "BODY") {
599
+ if(element.id && Sortable.sortables[element.id]) return element;
600
+ element = element.parentNode;
601
+ }
602
+ },
603
+
604
+ options: function(element) {
605
+ element = Sortable._findRootElement($(element));
606
+ if(!element) return;
607
+ return Sortable.sortables[element.id];
390
608
  },
391
-
609
+
392
610
  destroy: function(element){
393
611
  element = $(element);
394
- this.sortables.findAll(function(s) { return s.element == element }).each(function(s){
612
+ var s = Sortable.sortables[element.id];
613
+
614
+ if(s) {
395
615
  Draggables.removeObserver(s.element);
396
616
  s.droppables.each(function(d){ Droppables.remove(d) });
397
617
  s.draggables.invoke('destroy');
398
- });
399
- this.sortables = this.sortables.reject(function(s) { return s.element == element });
618
+
619
+ delete Sortable.sortables[s.element.id];
620
+ }
400
621
  },
401
-
622
+
402
623
  create: function(element) {
403
624
  element = $(element);
404
- var options = Object.extend({
625
+ var options = Object.extend({
405
626
  element: element,
406
627
  tag: 'li', // assumes li children, override with tag: 'tagname'
407
628
  dropOnEmpty: false,
408
- tree: false, // fixme: unimplemented
629
+ tree: false,
630
+ treeTag: 'ul',
409
631
  overlap: 'vertical', // one of 'vertical', 'horizontal'
410
632
  constraint: 'vertical', // one of 'vertical', 'horizontal', false
411
633
  containment: element, // also takes array of elements (or id's); or false
412
634
  handle: false, // or a CSS class
413
635
  only: false,
636
+ delay: 0,
414
637
  hoverclass: null,
415
638
  ghosting: false,
416
- format: null,
639
+ quiet: false,
640
+ scroll: false,
641
+ scrollSensitivity: 20,
642
+ scrollSpeed: 15,
643
+ format: this.SERIALIZE_RULE,
644
+
645
+ // these take arrays of elements or ids and can be
646
+ // used for better initialization performance
647
+ elements: false,
648
+ handles: false,
649
+
417
650
  onChange: Prototype.emptyFunction,
418
651
  onUpdate: Prototype.emptyFunction
419
- }, arguments[1] || {});
652
+ }, arguments[1] || { });
420
653
 
421
654
  // clear any old sortable with same element
422
655
  this.destroy(element);
@@ -424,6 +657,11 @@ var Sortable = {
424
657
  // build options for the draggables
425
658
  var options_for_draggable = {
426
659
  revert: true,
660
+ quiet: options.quiet,
661
+ scroll: options.scroll,
662
+ scrollSpeed: options.scrollSpeed,
663
+ scrollSensitivity: options.scrollSensitivity,
664
+ delay: options.delay,
427
665
  ghosting: options.ghosting,
428
666
  constraint: options.constraint,
429
667
  handle: options.handle };
@@ -445,42 +683,54 @@ var Sortable = {
445
683
  if(options.zindex)
446
684
  options_for_draggable.zindex = options.zindex;
447
685
 
448
- // build options for the droppables
686
+ // build options for the droppables
449
687
  var options_for_droppable = {
450
688
  overlap: options.overlap,
451
689
  containment: options.containment,
690
+ tree: options.tree,
452
691
  hoverclass: options.hoverclass,
453
- onHover: Sortable.onHover,
454
- greedy: !options.dropOnEmpty
455
- }
692
+ onHover: Sortable.onHover
693
+ };
694
+
695
+ var options_for_tree = {
696
+ onHover: Sortable.onEmptyHover,
697
+ overlap: options.overlap,
698
+ containment: options.containment,
699
+ hoverclass: options.hoverclass
700
+ };
456
701
 
457
702
  // fix for gecko engine
458
- Element.cleanWhitespace(element);
703
+ Element.cleanWhitespace(element);
459
704
 
460
705
  options.draggables = [];
461
706
  options.droppables = [];
462
707
 
463
- // make it so
464
-
465
708
  // drop on empty handling
466
- if(options.dropOnEmpty) {
467
- Droppables.add(element,
468
- {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false});
709
+ if(options.dropOnEmpty || options.tree) {
710
+ Droppables.add(element, options_for_tree);
469
711
  options.droppables.push(element);
470
712
  }
471
713
 
472
- (this.findElements(element, options) || []).each( function(e) {
473
- // handles are per-draggable
474
- var handle = options.handle ?
475
- Element.childrenWithClassName(e, options.handle)[0] : e;
714
+ (options.elements || this.findElements(element, options) || []).each( function(e,i) {
715
+ var handle = options.handles ? $(options.handles[i]) :
716
+ (options.handle ? $(e).select('.' + options.handle)[0] : e);
476
717
  options.draggables.push(
477
718
  new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
478
719
  Droppables.add(e, options_for_droppable);
479
- options.droppables.push(e);
720
+ if(options.tree) e.treeNode = element;
721
+ options.droppables.push(e);
480
722
  });
481
723
 
724
+ if(options.tree) {
725
+ (Sortable.findTreeElements(element, options) || []).each( function(e) {
726
+ Droppables.add(e, options_for_tree);
727
+ e.treeNode = element;
728
+ options.droppables.push(e);
729
+ });
730
+ }
731
+
482
732
  // keep reference
483
- this.sortables.push(options);
733
+ this.sortables[element.id] = options;
484
734
 
485
735
  // for onupdate
486
736
  Draggables.addObserver(new SortableObserver(element, options.onUpdate));
@@ -489,29 +739,27 @@ var Sortable = {
489
739
 
490
740
  // return all suitable-for-sortable elements in a guaranteed order
491
741
  findElements: function(element, options) {
492
- if(!element.hasChildNodes()) return null;
493
- var elements = [];
494
- $A(element.childNodes).each( function(e) {
495
- if(e.tagName && e.tagName.toUpperCase()==options.tag.toUpperCase() &&
496
- (!options.only || (Element.hasClassName(e, options.only))))
497
- elements.push(e);
498
- if(options.tree) {
499
- var grandchildren = this.findElements(e, options);
500
- if(grandchildren) elements.push(grandchildren);
501
- }
502
- });
742
+ return Element.findChildren(
743
+ element, options.only, options.tree ? true : false, options.tag);
744
+ },
503
745
 
504
- return (elements.length>0 ? elements.flatten() : null);
746
+ findTreeElements: function(element, options) {
747
+ return Element.findChildren(
748
+ element, options.only, options.tree ? true : false, options.treeTag);
505
749
  },
506
750
 
507
751
  onHover: function(element, dropon, overlap) {
508
- if(overlap>0.5) {
752
+ if(Element.isParent(dropon, element)) return;
753
+
754
+ if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
755
+ return;
756
+ } else if(overlap>0.5) {
509
757
  Sortable.mark(dropon, 'before');
510
758
  if(dropon.previousSibling != element) {
511
759
  var oldParentNode = element.parentNode;
512
760
  element.style.visibility = "hidden"; // fix gecko rendering
513
761
  dropon.parentNode.insertBefore(element, dropon);
514
- if(dropon.parentNode!=oldParentNode)
762
+ if(dropon.parentNode!=oldParentNode)
515
763
  Sortable.options(oldParentNode).onChange(element);
516
764
  Sortable.options(dropon.parentNode).onChange(element);
517
765
  }
@@ -522,63 +770,204 @@ var Sortable = {
522
770
  var oldParentNode = element.parentNode;
523
771
  element.style.visibility = "hidden"; // fix gecko rendering
524
772
  dropon.parentNode.insertBefore(element, nextElement);
525
- if(dropon.parentNode!=oldParentNode)
773
+ if(dropon.parentNode!=oldParentNode)
526
774
  Sortable.options(oldParentNode).onChange(element);
527
775
  Sortable.options(dropon.parentNode).onChange(element);
528
776
  }
529
777
  }
530
778
  },
531
779
 
532
- onEmptyHover: function(element, dropon) {
533
- if(element.parentNode!=dropon) {
534
- var oldParentNode = element.parentNode;
535
- dropon.appendChild(element);
780
+ onEmptyHover: function(element, dropon, overlap) {
781
+ var oldParentNode = element.parentNode;
782
+ var droponOptions = Sortable.options(dropon);
783
+
784
+ if(!Element.isParent(dropon, element)) {
785
+ var index;
786
+
787
+ var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
788
+ var child = null;
789
+
790
+ if(children) {
791
+ var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
792
+
793
+ for (index = 0; index < children.length; index += 1) {
794
+ if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
795
+ offset -= Element.offsetSize (children[index], droponOptions.overlap);
796
+ } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
797
+ child = index + 1 < children.length ? children[index + 1] : null;
798
+ break;
799
+ } else {
800
+ child = children[index];
801
+ break;
802
+ }
803
+ }
804
+ }
805
+
806
+ dropon.insertBefore(element, child);
807
+
536
808
  Sortable.options(oldParentNode).onChange(element);
537
- Sortable.options(dropon).onChange(element);
809
+ droponOptions.onChange(element);
538
810
  }
539
811
  },
540
812
 
541
813
  unmark: function() {
542
- if(Sortable._marker) Element.hide(Sortable._marker);
814
+ if(Sortable._marker) Sortable._marker.hide();
543
815
  },
544
816
 
545
817
  mark: function(dropon, position) {
546
818
  // mark on ghosting only
547
819
  var sortable = Sortable.options(dropon.parentNode);
548
- if(sortable && !sortable.ghosting) return;
820
+ if(sortable && !sortable.ghosting) return;
549
821
 
550
822
  if(!Sortable._marker) {
551
- Sortable._marker = $('dropmarker') || document.createElement('DIV');
552
- Element.hide(Sortable._marker);
553
- Element.addClassName(Sortable._marker, 'dropmarker');
554
- Sortable._marker.style.position = 'absolute';
823
+ Sortable._marker =
824
+ ($('dropmarker') || Element.extend(document.createElement('DIV'))).
825
+ hide().addClassName('dropmarker').setStyle({position:'absolute'});
555
826
  document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
556
- }
827
+ }
557
828
  var offsets = Position.cumulativeOffset(dropon);
558
- Sortable._marker.style.left = offsets[0] + 'px';
559
- Sortable._marker.style.top = offsets[1] + 'px';
560
-
829
+ Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
830
+
561
831
  if(position=='after')
562
- if(sortable.overlap == 'horizontal')
563
- Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
832
+ if(sortable.overlap == 'horizontal')
833
+ Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
564
834
  else
565
- Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
566
-
567
- Element.show(Sortable._marker);
835
+ Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
836
+
837
+ Sortable._marker.show();
568
838
  },
569
839
 
570
- serialize: function(element) {
840
+ _tree: function(element, options, parent) {
841
+ var children = Sortable.findElements(element, options) || [];
842
+
843
+ for (var i = 0; i < children.length; ++i) {
844
+ var match = children[i].id.match(options.format);
845
+
846
+ if (!match) continue;
847
+
848
+ var child = {
849
+ id: encodeURIComponent(match ? match[1] : null),
850
+ element: element,
851
+ parent: parent,
852
+ children: [],
853
+ position: parent.children.length,
854
+ container: $(children[i]).down(options.treeTag)
855
+ };
856
+
857
+ /* Get the element containing the children and recurse over it */
858
+ if (child.container)
859
+ this._tree(child.container, options, child);
860
+
861
+ parent.children.push (child);
862
+ }
863
+
864
+ return parent;
865
+ },
866
+
867
+ tree: function(element) {
571
868
  element = $(element);
572
869
  var sortableOptions = this.options(element);
573
870
  var options = Object.extend({
574
- tag: sortableOptions.tag,
871
+ tag: sortableOptions.tag,
872
+ treeTag: sortableOptions.treeTag,
575
873
  only: sortableOptions.only,
576
874
  name: element.id,
577
- format: sortableOptions.format || /^[^_]*_(.*)$/
578
- }, arguments[1] || {});
875
+ format: sortableOptions.format
876
+ }, arguments[1] || { });
877
+
878
+ var root = {
879
+ id: null,
880
+ parent: null,
881
+ children: [],
882
+ container: element,
883
+ position: 0
884
+ };
885
+
886
+ return Sortable._tree(element, options, root);
887
+ },
888
+
889
+ /* Construct a [i] index for a particular node */
890
+ _constructIndex: function(node) {
891
+ var index = '';
892
+ do {
893
+ if (node.id) index = '[' + node.position + ']' + index;
894
+ } while ((node = node.parent) != null);
895
+ return index;
896
+ },
897
+
898
+ sequence: function(element) {
899
+ element = $(element);
900
+ var options = Object.extend(this.options(element), arguments[1] || { });
901
+
579
902
  return $(this.findElements(element, options) || []).map( function(item) {
580
- return (encodeURIComponent(options.name) + "[]=" +
581
- encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : ''));
582
- }).join("&");
903
+ return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
904
+ });
905
+ },
906
+
907
+ setSequence: function(element, new_sequence) {
908
+ element = $(element);
909
+ var options = Object.extend(this.options(element), arguments[2] || { });
910
+
911
+ var nodeMap = { };
912
+ this.findElements(element, options).each( function(n) {
913
+ if (n.id.match(options.format))
914
+ nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
915
+ n.parentNode.removeChild(n);
916
+ });
917
+
918
+ new_sequence.each(function(ident) {
919
+ var n = nodeMap[ident];
920
+ if (n) {
921
+ n[1].appendChild(n[0]);
922
+ delete nodeMap[ident];
923
+ }
924
+ });
925
+ },
926
+
927
+ serialize: function(element) {
928
+ element = $(element);
929
+ var options = Object.extend(Sortable.options(element), arguments[1] || { });
930
+ var name = encodeURIComponent(
931
+ (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
932
+
933
+ if (options.tree) {
934
+ return Sortable.tree(element, arguments[1]).children.map( function (item) {
935
+ return [name + Sortable._constructIndex(item) + "[id]=" +
936
+ encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
937
+ }).flatten().join('&');
938
+ } else {
939
+ return Sortable.sequence(element, arguments[1]).map( function(item) {
940
+ return name + "[]=" + encodeURIComponent(item);
941
+ }).join('&');
942
+ }
583
943
  }
584
- }
944
+ };
945
+
946
+ // Returns true if child is contained within element
947
+ Element.isParent = function(child, element) {
948
+ if (!child.parentNode || child == element) return false;
949
+ if (child.parentNode == element) return true;
950
+ return Element.isParent(child.parentNode, element);
951
+ };
952
+
953
+ Element.findChildren = function(element, only, recursive, tagName) {
954
+ if(!element.hasChildNodes()) return null;
955
+ tagName = tagName.toUpperCase();
956
+ if(only) only = [only].flatten();
957
+ var elements = [];
958
+ $A(element.childNodes).each( function(e) {
959
+ if(e.tagName && e.tagName.toUpperCase()==tagName &&
960
+ (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
961
+ elements.push(e);
962
+ if(recursive) {
963
+ var grandchildren = Element.findChildren(e, only, recursive, tagName);
964
+ if(grandchildren) elements.push(grandchildren);
965
+ }
966
+ });
967
+
968
+ return (elements.length>0 ? elements.flatten() : []);
969
+ };
970
+
971
+ Element.offsetSize = function (element, type) {
972
+ return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
973
+ };