actionpack 1.11.2 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (149) hide show
  1. data/CHANGELOG +392 -5
  2. data/lib/action_controller.rb +8 -4
  3. data/lib/action_controller/assertions.rb +9 -10
  4. data/lib/action_controller/base.rb +177 -88
  5. data/lib/action_controller/benchmarking.rb +5 -5
  6. data/lib/action_controller/caching.rb +44 -36
  7. data/lib/action_controller/cgi_ext/cgi_methods.rb +71 -6
  8. data/lib/action_controller/cgi_ext/cookie_performance_fix.rb +1 -1
  9. data/lib/action_controller/cgi_process.rb +36 -24
  10. data/lib/action_controller/components.rb +152 -52
  11. data/lib/action_controller/dependencies.rb +1 -1
  12. data/lib/action_controller/deprecated_redirects.rb +2 -2
  13. data/lib/action_controller/deprecated_request_methods.rb +34 -0
  14. data/lib/action_controller/filters.rb +59 -19
  15. data/lib/action_controller/flash.rb +53 -47
  16. data/lib/action_controller/helpers.rb +2 -2
  17. data/lib/action_controller/integration.rb +524 -0
  18. data/lib/action_controller/layout.rb +58 -23
  19. data/lib/action_controller/mime_responds.rb +163 -0
  20. data/lib/action_controller/mime_type.rb +142 -0
  21. data/lib/action_controller/pagination.rb +13 -7
  22. data/lib/action_controller/request.rb +59 -56
  23. data/lib/action_controller/rescue.rb +1 -1
  24. data/lib/action_controller/routing.rb +29 -10
  25. data/lib/action_controller/scaffolding.rb +8 -0
  26. data/lib/action_controller/session/active_record_store.rb +21 -10
  27. data/lib/action_controller/session/mem_cache_store.rb +18 -12
  28. data/lib/action_controller/session_management.rb +30 -11
  29. data/lib/action_controller/templates/rescues/_trace.rhtml +1 -1
  30. data/lib/action_controller/templates/scaffolds/layout.rhtml +4 -4
  31. data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
  32. data/lib/action_controller/test_process.rb +189 -118
  33. data/lib/action_controller/vendor/html-scanner/html/node.rb +20 -1
  34. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +3 -0
  35. data/lib/action_controller/vendor/html-scanner/html/version.rb +1 -1
  36. data/lib/action_controller/vendor/xml_node.rb +97 -0
  37. data/lib/action_controller/verification.rb +2 -0
  38. data/lib/action_pack/version.rb +3 -3
  39. data/lib/action_view.rb +0 -2
  40. data/lib/action_view/base.rb +109 -36
  41. data/lib/action_view/compiled_templates.rb +1 -1
  42. data/lib/action_view/helpers/active_record_helper.rb +4 -2
  43. data/lib/action_view/helpers/asset_tag_helper.rb +6 -7
  44. data/lib/action_view/helpers/capture_helper.rb +49 -12
  45. data/lib/action_view/helpers/date_helper.rb +14 -4
  46. data/lib/action_view/helpers/form_helper.rb +136 -20
  47. data/lib/action_view/helpers/form_options_helper.rb +29 -7
  48. data/lib/action_view/helpers/form_tag_helper.rb +22 -20
  49. data/lib/action_view/helpers/java_script_macros_helper.rb +29 -9
  50. data/lib/action_view/helpers/javascript_helper.rb +50 -446
  51. data/lib/action_view/helpers/javascripts/controls.js +95 -30
  52. data/lib/action_view/helpers/javascripts/dragdrop.js +161 -21
  53. data/lib/action_view/helpers/javascripts/effects.js +310 -211
  54. data/lib/action_view/helpers/javascripts/prototype.js +228 -28
  55. data/lib/action_view/helpers/number_helper.rb +9 -9
  56. data/lib/action_view/helpers/pagination_helper.rb +1 -1
  57. data/lib/action_view/helpers/prototype_helper.rb +900 -0
  58. data/lib/action_view/helpers/scriptaculous_helper.rb +135 -0
  59. data/lib/action_view/helpers/text_helper.rb +7 -6
  60. data/lib/action_view/helpers/url_helper.rb +23 -14
  61. data/lib/action_view/partials.rb +12 -4
  62. data/rakefile +13 -5
  63. data/test/abstract_unit.rb +4 -3
  64. data/test/active_record_unit.rb +88 -0
  65. data/test/{controller → activerecord}/active_record_assertions_test.rb +7 -50
  66. data/test/{controller → activerecord}/active_record_store_test.rb +27 -4
  67. data/test/activerecord/pagination_test.rb +161 -0
  68. data/test/controller/action_pack_assertions_test.rb +18 -15
  69. data/test/controller/base_test.rb +31 -42
  70. data/test/controller/benchmark_test.rb +8 -11
  71. data/test/controller/capture_test.rb +33 -1
  72. data/test/controller/cgi_test.rb +33 -0
  73. data/test/controller/custom_handler_test.rb +8 -0
  74. data/test/controller/fake_controllers.rb +9 -17
  75. data/test/controller/filters_test.rb +32 -3
  76. data/test/controller/flash_test.rb +26 -41
  77. data/test/controller/fragment_store_setting_test.rb +1 -1
  78. data/test/controller/layout_test.rb +73 -0
  79. data/test/controller/mime_responds_test.rb +257 -0
  80. data/test/controller/mime_type_test.rb +24 -0
  81. data/test/controller/new_render_test.rb +157 -1
  82. data/test/controller/redirect_test.rb +23 -0
  83. data/test/controller/render_test.rb +54 -56
  84. data/test/controller/request_test.rb +25 -0
  85. data/test/controller/routing_test.rb +74 -66
  86. data/test/controller/test_test.rb +66 -1
  87. data/test/controller/verification_test.rb +3 -1
  88. data/test/controller/webservice_test.rb +255 -0
  89. data/test/fixtures/companies.yml +24 -0
  90. data/test/fixtures/company.rb +9 -0
  91. data/test/fixtures/db_definitions/sqlite.sql +42 -0
  92. data/test/fixtures/developer.rb +7 -0
  93. data/test/fixtures/developers.yml +21 -0
  94. data/test/fixtures/developers_projects.yml +13 -0
  95. data/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml +1 -0
  96. data/test/fixtures/layout_tests/layouts/item.rhtml +1 -0
  97. data/test/fixtures/layout_tests/layouts/layout_test.rhtml +1 -0
  98. data/test/fixtures/layout_tests/layouts/third_party_template_library.mab +1 -0
  99. data/test/fixtures/layout_tests/views/hello.rhtml +1 -0
  100. data/test/fixtures/multipart/mona_lisa.jpg +0 -0
  101. data/test/fixtures/project.rb +3 -0
  102. data/test/fixtures/projects.yml +7 -0
  103. data/test/fixtures/replies.yml +13 -0
  104. data/test/fixtures/reply.rb +5 -0
  105. data/test/fixtures/respond_to/all_types_with_layout.rhtml +1 -0
  106. data/test/fixtures/respond_to/all_types_with_layout.rjs +1 -0
  107. data/test/fixtures/respond_to/layouts/standard.rhtml +1 -0
  108. data/test/fixtures/respond_to/using_defaults.rhtml +1 -0
  109. data/test/fixtures/respond_to/using_defaults.rjs +1 -0
  110. data/test/fixtures/respond_to/using_defaults.rxml +1 -0
  111. data/test/fixtures/respond_to/using_defaults_with_type_list.rhtml +1 -0
  112. data/test/fixtures/respond_to/using_defaults_with_type_list.rjs +1 -0
  113. data/test/fixtures/respond_to/using_defaults_with_type_list.rxml +1 -0
  114. data/test/fixtures/test/block_content_for.rhtml +2 -0
  115. data/test/fixtures/test/delete_with_js.rjs +2 -0
  116. data/test/fixtures/test/dot.directory/render_file_with_ivar.rhtml +1 -0
  117. data/test/fixtures/test/enum_rjs_test.rjs +6 -0
  118. data/test/fixtures/test/erb_content_for.rhtml +2 -0
  119. data/test/fixtures/test/hello_world.rxml +3 -0
  120. data/test/fixtures/test/hello_world_with_layout_false.rhtml +1 -0
  121. data/test/fixtures/test/non_erb_block_content_for.rxml +4 -0
  122. data/test/fixtures/topic.rb +3 -0
  123. data/test/fixtures/topics.yml +22 -0
  124. data/test/template/active_record_helper_test.rb +4 -0
  125. data/test/template/asset_tag_helper_test.rb +7 -2
  126. data/test/template/date_helper_test.rb +39 -2
  127. data/test/template/form_helper_test.rb +238 -5
  128. data/test/template/form_options_helper_test.rb +78 -0
  129. data/test/template/form_tag_helper_test.rb +11 -0
  130. data/test/template/java_script_macros_helper_test.rb +51 -6
  131. data/test/template/javascript_helper_test.rb +7 -153
  132. data/test/template/number_helper_test.rb +14 -13
  133. data/test/template/prototype_helper_test.rb +423 -0
  134. data/test/template/scriptaculous_helper_test.rb +90 -0
  135. data/test/template/text_helper_test.rb +12 -9
  136. data/test/template/url_helper_test.rb +31 -15
  137. metadata +291 -246
  138. data/lib/action_controller/cgi_ext/multipart_progress.rb +0 -169
  139. data/lib/action_controller/upload_progress.rb +0 -473
  140. data/lib/action_controller/vendor/html-scanner/html/node.rb.rej +0 -17
  141. data/lib/action_view/helpers/upload_progress_helper.rb +0 -433
  142. data/lib/action_view/vendor/builder.rb +0 -13
  143. data/lib/action_view/vendor/builder/blankslate.rb +0 -53
  144. data/lib/action_view/vendor/builder/xmlbase.rb +0 -143
  145. data/lib/action_view/vendor/builder/xmlevents.rb +0 -63
  146. data/lib/action_view/vendor/builder/xmlmarkup.rb +0 -308
  147. data/test/controller/multipart_progress_testx.rb +0 -365
  148. data/test/controller/upload_progress_testx.rb +0 -89
  149. data/test/template/upload_progress_helper_testx.rb +0 -136
