actionpack 1.11.0 → 1.11.1
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.
- data/CHANGELOG +45 -0
- data/lib/action_controller/assertions.rb +1 -1
- data/lib/action_controller/base.rb +24 -12
- data/lib/action_controller/caching.rb +1 -0
- data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +1 -0
- data/lib/action_controller/cgi_process.rb +44 -27
- data/lib/action_controller/components.rb +2 -2
- data/lib/action_controller/flash.rb +16 -7
- data/lib/action_controller/helpers.rb +12 -1
- data/lib/action_controller/layout.rb +3 -1
- data/lib/action_controller/macros/in_place_editing.rb +1 -1
- data/lib/action_controller/request.rb +20 -16
- data/lib/action_controller/session/active_record_store.rb +57 -51
- data/lib/action_controller/templates/rescues/_request_and_response.rhtml +0 -2
- data/lib/action_controller/vendor/html-scanner/html/node.rb +1 -1
- data/lib/action_controller/vendor/html-scanner/html/node.rb.rej +17 -0
- data/lib/action_pack/version.rb +2 -2
- data/lib/action_view/base.rb +4 -5
- data/lib/action_view/helpers/asset_tag_helper.rb +2 -2
- data/lib/action_view/helpers/form_options_helper.rb +1 -1
- data/lib/action_view/helpers/form_tag_helper.rb +1 -1
- data/lib/action_view/helpers/java_script_macros_helper.rb +11 -9
- data/lib/action_view/helpers/javascripts/controls.js +30 -1
- data/lib/action_view/helpers/javascripts/dragdrop.js +37 -17
- data/lib/action_view/helpers/javascripts/effects.js +3 -91
- data/lib/action_view/helpers/javascripts/prototype.js +109 -67
- data/lib/action_view/helpers/text_helper.rb +30 -9
- data/rakefile +2 -2
- data/test/controller/active_record_assertions_test.rb +1 -0
- data/test/controller/active_record_store_test.rb +12 -6
- data/test/controller/flash_test.rb +1 -1
- data/test/controller/helper_test.rb +21 -5
- data/test/controller/new_render_test.rb +31 -0
- data/test/controller/request_test.rb +5 -0
- data/test/fixtures/helpers/fun/pdf_helper.rb +3 -0
- data/test/fixtures/test/render_file_with_ivar.rhtml +1 -0
- data/test/fixtures/test/render_file_with_locals.rhtml +1 -0
- data/test/template/form_options_helper_test.rb +16 -6
- data/test/template/text_helper_test.rb +7 -0
- metadata +7 -3
@@ -5,33 +5,42 @@ require 'base64'
|
|
5
5
|
|
6
6
|
class CGI
|
7
7
|
class Session
|
8
|
-
# Return this session's underlying Session
|
8
|
+
# Return this session's underlying Session instance. Useful for the DB-backed session stores.
|
9
9
|
def model
|
10
|
-
@dbman.model
|
10
|
+
@dbman.model if @dbman
|
11
11
|
end
|
12
12
|
|
13
|
-
# Proxy missing methods to the underlying Session model.
|
14
|
-
def method_missing(method, *args, &block)
|
15
|
-
if model then model.send(method, *args, &block) else super end
|
16
|
-
end
|
17
13
|
|
18
|
-
# A session store backed by an Active Record class.
|
14
|
+
# A session store backed by an Active Record class. A default class is
|
15
|
+
# provided, but any object duck-typing to an Active Record +Session+ class
|
16
|
+
# with text +session_id+ and +data+ attributes is sufficient.
|
19
17
|
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
18
|
+
# The default assumes a +sessions+ tables with columns:
|
19
|
+
# +id+ (numeric primary key),
|
20
|
+
# +session_id+ (text, or longtext if your session data exceeds 65K), and
|
21
|
+
# +data+ (text or longtext; careful if your session data exceeds 65KB).
|
22
|
+
# The +session_id+ column should always be indexed for speedy lookups.
|
23
|
+
# Session data is marshaled to the +data+ column in Base64 format.
|
24
|
+
# If the data you write is larger than the column's size limit,
|
25
|
+
# ActionController::SessionOverflowError will be raised.
|
23
26
|
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
27
|
+
# You may configure the table name, primary key, and data column.
|
28
|
+
# For example, at the end of config/environment.rb:
|
29
|
+
# CGI::Session::ActiveRecordStore::Session.table_name = 'legacy_session_table'
|
30
|
+
# CGI::Session::ActiveRecordStore::Session.primary_key = 'session_id'
|
31
|
+
# CGI::Session::ActiveRecordStore::Session.data_column_name = 'legacy_session_data'
|
32
|
+
# Note that setting the primary key to the session_id frees you from
|
33
|
+
# having a separate id column if you don't want it. However, you must
|
34
|
+
# set session.model.id = session.session_id by hand! A before_filter
|
35
|
+
# on ApplicationController is a good place.
|
28
36
|
#
|
29
37
|
# Since the default class is a simple Active Record, you get timestamps
|
30
38
|
# for free if you add +created_at+ and +updated_at+ datetime columns to
|
31
39
|
# the +sessions+ table, making periodic session expiration a snap.
|
32
40
|
#
|
33
|
-
# You may provide your own session class, whether a
|
34
|
-
# Active Record or a bare-metal high-performance SQL
|
41
|
+
# You may provide your own session class implementation, whether a
|
42
|
+
# feature-packed Active Record or a bare-metal high-performance SQL
|
43
|
+
# store, by setting
|
35
44
|
# +CGI::Session::ActiveRecordStore.session_class = MySessionClass+
|
36
45
|
# You must implement these methods:
|
37
46
|
# self.find_by_session_id(session_id)
|
@@ -41,28 +50,28 @@ class CGI
|
|
41
50
|
# save
|
42
51
|
# destroy
|
43
52
|
#
|
44
|
-
# The
|
53
|
+
# The example SqlBypass class is a generic SQL session store. You may
|
45
54
|
# use it as a basis for high-performance database-specific stores.
|
46
|
-
#
|
47
|
-
# If the data you are attempting to write to the +data+ column is larger
|
48
|
-
# than the column's size limit, ActionController::SessionOverflowError
|
49
|
-
# will be raised.
|
50
55
|
class ActiveRecordStore
|
51
56
|
# The default Active Record class.
|
52
57
|
class Session < ActiveRecord::Base
|
53
|
-
|
58
|
+
# Customizable data column name. Defaults to 'data'.
|
59
|
+
cattr_accessor :data_column_name
|
60
|
+
self.data_column_name = 'data'
|
61
|
+
|
62
|
+
# Don't try to save if we haven't loaded the session.
|
63
|
+
before_update :loaded?
|
54
64
|
before_save :marshal_data!
|
55
|
-
before_save :
|
56
|
-
|
57
|
-
class << self
|
65
|
+
before_save :raise_on_session_data_overflow!
|
58
66
|
|
67
|
+
class << self
|
59
68
|
# Don't try to reload ARStore::Session in dev mode.
|
60
69
|
def reloadable? #:nodoc:
|
61
70
|
false
|
62
71
|
end
|
63
|
-
|
72
|
+
|
64
73
|
def data_column_size_limit
|
65
|
-
columns_hash[
|
74
|
+
@data_column_size_limit ||= columns_hash[@@data_column_name].limit
|
66
75
|
end
|
67
76
|
|
68
77
|
# Hook to set up sessid compatibility.
|
@@ -71,15 +80,15 @@ class CGI
|
|
71
80
|
find_by_session_id(session_id)
|
72
81
|
end
|
73
82
|
|
74
|
-
def marshal(data)
|
75
|
-
def unmarshal(data)
|
83
|
+
def marshal(data) Base64.encode64(Marshal.dump(data)) if data end
|
84
|
+
def unmarshal(data) Marshal.load(Base64.decode64(data)) if data end
|
76
85
|
|
77
86
|
def create_table!
|
78
87
|
connection.execute <<-end_sql
|
79
88
|
CREATE TABLE #{table_name} (
|
80
89
|
id INTEGER PRIMARY KEY,
|
81
90
|
#{connection.quote_column_name('session_id')} TEXT UNIQUE,
|
82
|
-
#{connection.quote_column_name(
|
91
|
+
#{connection.quote_column_name(@@data_column_name)} TEXT(255)
|
83
92
|
)
|
84
93
|
end_sql
|
85
94
|
end
|
@@ -110,22 +119,16 @@ class CGI
|
|
110
119
|
|
111
120
|
# Lazy-unmarshal session state.
|
112
121
|
def data
|
113
|
-
|
114
|
-
case data = read_attribute('data')
|
115
|
-
when String
|
116
|
-
@data = self.class.unmarshal(data)
|
117
|
-
else
|
118
|
-
@data = data || {}
|
119
|
-
end
|
120
|
-
end
|
121
|
-
@data
|
122
|
+
@data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
|
122
123
|
end
|
123
124
|
|
124
125
|
private
|
126
|
+
attr_writer :data
|
127
|
+
|
125
128
|
def marshal_data!
|
126
|
-
write_attribute(
|
129
|
+
write_attribute(@@data_column_name, self.class.marshal(self.data))
|
127
130
|
end
|
128
|
-
|
131
|
+
|
129
132
|
# Has the session been loaded yet?
|
130
133
|
def loaded?
|
131
134
|
!! @data
|
@@ -134,15 +137,17 @@ class CGI
|
|
134
137
|
# Ensures that the data about to be stored in the database is not
|
135
138
|
# larger than the data storage column. Raises
|
136
139
|
# ActionController::SessionOverflowError.
|
137
|
-
def
|
138
|
-
|
139
|
-
|
140
|
+
def raise_on_session_data_overflow!
|
141
|
+
limit = self.class.data_column_size_limit
|
142
|
+
if loaded? and limit and read_attribute(@@data_column_name).size > limit
|
143
|
+
raise ActionController::SessionOverflowError
|
144
|
+
end
|
140
145
|
end
|
141
|
-
|
142
146
|
end
|
143
147
|
|
144
148
|
# A barebones session store which duck-types with the default session
|
145
|
-
# store but bypasses Active Record and issues SQL directly.
|
149
|
+
# store but bypasses Active Record and issues SQL directly. This is
|
150
|
+
# an example session model class meant as a basis for your own classes.
|
146
151
|
#
|
147
152
|
# The database connection, table name, and session id and data columns
|
148
153
|
# are configurable class attributes. Marshaling and unmarshaling
|
@@ -182,8 +187,8 @@ class CGI
|
|
182
187
|
end
|
183
188
|
end
|
184
189
|
|
185
|
-
def marshal(data)
|
186
|
-
def unmarshal(data)
|
190
|
+
def marshal(data) Base64.encode64(Marshal.dump(data)) if data end
|
191
|
+
def unmarshal(data) Marshal.load(Base64.decode64(data)) if data end
|
187
192
|
|
188
193
|
def create_table!
|
189
194
|
@@connection.execute <<-end_sql
|
@@ -219,7 +224,7 @@ class CGI
|
|
219
224
|
def data
|
220
225
|
unless @data
|
221
226
|
if @marshaled_data
|
222
|
-
@data, @marshaled_data = self.class.unmarshal(@marshaled_data), nil
|
227
|
+
@data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
|
223
228
|
else
|
224
229
|
@data = {}
|
225
230
|
end
|
@@ -257,13 +262,13 @@ class CGI
|
|
257
262
|
end_sql
|
258
263
|
end
|
259
264
|
end
|
260
|
-
|
261
265
|
end
|
262
266
|
|
267
|
+
|
263
268
|
# The class used for session storage. Defaults to
|
264
269
|
# CGI::Session::ActiveRecordStore::Session.
|
265
270
|
cattr_accessor :session_class
|
266
|
-
|
271
|
+
self.session_class = Session
|
267
272
|
|
268
273
|
# Find or instantiate a session given a CGI::Session.
|
269
274
|
def initialize(session, option = nil)
|
@@ -273,6 +278,7 @@ class CGI
|
|
273
278
|
raise CGI::Session::NoSession, 'uninitialized session'
|
274
279
|
end
|
275
280
|
@session = @@session_class.new(:session_id => session_id, :data => {})
|
281
|
+
@session.save
|
276
282
|
end
|
277
283
|
end
|
278
284
|
|
@@ -31,8 +31,6 @@
|
|
31
31
|
request_parameters_without_action.delete("controller")
|
32
32
|
|
33
33
|
request_dump = request_parameters_without_action.inspect.gsub(/,/, ",\n")
|
34
|
-
session_dump = @request.session.instance_variable_get("@data").inspect.gsub(/,/, ",\n")
|
35
|
-
response_dump = @response.inspect.gsub(/,/, ",\n")
|
36
34
|
%>
|
37
35
|
|
38
36
|
<h2 style="margin-top: 30px">Request</h2>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
***************
|
2
|
+
*** 264,270 ****
|
3
|
+
|
4
|
+
# A CDATA node is simply a text node with a specialized way of displaying
|
5
|
+
# itself.
|
6
|
+
- class CDATA < Text
|
7
|
+
def to_s
|
8
|
+
"<![CDATA[#{super}]>"
|
9
|
+
end
|
10
|
+
--- 264,270 ----
|
11
|
+
|
12
|
+
# A CDATA node is simply a text node with a specialized way of displaying
|
13
|
+
# itself.
|
14
|
+
+ class CDATA < Text #:nodoc:
|
15
|
+
def to_s
|
16
|
+
"<![CDATA[#{super}]>"
|
17
|
+
end
|
data/lib/action_pack/version.rb
CHANGED
data/lib/action_view/base.rb
CHANGED
@@ -157,13 +157,12 @@ module ActionView #:nodoc:
|
|
157
157
|
|
158
158
|
class ObjectWrapper < Struct.new(:value) #:nodoc:
|
159
159
|
end
|
160
|
-
|
160
|
+
|
161
161
|
def self.load_helpers(helper_dir)#:nodoc:
|
162
162
|
Dir.foreach(helper_dir) do |helper_file|
|
163
|
-
next unless helper_file =~
|
164
|
-
require helper_dir
|
165
|
-
helper_module_name =
|
166
|
-
|
163
|
+
next unless helper_file =~ /^([a-z][a-z_]*_helper).rb$/
|
164
|
+
require File.join(helper_dir, $1)
|
165
|
+
helper_module_name = $1.camelize
|
167
166
|
class_eval("include ActionView::Helpers::#{helper_module_name}") if Helpers.const_defined?(helper_module_name)
|
168
167
|
end
|
169
168
|
end
|
@@ -35,7 +35,7 @@ module ActionView
|
|
35
35
|
compute_public_path(source, 'javascripts', 'js')
|
36
36
|
end
|
37
37
|
|
38
|
-
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls']
|
38
|
+
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
|
39
39
|
@@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
|
40
40
|
|
41
41
|
# Returns a script include tag per source given as argument. Examples:
|
@@ -60,7 +60,7 @@ module ActionView
|
|
60
60
|
def javascript_include_tag(*sources)
|
61
61
|
options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
|
62
62
|
if sources.first == :defaults
|
63
|
-
sources = @@javascript_default_sources
|
63
|
+
sources = @@javascript_default_sources.dup
|
64
64
|
if defined?(RAILS_ROOT) and File.exists?("#{RAILS_ROOT}/public/javascripts/application.js")
|
65
65
|
sources << 'application'
|
66
66
|
end
|
@@ -109,7 +109,7 @@ module ActionView
|
|
109
109
|
container = container.to_a if Hash === container
|
110
110
|
|
111
111
|
options_for_select = container.inject([]) do |options, element|
|
112
|
-
if element.is_a?(
|
112
|
+
if !element.is_a?(String) and element.respond_to?(:first) and element.respond_to?(:last)
|
113
113
|
is_selected = ( (selected.respond_to?(:include?) ? selected.include?(element.last) : element.last == selected) )
|
114
114
|
is_selected = ( (selected.respond_to?(:include?) && !selected.is_a?(String) ? selected.include?(element.last) : element.last == selected) )
|
115
115
|
if is_selected
|
@@ -95,7 +95,7 @@ module ActionView
|
|
95
95
|
# Options:
|
96
96
|
# * <tt>:size</tt> - A string specifying the dimensions of the textarea.
|
97
97
|
# # Outputs <textarea name="body" id="body" cols="25" rows="10"></textarea>
|
98
|
-
# <%= text_area_tag "body", nil, :size => 25x10 %>
|
98
|
+
# <%= text_area_tag "body", nil, :size => "25x10" %>
|
99
99
|
def text_area_tag(name, content = nil, options = {})
|
100
100
|
options = options.stringify_keys
|
101
101
|
if options["size"]
|
@@ -28,21 +28,23 @@ module ActionView
|
|
28
28
|
# be sent after the user presses "ok".
|
29
29
|
#
|
30
30
|
# Addtional +options+ are:
|
31
|
-
# <tt>:rows</tt>::
|
32
|
-
# <tt>:cancel_text</tt>::
|
33
|
-
# <tt>:
|
34
|
-
# <tt>:
|
35
|
-
# <tt>:
|
36
|
-
#
|
31
|
+
# <tt>:rows</tt>:: Number of rows (more than 1 will use a TEXTAREA)
|
32
|
+
# <tt>:cancel_text</tt>:: The text on the cancel link. (default: "cancel")
|
33
|
+
# <tt>:save_text</tt>:: The text on the save link. (default: "ok")
|
34
|
+
# <tt>:external_control</tt>:: The id of an external control used to enter edit mode.
|
35
|
+
# <tt>:options</tt>:: Pass through options to the AJAX call (see prototype's Ajax.Updater)
|
36
|
+
# <tt>:with</tt>:: JavaScript snippet that should return what is to be sent
|
37
|
+
# in the AJAX call, +form+ is an implicit parameter
|
37
38
|
def in_place_editor(field_id, options = {})
|
38
39
|
function = "new Ajax.InPlaceEditor("
|
39
40
|
function << "'#{field_id}', "
|
40
41
|
function << "'#{url_for(options[:url])}'"
|
41
42
|
|
42
43
|
js_options = {}
|
43
|
-
js_options['cancelText'] = options[:cancel_text] if options[:cancel_text]
|
44
|
-
js_options['okText'] = options[:save_text] if options[:save_text]
|
44
|
+
js_options['cancelText'] = %('#{options[:cancel_text]}') if options[:cancel_text]
|
45
|
+
js_options['okText'] = %('#{options[:save_text]}') if options[:save_text]
|
45
46
|
js_options['rows'] = options[:rows] if options[:rows]
|
47
|
+
js_options['externalControl'] = options[:external_control] if options[:external_control]
|
46
48
|
js_options['ajaxOptions'] = options[:options] if options[:options]
|
47
49
|
js_options['callback'] = "function(form) { return #{options[:with]} }" if options[:with]
|
48
50
|
function << (', ' + options_for_javascript(js_options)) unless js_options.empty?
|
@@ -59,7 +61,7 @@ module ActionView
|
|
59
61
|
tag = ::ActionView::Helpers::InstanceTag.new(object, method, self)
|
60
62
|
tag_options = {:tag => "span", :id => "#{object}_#{method}_#{tag.object.id}_in_place_editor", :class => "in_place_editor_field"}.merge!(tag_options)
|
61
63
|
in_place_editor_options[:url] = in_place_editor_options[:url] || url_for({ :action => "set_#{object}_#{method}", :id => tag.object.id })
|
62
|
-
tag.to_content_tag(tag_options
|
64
|
+
tag.to_content_tag(tag_options.delete(:tag), tag_options) +
|
63
65
|
in_place_editor(tag_options[:id], in_place_editor_options)
|
64
66
|
end
|
65
67
|
|
@@ -80,7 +80,10 @@ Autocompleter.Base.prototype = {
|
|
80
80
|
|
81
81
|
show: function() {
|
82
82
|
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
|
83
|
-
if(!this.iefix &&
|
83
|
+
if(!this.iefix &&
|
84
|
+
(navigator.appVersion.indexOf('MSIE')>0) &&
|
85
|
+
(navigator.userAgent.indexOf('Opera')<0) &&
|
86
|
+
(Element.getStyle(this.update, 'position')=='absolute')) {
|
84
87
|
new Insertion.After(this.update,
|
85
88
|
'<iframe id="' + this.update.id + '_iefix" '+
|
86
89
|
'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
|
@@ -718,4 +721,30 @@ Ajax.InPlaceEditor.prototype = {
|
|
718
721
|
Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
|
719
722
|
}
|
720
723
|
}
|
724
|
+
};
|
725
|
+
|
726
|
+
// Delayed observer, like Form.Element.Observer,
|
727
|
+
// but waits for delay after last key input
|
728
|
+
// Ideal for live-search fields
|
729
|
+
|
730
|
+
Form.Element.DelayedObserver = Class.create();
|
731
|
+
Form.Element.DelayedObserver.prototype = {
|
732
|
+
initialize: function(element, delay, callback) {
|
733
|
+
this.delay = delay || 0.5;
|
734
|
+
this.element = $(element);
|
735
|
+
this.callback = callback;
|
736
|
+
this.timer = null;
|
737
|
+
this.lastValue = $F(this.element);
|
738
|
+
Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
|
739
|
+
},
|
740
|
+
delayedListener: function(event) {
|
741
|
+
if(this.lastValue == $F(this.element)) return;
|
742
|
+
if(this.timer) clearTimeout(this.timer);
|
743
|
+
this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
|
744
|
+
this.lastValue = $F(this.element);
|
745
|
+
},
|
746
|
+
onTimerEvent: function() {
|
747
|
+
this.timer = null;
|
748
|
+
this.callback(this.element, $F(this.element));
|
749
|
+
}
|
721
750
|
};
|
@@ -1,7 +1,5 @@
|
|
1
1
|
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
2
2
|
//
|
3
|
-
// Element.Class part Copyright (c) 2005 by Rick Olson
|
4
|
-
//
|
5
3
|
// See scriptaculous.js for full license.
|
6
4
|
|
7
5
|
/*--------------------------------------------------------------------------*/
|
@@ -31,6 +29,8 @@ var Droppables = {
|
|
31
29
|
options._containers.push($(containment));
|
32
30
|
}
|
33
31
|
}
|
32
|
+
|
33
|
+
if(options.accept) options.accept = [options.accept].flatten();
|
34
34
|
|
35
35
|
Element.makePositioned(element); // fix IE
|
36
36
|
options.element = element;
|
@@ -49,20 +49,21 @@ var Droppables = {
|
|
49
49
|
((!drop._containers) ||
|
50
50
|
this.isContained(element, drop)) &&
|
51
51
|
((!drop.accept) ||
|
52
|
-
(Element.
|
52
|
+
(Element.classNames(element).detect(
|
53
|
+
function(v) { return drop.accept.include(v) } ) )) &&
|
53
54
|
Position.within(drop.element, pX, pY) );
|
54
55
|
},
|
55
56
|
|
56
57
|
deactivate: function(drop) {
|
57
58
|
if(drop.hoverclass)
|
58
|
-
Element.
|
59
|
+
Element.removeClassName(drop.element, drop.hoverclass);
|
59
60
|
this.last_active = null;
|
60
61
|
},
|
61
62
|
|
62
63
|
activate: function(drop) {
|
63
64
|
if(this.last_active) this.deactivate(this.last_active);
|
64
65
|
if(drop.hoverclass)
|
65
|
-
Element.
|
66
|
+
Element.addClassName(drop.element, drop.hoverclass);
|
66
67
|
this.last_active = drop;
|
67
68
|
},
|
68
69
|
|
@@ -105,13 +106,25 @@ var Droppables = {
|
|
105
106
|
var Draggables = {
|
106
107
|
observers: [],
|
107
108
|
addObserver: function(observer) {
|
108
|
-
this.observers.push(observer);
|
109
|
+
this.observers.push(observer);
|
110
|
+
this._cacheObserverCallbacks();
|
109
111
|
},
|
110
|
-
removeObserver: function(element) { // element instead of
|
112
|
+
removeObserver: function(element) { // element instead of observer fixes mem leaks
|
111
113
|
this.observers = this.observers.reject( function(o) { return o.element==element });
|
114
|
+
this._cacheObserverCallbacks();
|
115
|
+
},
|
116
|
+
notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
|
117
|
+
if(this[eventName+'Count'] > 0)
|
118
|
+
this.observers.each( function(o) {
|
119
|
+
if(o[eventName]) o[eventName](eventName, draggable, event);
|
120
|
+
});
|
112
121
|
},
|
113
|
-
|
114
|
-
|
122
|
+
_cacheObserverCallbacks: function() {
|
123
|
+
['onStart','onEnd','onDrag'].each( function(eventName) {
|
124
|
+
Draggables[eventName+'Count'] = Draggables.observers.select(
|
125
|
+
function(o) { return o[eventName]; }
|
126
|
+
).length;
|
127
|
+
});
|
115
128
|
}
|
116
129
|
}
|
117
130
|
|
@@ -138,7 +151,7 @@ Draggable.prototype = {
|
|
138
151
|
|
139
152
|
this.element = $(element);
|
140
153
|
if(options.handle && (typeof options.handle == 'string'))
|
141
|
-
this.handle = Element.
|
154
|
+
this.handle = Element.childrenWithClassName(this.element, options.handle)[0];
|
142
155
|
|
143
156
|
if(!this.handle) this.handle = $(options.handle);
|
144
157
|
if(!this.handle) this.handle = this.element;
|
@@ -219,7 +232,7 @@ Draggable.prototype = {
|
|
219
232
|
}
|
220
233
|
|
221
234
|
if(success) Droppables.fire(event, this.element);
|
222
|
-
Draggables.notify('onEnd', this);
|
235
|
+
Draggables.notify('onEnd', this, event);
|
223
236
|
|
224
237
|
var revert = this.options.revert;
|
225
238
|
if(revert && typeof revert == 'function') revert = revert(this.element);
|
@@ -290,11 +303,12 @@ Draggable.prototype = {
|
|
290
303
|
this.element.parentNode.insertBefore(this._clone, this.element);
|
291
304
|
}
|
292
305
|
|
293
|
-
Draggables.notify('onStart', this);
|
306
|
+
Draggables.notify('onStart', this, event);
|
294
307
|
if(this.options.starteffect) this.options.starteffect(this.element);
|
295
308
|
}
|
296
309
|
|
297
310
|
Droppables.show(event, this.element);
|
311
|
+
Draggables.notify('onDrag', this, event);
|
298
312
|
this.draw(event);
|
299
313
|
if(this.options.change) this.options.change(this);
|
300
314
|
|
@@ -413,7 +427,7 @@ var Sortable = {
|
|
413
427
|
(this.findElements(element, options) || []).each( function(e) {
|
414
428
|
// handles are per-draggable
|
415
429
|
var handle = options.handle ?
|
416
|
-
Element.
|
430
|
+
Element.childrenWithClassName(e, options.handle)[0] : e;
|
417
431
|
options.draggables.push(
|
418
432
|
new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
|
419
433
|
Droppables.add(e, options_for_droppable);
|
@@ -434,7 +448,7 @@ var Sortable = {
|
|
434
448
|
var elements = [];
|
435
449
|
$A(element.childNodes).each( function(e) {
|
436
450
|
if(e.tagName && e.tagName==options.tag.toUpperCase() &&
|
437
|
-
(!options.only || (Element.
|
451
|
+
(!options.only || (Element.hasClassName(e, options.only))))
|
438
452
|
elements.push(e);
|
439
453
|
if(options.tree) {
|
440
454
|
var grandchildren = this.findElements(e, options);
|
@@ -491,14 +505,20 @@ var Sortable = {
|
|
491
505
|
if(!Sortable._marker) {
|
492
506
|
Sortable._marker = $('dropmarker') || document.createElement('DIV');
|
493
507
|
Element.hide(Sortable._marker);
|
494
|
-
Element.
|
508
|
+
Element.addClassName(Sortable._marker, 'dropmarker');
|
495
509
|
Sortable._marker.style.position = 'absolute';
|
496
510
|
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
|
497
511
|
}
|
498
512
|
var offsets = Position.cumulativeOffset(dropon);
|
499
|
-
Sortable._marker.style.top = offsets[1] + 'px';
|
500
|
-
if(position=='after') Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
|
501
513
|
Sortable._marker.style.left = offsets[0] + 'px';
|
514
|
+
Sortable._marker.style.top = offsets[1] + 'px';
|
515
|
+
|
516
|
+
if(position=='after')
|
517
|
+
if(sortable.overlap == 'horizontal')
|
518
|
+
Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
|
519
|
+
else
|
520
|
+
Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
|
521
|
+
|
502
522
|
Element.show(Sortable._marker);
|
503
523
|
},
|
504
524
|
|