@@ -141,8 +141,8 @@ Autocompleter.Base.prototype = {
141
141
  return;
142
142
  }
143
143
  else
144
- if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
145
- return;
144
+ if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
145
+ (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
146
146
 
147
147
  this.changed = true;
148
148
  this.hasFocus = true;
@@ -152,6 +152,12 @@ Autocompleter.Base.prototype = {
152
152
  setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
153
153
  },
154
154
 
155
+ activate: function() {
156
+ this.changed = false;
157
+ this.hasFocus = true;
158
+ this.getUpdatedChoices();
159
+ },
160
+
155
161
  onHover: function(event) {
156
162
  var element = Event.findElement(event, 'LI');
157
163
  if(this.index != element.autocompleteIndex)
@@ -221,8 +227,13 @@ Autocompleter.Base.prototype = {
221
227
  this.options.updateElement(selectedElement);
222
228
  return;
223
229
  }
224
-
225
- var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
230
+ var value = '';
231
+ if (this.options.select) {
232
+ var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
233
+ if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
234
+ } else
235
+ value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
236
+
226
237
  var lastTokenPos = this.findLastToken();
227
238
  if (lastTokenPos != -1) {
228
239
  var newValue = this.element.value.substr(0, lastTokenPos + 1);
@@ -305,7 +316,7 @@ Autocompleter.Base.prototype = {
305
316
  Ajax.Autocompleter = Class.create();
306
317
  Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
307
318
  initialize: function(element, update, url, options) {
308
- this.baseInitialize(element, update, options);
319
+ this.baseInitialize(element, update, options);
309
320
  this.options.asynchronous = true;
310
321
  this.options.onComplete = this.onComplete.bind(this);
311
322
  this.options.defaultParams = this.options.parameters || null;
@@ -448,7 +459,9 @@ Ajax.InPlaceEditor.prototype = {
448
459
  this.element = $(element);
449
460
 
450
461
  this.options = Object.extend({
462
+ okButton: true,
451
463
  okText: "ok",
464
+ cancelLink: true,
452
465
  cancelText: "cancel",
453
466
  savingText: "Saving...",
454
467
  clickToEditText: "Click to edit",
@@ -470,8 +483,10 @@ Ajax.InPlaceEditor.prototype = {
470
483
  formClassName: 'inplaceeditor-form',
471
484
  highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
472
485
  highlightendcolor: "#FFFFFF",
473
- externalControl: null,
474
- ajaxOptions: {}
486
+ externalControl: null,
487
+ submitOnBlur: false,
488
+ ajaxOptions: {},
489
+ evalScripts: false
475
490
  }, options || {});
476
491
 
477
492
  if(!this.options.formId && this.element.id) {
@@ -536,16 +551,22 @@ Ajax.InPlaceEditor.prototype = {
536
551
  this.form.appendChild(br);
537
552
  }
538
553
 
539
- okButton = document.createElement("input");
540
- okButton.type = "submit";
541
- okButton.value = this.options.okText;
542
- this.form.appendChild(okButton);
554
+ if (this.options.okButton) {
555
+ okButton = document.createElement("input");
556
+ okButton.type = "submit";
557
+ okButton.value = this.options.okText;
558
+ okButton.className = 'editor_ok_button';
559
+ this.form.appendChild(okButton);
560
+ }
543
561
 
544
- cancelLink = document.createElement("a");
545
- cancelLink.href = "#";
546
- cancelLink.appendChild(document.createTextNode(this.options.cancelText));
547
- cancelLink.onclick = this.onclickCancel.bind(this);
548
- this.form.appendChild(cancelLink);
562
+ if (this.options.cancelLink) {
563
+ cancelLink = document.createElement("a");
564
+ cancelLink.href = "#";
565
+ cancelLink.appendChild(document.createTextNode(this.options.cancelText));
566
+ cancelLink.onclick = this.onclickCancel.bind(this);
567
+ cancelLink.className = 'editor_cancel';
568
+ this.form.appendChild(cancelLink);
569
+ }
549
570
  },
550
571
  hasHTMLLineBreaks: function(string) {
551
572
  if (!this.options.handleLineBreaks) return false;
@@ -561,24 +582,34 @@ Ajax.InPlaceEditor.prototype = {
561
582
  } else {
562
583
  text = this.getText();
563
584
  }
585
+
586
+ var obj = this;
564
587
 
565
588
  if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
566
589
  this.options.textarea = false;
567
590
  var textField = document.createElement("input");
591
+ textField.obj = this;
568
592
  textField.type = "text";
569
593
  textField.name = "value";
570
594
  textField.value = text;
571
595
  textField.style.backgroundColor = this.options.highlightcolor;
596
+ textField.className = 'editor_field';
572
597
  var size = this.options.size || this.options.cols || 0;
573
598
  if (size != 0) textField.size = size;
599
+ if (this.options.submitOnBlur)
600
+ textField.onblur = this.onSubmit.bind(this);
574
601
  this.editField = textField;
575
602
  } else {
576
603
  this.options.textarea = true;
577
604
  var textArea = document.createElement("textarea");
605
+ textArea.obj = this;
578
606
  textArea.name = "value";
579
607
  textArea.value = this.convertHTMLLineBreaks(text);
580
608
  textArea.rows = this.options.rows;
581
609
  textArea.cols = this.options.cols || 40;
610
+ textArea.className = 'editor_field';
611
+ if (this.options.submitOnBlur)
612
+ textArea.onblur = this.onSubmit.bind(this);
582
613
  this.editField = textArea;
583
614
  }
584
615
 
@@ -629,19 +660,26 @@ Ajax.InPlaceEditor.prototype = {
629
660
  // to be displayed indefinitely
630
661
  this.onLoading();
631
662
 
632
- new Ajax.Updater(
633
- {
634
- success: this.element,
635
- // don't update on failure (this could be an option)
636
- failure: null
637
- },
638
- this.url,
639
- Object.extend({
640
- parameters: this.options.callback(form, value),
641
- onComplete: this.onComplete.bind(this),
642
- onFailure: this.onFailure.bind(this)
643
- }, this.options.ajaxOptions)
644
- );
663
+ if (this.options.evalScripts) {
664
+ new Ajax.Request(
665
+ this.url, Object.extend({
666
+ parameters: this.options.callback(form, value),
667
+ onComplete: this.onComplete.bind(this),
668
+ onFailure: this.onFailure.bind(this),
669
+ asynchronous:true,
670
+ evalScripts:true
671
+ }, this.options.ajaxOptions));
672
+ } else {
673
+ new Ajax.Updater(
674
+ { success: this.element,
675
+ // don't update on failure (this could be an option)
676
+ failure: null },
677
+ this.url, Object.extend({
678
+ parameters: this.options.callback(form, value),
679
+ onComplete: this.onComplete.bind(this),
680
+ onFailure: this.onFailure.bind(this)
681
+ }, this.options.ajaxOptions));
682
+ }
645
683
  // stop the event to avoid a page refresh in Safari
646
684
  if (arguments.length > 1) {
647
685
  Event.stop(arguments[0]);
@@ -723,6 +761,33 @@ Ajax.InPlaceEditor.prototype = {
723
761
  }
724
762
  };
725
763
 
764
+ Ajax.InPlaceCollectionEditor = Class.create();
765
+ Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
766
+ Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
767
+ createEditField: function() {
768
+ if (!this.cached_selectTag) {
769
+ var selectTag = document.createElement("select");
770
+ var collection = this.options.collection || [];
771
+ var optionTag;
772
+ collection.each(function(e,i) {
773
+ optionTag = document.createElement("option");
774
+ optionTag.value = (e instanceof Array) ? e[0] : e;
775
+ if(this.options.value==optionTag.value) optionTag.selected = true;
776
+ optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
777
+ selectTag.appendChild(optionTag);
778
+ }.bind(this));
779
+ this.cached_selectTag = selectTag;
780
+ }
781
+
782
+ this.editField = this.cached_selectTag;
783
+ if(this.options.loadTextURL) this.loadExternalText();
784
+ this.form.appendChild(this.editField);
785
+ this.options.callback = function(form, value) {
786
+ return "value=" + encodeURIComponent(value);
787
+ }
788
+ }
789
+ });
790
+
726
791
  // Delayed observer, like Form.Element.Observer,
727
792
  // but waits for delay after last key input
728
793
  // Ideal for live-search fields
@@ -747,4 +812,4 @@ Form.Element.DelayedObserver.prototype = {
747
812
  this.timer = null;
748
813
  this.callback(this.element, $F(this.element));
749
814
  }
750
- };
815
+ };
@@ -128,7 +128,7 @@ var Draggables = {
128
128
  this.activeDraggable = draggable;
129
129
  },
130
130
 
131
- deactivate: function(draggbale) {
131
+ deactivate: function() {
132
132
  this.activeDraggable = null;
133
133
  },
134
134
 
@@ -146,6 +146,7 @@ var Draggables = {
146
146
  if(!this.activeDraggable) return;
147
147
  this._lastPointer = null;
148
148
  this.activeDraggable.endDrag(event);
149
+ this.activeDraggable = null;
149
150
  },
150
151
 
151
152
  keyPress: function(event) {
@@ -191,22 +192,28 @@ Draggable.prototype = {
191
192
  },
192
193
  reverteffect: function(element, top_offset, left_offset) {
193
194
  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});
195
+ element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur});
195
196
  },
196
197
  endeffect: function(element) {
197
198
  new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
198
199
  },
199
200
  zindex: 1000,
200
201
  revert: false,
202
+ scroll: false,
203
+ scrollSensitivity: 20,
204
+ scrollSpeed: 15,
201
205
  snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] }
202
206
  }, arguments[1] || {});
203
207
 
204
208
  this.element = $(element);
205
209
 
206
210
  if(options.handle && (typeof options.handle == 'string'))
207
- this.handle = Element.childrenWithClassName(this.element, options.handle)[0];
211
+ this.handle = Element.childrenWithClassName(this.element, options.handle, true)[0];
208
212
  if(!this.handle) this.handle = $(options.handle);
209
213
  if(!this.handle) this.handle = this.element;
214
+
215
+ if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML)
216
+ options.scroll = $(options.scroll);
210
217
 
211
218
  Element.makePositioned(this.element); // fix IE
212
219
 
@@ -227,8 +234,8 @@ Draggable.prototype = {
227
234
 
228
235
  currentDelta: function() {
229
236
  return([
230
- parseInt(this.element.style.left || '0'),
231
- parseInt(this.element.style.top || '0')]);
237
+ parseInt(Element.getStyle(this.element,'left') || '0'),
238
+ parseInt(Element.getStyle(this.element,'top') || '0')]);
232
239
  },
233
240
 
234
241
  initDrag: function(event) {
@@ -238,6 +245,7 @@ Draggable.prototype = {
238
245
  if(src.tagName && (
239
246
  src.tagName=='INPUT' ||
240
247
  src.tagName=='SELECT' ||
248
+ src.tagName=='OPTION' ||
241
249
  src.tagName=='BUTTON' ||
242
250
  src.tagName=='TEXTAREA')) return;
243
251
 
@@ -269,6 +277,17 @@ Draggable.prototype = {
269
277
  this.element.parentNode.insertBefore(this._clone, this.element);
270
278
  }
271
279
 
280
+ if(this.options.scroll) {
281
+ if (this.options.scroll == window) {
282
+ var where = this._getWindowScroll(this.options.scroll);
283
+ this.originalScrollLeft = where.left;
284
+ this.originalScrollTop = where.top;
285
+ } else {
286
+ this.originalScrollLeft = this.options.scroll.scrollLeft;
287
+ this.originalScrollTop = this.options.scroll.scrollTop;
288
+ }
289
+ }
290
+
272
291
  Draggables.notify('onStart', this, event);
273
292
  if(this.options.starteffect) this.options.starteffect(this.element);
274
293
  },
@@ -281,8 +300,30 @@ Draggable.prototype = {
281
300
  this.draw(pointer);
282
301
  if(this.options.change) this.options.change(this);
283
302
 
303
+ if(this.options.scroll) {
304
+ this.stopScrolling();
305
+
306
+ var p;
307
+ if (this.options.scroll == window) {
308
+ with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
309
+ } else {
310
+ p = Position.page(this.options.scroll);
311
+ p[0] += this.options.scroll.scrollLeft;
312
+ p[1] += this.options.scroll.scrollTop;
313
+ p.push(p[0]+this.options.scroll.offsetWidth);
314
+ p.push(p[1]+this.options.scroll.offsetHeight);
315
+ }
316
+ var speed = [0,0];
317
+ if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
318
+ if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
319
+ if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
320
+ if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
321
+ this.startScrolling(speed);
322
+ }
323
+
284
324
  // fix AppleWebKit rendering
285
325
  if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
326
+
286
327
  Event.stop(event);
287
328
  },
288
329
 
@@ -320,13 +361,14 @@ Draggable.prototype = {
320
361
  },
321
362
 
322
363
  keyPress: function(event) {
323
- if(!event.keyCode==Event.KEY_ESC) return;
364
+ if(event.keyCode!=Event.KEY_ESC) return;
324
365
  this.finishDrag(event, false);
325
366
  Event.stop(event);
326
367
  },
327
368
 
328
369
  endDrag: function(event) {
329
370
  if(!this.dragging) return;
371
+ this.stopScrolling();
330
372
  this.finishDrag(event, true);
331
373
  Event.stop(event);
332
374
  },
@@ -336,7 +378,14 @@ Draggable.prototype = {
336
378
  var d = this.currentDelta();
337
379
  pos[0] -= d[0]; pos[1] -= d[1];
338
380
 
339
- var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this));
381
+ if(this.options.scroll && (this.options.scroll != window)) {
382
+ pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
383
+ pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
384
+ }
385
+
386
+ var p = [0,1].map(function(i){
387
+ return (point[i]-pos[i]-this.offset[i])
388
+ }.bind(this));
340
389
 
341
390
  if(this.options.snap) {
342
391
  if(typeof this.options.snap == 'function') {
@@ -357,6 +406,67 @@ Draggable.prototype = {
357
406
  if((!this.options.constraint) || (this.options.constraint=='vertical'))
358
407
  style.top = p[1] + "px";
359
408
  if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
409
+ },
410
+
411
+ stopScrolling: function() {
412
+ if(this.scrollInterval) {
413
+ clearInterval(this.scrollInterval);
414
+ this.scrollInterval = null;
415
+ }
416
+ },
417
+
418
+ startScrolling: function(speed) {
419
+ this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
420
+ this.lastScrolled = new Date();
421
+ this.scrollInterval = setInterval(this.scroll.bind(this), 10);
422
+ },
423
+
424
+ scroll: function() {
425
+ var current = new Date();
426
+ var delta = current - this.lastScrolled;
427
+ this.lastScrolled = current;
428
+ if(this.options.scroll == window) {
429
+ with (this._getWindowScroll(this.options.scroll)) {
430
+ if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
431
+ var d = delta / 1000;
432
+ this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
433
+ }
434
+ }
435
+ } else {
436
+ this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
437
+ this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
438
+ }
439
+
440
+ Position.prepare();
441
+ Droppables.show(Draggables._lastPointer, this.element);
442
+ Draggables.notify('onDrag', this);
443
+ this.draw(Draggables._lastPointer);
444
+
445
+ if(this.options.change) this.options.change(this);
446
+ },
447
+
448
+ _getWindowScroll: function(w) {
449
+ var T, L, W, H;
450
+ with (w.document) {
451
+ if (w.document.documentElement && documentElement.scrollTop) {
452
+ T = documentElement.scrollTop;
453
+ L = documentElement.scrollLeft;
454
+ } else if (w.document.body) {
455
+ T = body.scrollTop;
456
+ L = body.scrollLeft;
457
+ }
458
+ if (w.innerWidth) {
459
+ W = w.innerWidth;
460
+ H = w.innerHeight;
461
+ } else if (w.document.documentElement && documentElement.clientWidth) {
462
+ W = documentElement.clientWidth;
463
+ H = documentElement.clientHeight;
464
+ } else {
465
+ W = body.offsetWidth;
466
+ H = body.offsetHeight
467
+ }
468
+ }
469
+ return { top: T, left: L, width: W, height: H };
360
470
  }
361
471
  }
362
472
 
@@ -413,7 +523,10 @@ var Sortable = {
413
523
  only: false,
414
524
  hoverclass: null,
415
525
  ghosting: false,
416
- format: null,
526
+ scroll: false,
527
+ scrollSensitivity: 20,
528
+ scrollSpeed: 15,
529
+ format: /^[^_]*_(.*)$/,
417
530
  onChange: Prototype.emptyFunction,
418
531
  onUpdate: Prototype.emptyFunction
419
532
  }, arguments[1] || {});
@@ -424,6 +537,9 @@ var Sortable = {
424
537
  // build options for the draggables
425
538
  var options_for_draggable = {
426
539
  revert: true,
540
+ scroll: options.scroll,
541
+ scrollSpeed: options.scrollSpeed,
542
+ scrollSensitivity: options.scrollSensitivity,
427
543
  ghosting: options.ghosting,
428
544
  constraint: options.constraint,
429
545
  handle: options.handle };
@@ -491,9 +607,10 @@ var Sortable = {
491
607
  findElements: function(element, options) {
492
608
  if(!element.hasChildNodes()) return null;
493
609
  var elements = [];
610
+ var only = options.only ? [options.only].flatten() : null;
494
611
  $A(element.childNodes).each( function(e) {
495
612
  if(e.tagName && e.tagName.toUpperCase()==options.tag.toUpperCase() &&
496
- (!options.only || (Element.hasClassName(e, options.only))))
613
+ (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
497
614
  elements.push(e);
498
615
  if(options.tree) {
499
616
  var grandchildren = this.findElements(e, options);
@@ -567,18 +684,41 @@ var Sortable = {
567
684
  Element.show(Sortable._marker);
568
685
  },
569
686
 
570
- serialize: function(element) {
687
+ sequence: function(element) {
571
688
  element = $(element);
572
- var sortableOptions = this.options(element);
573
- var options = Object.extend({
574
- tag: sortableOptions.tag,
575
- only: sortableOptions.only,
576
- name: element.id,
577
- format: sortableOptions.format || /^[^_]*_(.*)$/
578
- }, arguments[1] || {});
689
+ var options = Object.extend(this.options(element), arguments[1] || {});
690
+
579
691
  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("&");
692
+ return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
693
+ });
694
+ },
695
+
696
+ setSequence: function(element, new_sequence) {
697
+ element = $(element);
698
+ var options = Object.extend(this.options(element), arguments[2] || {});
699
+
700
+ var nodeMap = {};
701
+ this.findElements(element, options).each( function(n) {
702
+ if (n.id.match(options.format))
703
+ nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
704
+ n.parentNode.removeChild(n);
705
+ });
706
+
707
+ new_sequence.each(function(ident) {
708
+ var n = nodeMap[ident];
709
+ if (n) {
710
+ n[1].appendChild(n[0]);
711
+ delete nodeMap[ident];
712
+ }
713
+ });
714
+ },
715
+
716
+ serialize: function(element) {
717
+ element = $(element);
718
+ var name = encodeURIComponent(
719
+ (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
720
+ return Sortable.sequence(element, arguments[1]).map( function(item) {
721
+ return name + "[]=" + encodeURIComponent(item);
722
+ }).join('&');
583
723
  }
584
- }
724
+ }