actionpack 1.9.0 → 1.9.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 +38 -0
- data/README +1 -1
- data/lib/action_controller/auto_complete.rb +2 -2
- data/lib/action_controller/base.rb +21 -13
- data/lib/action_controller/cgi_process.rb +16 -23
- data/lib/action_controller/routing.rb +17 -9
- data/lib/action_controller/session/active_record_store.rb +4 -4
- data/lib/action_controller/test_process.rb +37 -1
- data/lib/action_view.rb +2 -8
- data/lib/action_view/helpers/javascript_helper.rb +20 -12
- data/lib/action_view/helpers/javascripts/controls.js +254 -69
- data/lib/action_view/helpers/javascripts/dragdrop.js +131 -70
- data/lib/action_view/helpers/javascripts/effects.js +140 -98
- data/lib/action_view/helpers/javascripts/prototype.js +50 -39
- data/lib/action_view/helpers/tag_helper.rb +3 -2
- data/lib/action_view/helpers/text_helper.rb +1 -1
- data/lib/action_view/helpers/url_helper.rb +1 -1
- data/rakefile +2 -2
- data/test/controller/active_record_store_test.rb +9 -3
- data/test/controller/filters_test.rb +21 -0
- data/test/controller/new_render_test.rb +27 -0
- data/test/controller/routing_test.rb +70 -1
- data/test/controller/test_test.rb +37 -3
- data/test/template/form_helper_test.rb +3 -3
- data/test/template/form_tag_helper_test.rb +2 -2
- data/test/template/{javascript_helper.rb → javascript_helper_test.rb} +24 -20
- data/test/template/tag_helper_test.rb +8 -0
- metadata +4 -4
@@ -1,4 +1,5 @@
|
|
1
1
|
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
2
|
+
// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
|
2
3
|
//
|
3
4
|
// Permission is hereby granted, free of charge, to any person obtaining
|
4
5
|
// a copy of this software and associated documentation files (the
|
@@ -19,7 +20,6 @@
|
|
19
20
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
21
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
22
|
|
22
|
-
|
23
23
|
Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
|
24
24
|
var children = $(element).childNodes;
|
25
25
|
var text = "";
|
@@ -37,42 +37,70 @@ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
|
|
37
37
|
return text;
|
38
38
|
}
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
// Autocompleter.Base handles all the autocompletion functionality
|
41
|
+
// that's independent of the data source for autocompletion. This
|
42
|
+
// includes drawing the autocompletion menu, observing keyboard
|
43
|
+
// and mouse events, and similar.
|
44
|
+
//
|
45
|
+
// Specific autocompleters need to provide, at the very least,
|
46
|
+
// a getUpdatedChoices function that will be invoked every time
|
47
|
+
// the text inside the monitored textbox changes. This method
|
48
|
+
// should get the text for which to provide autocompletion by
|
49
|
+
// invoking this.getEntry(), NOT by directly accessing
|
50
|
+
// this.element.value. This is to allow incremental tokenized
|
51
|
+
// autocompletion. Specific auto-completion logic (AJAX, etc)
|
52
|
+
// belongs in getUpdatedChoices.
|
53
|
+
//
|
54
|
+
// Tokenized incremental autocompletion is enabled automatically
|
55
|
+
// when an autocompleter is instantiated with the 'tokens' option
|
56
|
+
// in the options parameter, e.g.:
|
57
|
+
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
|
58
|
+
// will incrementally autocomplete with a comma as the token.
|
59
|
+
// Additionally, ',' in the above example can be replaced with
|
60
|
+
// a token array, e.g. { tokens: new Array (',', '\n') } which
|
61
|
+
// enables autocompletion on multiple tokens. This is most
|
62
|
+
// useful when one of the tokens is \n (a newline), as it
|
63
|
+
// allows smart autocompletion after linebreaks.
|
64
|
+
|
65
|
+
var Autocompleter = {}
|
66
|
+
Autocompleter.Base = function() {};
|
67
|
+
Autocompleter.Base.prototype = {
|
68
|
+
base_initialize: function(element, update, options) {
|
43
69
|
this.element = $(element);
|
44
70
|
this.update = $(update);
|
45
71
|
this.has_focus = false;
|
46
72
|
this.changed = false;
|
47
73
|
this.active = false;
|
48
74
|
this.index = 0;
|
49
|
-
this.entry_count = 0;
|
50
|
-
this.url = url;
|
75
|
+
this.entry_count = 0;
|
51
76
|
|
52
|
-
this.setOptions
|
53
|
-
|
54
|
-
|
77
|
+
if (this.setOptions)
|
78
|
+
this.setOptions(options);
|
79
|
+
else
|
80
|
+
this.options = {}
|
81
|
+
|
82
|
+
this.options.tokens = this.options.tokens || new Array();
|
55
83
|
this.options.frequency = this.options.frequency || 0.4;
|
56
84
|
this.options.min_chars = this.options.min_chars || 1;
|
57
|
-
this.options.
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
if(!update.style.position || update.style.position=='absolute') {
|
62
|
-
update.style.position = 'absolute';
|
85
|
+
this.options.onShow = this.options.onShow ||
|
86
|
+
function(element, update){
|
87
|
+
if(!update.style.position || update.style.position=='absolute') {
|
88
|
+
update.style.position = 'absolute';
|
63
89
|
var offsets = Position.cumulativeOffset(element);
|
64
90
|
update.style.left = offsets[0] + 'px';
|
65
91
|
update.style.top = (offsets[1] + element.offsetHeight) + 'px';
|
66
92
|
update.style.width = element.offsetWidth + 'px';
|
67
|
-
|
68
|
-
|
69
|
-
|
93
|
+
}
|
94
|
+
new Effect.Appear(update,{duration:0.15});
|
95
|
+
};
|
70
96
|
this.options.onHide = this.options.onHide ||
|
71
|
-
|
72
|
-
|
97
|
+
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
|
73
98
|
|
74
99
|
if(this.options.indicator)
|
75
100
|
this.indicator = $(this.options.indicator);
|
101
|
+
|
102
|
+
if (typeof(this.options.tokens) == 'string')
|
103
|
+
this.options.tokens = new Array(this.options.tokens);
|
76
104
|
|
77
105
|
this.observer = null;
|
78
106
|
|
@@ -81,14 +109,14 @@ Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({
|
|
81
109
|
Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
|
82
110
|
Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
|
83
111
|
},
|
84
|
-
|
112
|
+
|
85
113
|
show: function() {
|
86
114
|
if(this.update.style.display=='none') this.options.onShow(this.element, this.update);
|
87
115
|
if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') {
|
88
116
|
new Insertion.After(this.update,
|
89
117
|
'<iframe id="' + this.update.id + '_iefix" '+
|
90
|
-
'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(
|
91
|
-
'src="javascript
|
118
|
+
'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
|
119
|
+
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
|
92
120
|
this.iefix = $(this.update.id+'_iefix');
|
93
121
|
}
|
94
122
|
if(this.iefix) {
|
@@ -111,51 +139,7 @@ Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({
|
|
111
139
|
stopIndicator: function() {
|
112
140
|
if(this.indicator) Element.hide(this.indicator);
|
113
141
|
},
|
114
|
-
|
115
|
-
onObserverEvent: function() {
|
116
|
-
this.changed = false;
|
117
|
-
if(this.element.value.length>=this.options.min_chars) {
|
118
|
-
this.startIndicator();
|
119
|
-
this.options.parameters = this.options.callback ?
|
120
|
-
this.options.callback(this.element, Form.Element.getValue(this.element)) :
|
121
|
-
Form.Element.serialize(this.element);
|
122
|
-
new Ajax.Request(this.url, this.options);
|
123
|
-
} else {
|
124
|
-
this.active = false;
|
125
|
-
this.hide();
|
126
|
-
}
|
127
|
-
},
|
128
|
-
|
129
|
-
addObservers: function(element) {
|
130
|
-
Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
|
131
|
-
Event.observe(element, "click", this.onClick.bindAsEventListener(this));
|
132
|
-
},
|
133
|
-
|
134
|
-
onComplete: function(request) {
|
135
|
-
if(!this.changed && this.has_focus) {
|
136
|
-
this.update.innerHTML = request.responseText;
|
137
|
-
Element.cleanWhitespace(this.update);
|
138
|
-
Element.cleanWhitespace(this.update.firstChild);
|
139
142
|
|
140
|
-
if(this.update.firstChild && this.update.firstChild.childNodes) {
|
141
|
-
this.entry_count =
|
142
|
-
this.update.firstChild.childNodes.length;
|
143
|
-
for (var i = 0; i < this.entry_count; i++) {
|
144
|
-
entry = this.get_entry(i);
|
145
|
-
entry.autocompleteIndex = i;
|
146
|
-
this.addObservers(entry);
|
147
|
-
}
|
148
|
-
} else {
|
149
|
-
this.entry_count = 0;
|
150
|
-
}
|
151
|
-
|
152
|
-
this.stopIndicator();
|
153
|
-
|
154
|
-
this.index = 0;
|
155
|
-
this.render();
|
156
|
-
}
|
157
|
-
},
|
158
|
-
|
159
143
|
onKeyPress: function(event) {
|
160
144
|
if(this.active)
|
161
145
|
switch(event.keyCode) {
|
@@ -255,7 +239,208 @@ Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({
|
|
255
239
|
select_entry: function() {
|
256
240
|
this.active = false;
|
257
241
|
value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML();
|
258
|
-
this.
|
242
|
+
this.updateElement(value);
|
259
243
|
this.element.focus();
|
244
|
+
},
|
245
|
+
|
246
|
+
updateElement: function(value) {
|
247
|
+
var last_token_pos = this.findLastToken();
|
248
|
+
if (last_token_pos != -1) {
|
249
|
+
var new_value = this.element.value.substr(0, last_token_pos + 1);
|
250
|
+
var whitespace = this.element.value.substr(last_token_pos + 1).match(/^\s+/);
|
251
|
+
if (whitespace)
|
252
|
+
new_value += whitespace[0];
|
253
|
+
this.element.value = new_value + value;
|
254
|
+
} else {
|
255
|
+
this.element.value = value;
|
256
|
+
}
|
257
|
+
},
|
258
|
+
|
259
|
+
updateChoices: function(choices) {
|
260
|
+
if(!this.changed && this.has_focus) {
|
261
|
+
this.update.innerHTML = choices;
|
262
|
+
Element.cleanWhitespace(this.update);
|
263
|
+
Element.cleanWhitespace(this.update.firstChild);
|
264
|
+
|
265
|
+
if(this.update.firstChild && this.update.firstChild.childNodes) {
|
266
|
+
this.entry_count =
|
267
|
+
this.update.firstChild.childNodes.length;
|
268
|
+
for (var i = 0; i < this.entry_count; i++) {
|
269
|
+
entry = this.get_entry(i);
|
270
|
+
entry.autocompleteIndex = i;
|
271
|
+
this.addObservers(entry);
|
272
|
+
}
|
273
|
+
} else {
|
274
|
+
this.entry_count = 0;
|
275
|
+
}
|
276
|
+
|
277
|
+
this.stopIndicator();
|
278
|
+
|
279
|
+
this.index = 0;
|
280
|
+
this.render();
|
281
|
+
}
|
282
|
+
},
|
283
|
+
|
284
|
+
addObservers: function(element) {
|
285
|
+
Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
|
286
|
+
Event.observe(element, "click", this.onClick.bindAsEventListener(this));
|
287
|
+
},
|
288
|
+
|
289
|
+
onObserverEvent: function() {
|
290
|
+
this.changed = false;
|
291
|
+
if(this.getEntry().length>=this.options.min_chars) {
|
292
|
+
this.startIndicator();
|
293
|
+
this.getUpdatedChoices();
|
294
|
+
} else {
|
295
|
+
this.active = false;
|
296
|
+
this.hide();
|
297
|
+
}
|
298
|
+
},
|
299
|
+
|
300
|
+
getEntry: function() {
|
301
|
+
var token_pos = this.findLastToken();
|
302
|
+
if (token_pos != -1)
|
303
|
+
var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
|
304
|
+
else
|
305
|
+
var ret = this.element.value;
|
306
|
+
|
307
|
+
return /\n/.test(ret) ? '' : ret;
|
308
|
+
},
|
309
|
+
|
310
|
+
findLastToken: function() {
|
311
|
+
var last_token_pos = -1;
|
312
|
+
|
313
|
+
for (var i=0; i<this.options.tokens.length; i++) {
|
314
|
+
var this_token_pos = this.element.value.lastIndexOf(this.options.tokens[i]);
|
315
|
+
if (this_token_pos > last_token_pos)
|
316
|
+
last_token_pos = this_token_pos;
|
317
|
+
}
|
318
|
+
return last_token_pos;
|
319
|
+
}
|
320
|
+
}
|
321
|
+
|
322
|
+
Ajax.Autocompleter = Class.create();
|
323
|
+
Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(),
|
324
|
+
Object.extend(new Ajax.Base(), {
|
325
|
+
initialize: function(element, update, url, options) {
|
326
|
+
this.base_initialize(element, update, options);
|
327
|
+
this.options.asynchronous = true;
|
328
|
+
this.options.onComplete = this.onComplete.bind(this)
|
329
|
+
this.options.method = 'post';
|
330
|
+
this.options.defaultParams = this.options.parameters || null;
|
331
|
+
this.url = url;
|
332
|
+
},
|
333
|
+
|
334
|
+
getUpdatedChoices: function() {
|
335
|
+
entry = encodeURIComponent(this.element.name) + '=' +
|
336
|
+
encodeURIComponent(this.getEntry());
|
337
|
+
|
338
|
+
this.options.parameters = this.options.callback ?
|
339
|
+
this.options.callback(this.element, entry) : entry;
|
340
|
+
|
341
|
+
if(this.options.defaultParams)
|
342
|
+
this.options.parameters += '&' + this.options.defaultParams;
|
343
|
+
|
344
|
+
new Ajax.Request(this.url, this.options);
|
345
|
+
},
|
346
|
+
|
347
|
+
onComplete: function(request) {
|
348
|
+
this.updateChoices(request.responseText);
|
349
|
+
}
|
350
|
+
|
351
|
+
}));
|
352
|
+
|
353
|
+
// The local array autocompleter. Used when you'd prefer to
|
354
|
+
// inject an array of autocompletion options into the page, rather
|
355
|
+
// than sending out Ajax queries, which can be quite slow sometimes.
|
356
|
+
//
|
357
|
+
// The constructor takes four parameters. The first two are, as usual,
|
358
|
+
// the id of the monitored textbox, and id of the autocompletion menu.
|
359
|
+
// The third is the array you want to autocomplete from, and the fourth
|
360
|
+
// is the options block.
|
361
|
+
//
|
362
|
+
// Extra local autocompletion options:
|
363
|
+
// - choices - How many autocompletion choices to offer
|
364
|
+
//
|
365
|
+
// - partial_search - If false, the autocompleter will match entered
|
366
|
+
// text only at the beginning of strings in the
|
367
|
+
// autocomplete array. Defaults to true, which will
|
368
|
+
// match text at the beginning of any *word* in the
|
369
|
+
// strings in the autocomplete array. If you want to
|
370
|
+
// search anywhere in the string, additionally set
|
371
|
+
// the option full_search to true (default: off).
|
372
|
+
//
|
373
|
+
// - full_search - Search anywhere in autocomplete array strings.
|
374
|
+
//
|
375
|
+
// - partial_chars - How many characters to enter before triggering
|
376
|
+
// a partial match (unlike min_chars, which defines
|
377
|
+
// how many characters are required to do any match
|
378
|
+
// at all). Defaults to 2.
|
379
|
+
//
|
380
|
+
// - ignore_case - Whether to ignore case when autocompleting.
|
381
|
+
// Defaults to true.
|
382
|
+
//
|
383
|
+
// It's possible to pass in a custom function as the 'selector'
|
384
|
+
// option, if you prefer to write your own autocompletion logic.
|
385
|
+
// In that case, the other options above will not apply unless
|
386
|
+
// you support them.
|
387
|
+
|
388
|
+
Autocompleter.Local = Class.create();
|
389
|
+
Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
|
390
|
+
initialize: function(element, update, array, options) {
|
391
|
+
this.base_initialize(element, update, options);
|
392
|
+
this.options.array = array;
|
393
|
+
},
|
394
|
+
|
395
|
+
getUpdatedChoices: function() {
|
396
|
+
this.updateChoices(this.options.selector(this));
|
397
|
+
},
|
398
|
+
|
399
|
+
setOptions: function(options) {
|
400
|
+
this.options = Object.extend({
|
401
|
+
choices: 10,
|
402
|
+
partial_search: true,
|
403
|
+
partial_chars: 2,
|
404
|
+
ignore_case: true,
|
405
|
+
full_search: false,
|
406
|
+
selector: function(instance) {
|
407
|
+
var ret = new Array(); // Beginning matches
|
408
|
+
var partial = new Array(); // Inside matches
|
409
|
+
var entry = instance.getEntry();
|
410
|
+
var count = 0;
|
411
|
+
|
412
|
+
for (var i = 0; i < instance.options.array.length &&
|
413
|
+
ret.length < instance.options.choices ; i++) {
|
414
|
+
var elem = instance.options.array[i];
|
415
|
+
var found_pos = instance.options.ignore_case ?
|
416
|
+
elem.toLowerCase().indexOf(entry.toLowerCase()) :
|
417
|
+
elem.indexOf(entry);
|
418
|
+
|
419
|
+
while (found_pos != -1) {
|
420
|
+
if (found_pos == 0 && elem.length != entry.length) {
|
421
|
+
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
|
422
|
+
elem.substr(entry.length) + "</li>");
|
423
|
+
break;
|
424
|
+
} else if (entry.length >= instance.options.partial_chars &&
|
425
|
+
instance.options.partial_search && found_pos != -1) {
|
426
|
+
if (instance.options.full_search || /\s/.test(elem.substr(found_pos-1,1))) {
|
427
|
+
partial.push("<li>" + elem.substr(0, found_pos) + "<strong>" +
|
428
|
+
elem.substr(found_pos, entry.length) + "</strong>" + elem.substr(
|
429
|
+
found_pos + entry.length) + "</li>");
|
430
|
+
break;
|
431
|
+
}
|
432
|
+
}
|
433
|
+
|
434
|
+
found_pos = instance.options.ignore_case ?
|
435
|
+
elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) :
|
436
|
+
elem.indexOf(entry, found_pos + 1);
|
437
|
+
|
438
|
+
}
|
439
|
+
}
|
440
|
+
if (partial.length)
|
441
|
+
ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
|
442
|
+
return "<ul>" + ret.join('') + "</ul>";
|
443
|
+
}
|
444
|
+
}, options || {});
|
260
445
|
}
|
261
|
-
});
|
446
|
+
});
|
@@ -112,12 +112,18 @@ Element.Class = {
|
|
112
112
|
var Droppables = {
|
113
113
|
drops: false,
|
114
114
|
|
115
|
+
remove: function(element) {
|
116
|
+
for(var i = 0; i < this.drops.length; i++)
|
117
|
+
if(this.drops[i].element == element)
|
118
|
+
this.drops.splice(i,1);
|
119
|
+
},
|
120
|
+
|
115
121
|
add: function(element) {
|
116
122
|
var element = $(element);
|
117
|
-
var options = {
|
123
|
+
var options = Object.extend({
|
118
124
|
greedy: true,
|
119
125
|
hoverclass: null
|
120
|
-
}
|
126
|
+
}, arguments[1] || {});
|
121
127
|
|
122
128
|
// cache containers
|
123
129
|
if(options.containment) {
|
@@ -134,43 +140,42 @@ var Droppables = {
|
|
134
140
|
options._containers.length-1;
|
135
141
|
}
|
136
142
|
|
137
|
-
|
138
|
-
element.style.position = 'relative';
|
143
|
+
Element.makePositioned(element); // fix IE
|
139
144
|
|
140
|
-
|
141
|
-
element.droppable = options;
|
145
|
+
options.element = element;
|
142
146
|
|
147
|
+
// activate the droppable
|
143
148
|
if(!this.drops) this.drops = [];
|
144
|
-
this.drops.push(
|
149
|
+
this.drops.push(options);
|
145
150
|
},
|
146
151
|
|
147
152
|
is_contained: function(element, drop) {
|
148
|
-
var containers = drop.
|
153
|
+
var containers = drop._containers;
|
149
154
|
var parentNode = element.parentNode;
|
150
|
-
var i = drop.
|
155
|
+
var i = drop._containers_length;
|
151
156
|
do { if(parentNode==containers[i]) return true; } while (i--);
|
152
157
|
return false;
|
153
158
|
},
|
154
159
|
|
155
160
|
is_affected: function(pX, pY, element, drop) {
|
156
161
|
return (
|
157
|
-
(drop!=element) &&
|
158
|
-
((!drop.
|
162
|
+
(drop.element!=element) &&
|
163
|
+
((!drop._containers) ||
|
159
164
|
this.is_contained(element, drop)) &&
|
160
|
-
((!drop.
|
161
|
-
(Element.Class.has_any(element, drop.
|
162
|
-
Position.within(drop, pX, pY) );
|
165
|
+
((!drop.accept) ||
|
166
|
+
(Element.Class.has_any(element, drop.accept))) &&
|
167
|
+
Position.within(drop.element, pX, pY) );
|
163
168
|
},
|
164
169
|
|
165
170
|
deactivate: function(drop) {
|
166
|
-
Element.Class.remove(drop, drop.
|
171
|
+
Element.Class.remove(drop.element, drop.hoverclass);
|
167
172
|
this.last_active = null;
|
168
173
|
},
|
169
174
|
|
170
175
|
activate: function(drop) {
|
171
176
|
if(this.last_active) this.deactivate(this.last_active);
|
172
|
-
if(drop.
|
173
|
-
Element.Class.add(drop, drop.
|
177
|
+
if(drop.hoverclass) {
|
178
|
+
Element.Class.add(drop.element, drop.hoverclass);
|
174
179
|
this.last_active = drop;
|
175
180
|
}
|
176
181
|
},
|
@@ -184,10 +189,9 @@ var Droppables = {
|
|
184
189
|
var i = this.drops.length-1; do {
|
185
190
|
var drop = this.drops[i];
|
186
191
|
if(this.is_affected(pX, pY, element, drop)) {
|
187
|
-
if(drop.
|
188
|
-
drop.
|
189
|
-
|
190
|
-
if(drop.droppable.greedy) {
|
192
|
+
if(drop.onHover)
|
193
|
+
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
|
194
|
+
if(drop.greedy) {
|
191
195
|
this.activate(drop);
|
192
196
|
return;
|
193
197
|
}
|
@@ -196,17 +200,13 @@ var Droppables = {
|
|
196
200
|
},
|
197
201
|
|
198
202
|
fire: function(event, element) {
|
199
|
-
if(!this.
|
200
|
-
var pX = Event.pointerX(event);
|
201
|
-
var pY = Event.pointerY(event);
|
203
|
+
if(!this.last_active) return;
|
202
204
|
Position.prepare();
|
203
205
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
drop.droppable.onDrop(element);
|
209
|
-
} while (i--);
|
206
|
+
if (this.is_affected(Event.pointerX(event), Event.pointerY(event), element, this.last_active))
|
207
|
+
if (this.last_active.onDrop)
|
208
|
+
this.last_active.onDrop(element, this.last_active);
|
209
|
+
|
210
210
|
},
|
211
211
|
|
212
212
|
reset: function() {
|
@@ -220,6 +220,11 @@ Draggables = {
|
|
220
220
|
addObserver: function(observer) {
|
221
221
|
this.observers.push(observer);
|
222
222
|
},
|
223
|
+
removeObserver: function(element) { // element instead of obsever fixes mem leaks
|
224
|
+
for(var i = 0; i < this.observers.length; i++)
|
225
|
+
if(this.observers[i].element && (this.observers[i].element == element))
|
226
|
+
this.observers.splice(i,1);
|
227
|
+
},
|
223
228
|
notify: function(eventName, draggable) { // 'onStart', 'onEnd'
|
224
229
|
for(var i = 0; i < this.observers.length; i++)
|
225
230
|
this.observers[i][eventName](draggable);
|
@@ -231,7 +236,7 @@ Draggables = {
|
|
231
236
|
Draggable = Class.create();
|
232
237
|
Draggable.prototype = {
|
233
238
|
initialize: function(element) {
|
234
|
-
var options = {
|
239
|
+
var options = Object.extend({
|
235
240
|
handle: false,
|
236
241
|
starteffect: function(element) {
|
237
242
|
new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
|
@@ -244,15 +249,12 @@ Draggable.prototype = {
|
|
244
249
|
},
|
245
250
|
zindex: 1000,
|
246
251
|
revert: false
|
247
|
-
}
|
252
|
+
}, arguments[1] || {});
|
248
253
|
|
249
254
|
this.element = $(element);
|
250
|
-
this.element.drag = this;
|
251
255
|
this.handle = options.handle ? $(options.handle) : this.element;
|
252
256
|
|
253
|
-
// fix IE
|
254
|
-
if(!this.element.style.position)
|
255
|
-
this.element.style.position = 'relative';
|
257
|
+
Element.makePositioned(this.element); // fix IE
|
256
258
|
|
257
259
|
this.offsetX = 0;
|
258
260
|
this.offsetY = 0;
|
@@ -267,9 +269,21 @@ Draggable.prototype = {
|
|
267
269
|
this.active = false;
|
268
270
|
this.dragging = false;
|
269
271
|
|
270
|
-
|
271
|
-
|
272
|
-
|
272
|
+
this.eventMouseDown = this.startDrag.bindAsEventListener(this);
|
273
|
+
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
|
274
|
+
this.eventMouseMove = this.update.bindAsEventListener(this);
|
275
|
+
this.eventKeypress = this.keyPress.bindAsEventListener(this);
|
276
|
+
|
277
|
+
Event.observe(this.handle, "mousedown", this.eventMouseDown);
|
278
|
+
Event.observe(document, "mouseup", this.eventMouseUp);
|
279
|
+
Event.observe(document, "mousemove", this.eventMouseMove);
|
280
|
+
Event.observe(document, "keypress", this.eventKeypress);
|
281
|
+
},
|
282
|
+
destroy: function() {
|
283
|
+
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
|
284
|
+
Event.stopObserving(document, "mouseup", this.eventMouseUp);
|
285
|
+
Event.stopObserving(document, "mousemove", this.eventMouseMove);
|
286
|
+
Event.stopObserving(document, "keypress", this.eventKeypress);
|
273
287
|
},
|
274
288
|
currentLeft: function() {
|
275
289
|
return parseInt(this.element.style.left || '0');
|
@@ -290,31 +304,43 @@ Draggable.prototype = {
|
|
290
304
|
Event.stop(event);
|
291
305
|
}
|
292
306
|
},
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
307
|
+
finishDrag: function(event, success) {
|
308
|
+
this.active = false;
|
309
|
+
this.dragging = false;
|
310
|
+
|
311
|
+
if(success) Droppables.fire(event, this.element);
|
312
|
+
Draggables.notify('onEnd', this);
|
313
|
+
|
314
|
+
var revert = this.options.revert;
|
315
|
+
if(revert && typeof revert == 'function') revert = revert(this.element);
|
297
316
|
|
298
|
-
|
299
|
-
|
317
|
+
if(revert && this.options.reverteffect) {
|
318
|
+
this.options.reverteffect(this.element,
|
319
|
+
this.currentTop()-this.originalTop,
|
320
|
+
this.currentLeft()-this.originalLeft);
|
321
|
+
} else {
|
322
|
+
this.originalLeft = this.currentLeft();
|
323
|
+
this.originalTop = this.currentTop();
|
324
|
+
}
|
325
|
+
|
326
|
+
this.element.style.zIndex = this.originalZ;
|
300
327
|
|
301
|
-
|
302
|
-
|
328
|
+
if(this.options.endeffect)
|
329
|
+
this.options.endeffect(this.element);
|
303
330
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
this.
|
310
|
-
|
331
|
+
Droppables.reset();
|
332
|
+
},
|
333
|
+
keyPress: function(event) {
|
334
|
+
if(this.active) {
|
335
|
+
if(event.keyCode==Event.KEY_ESC) {
|
336
|
+
this.finishDrag(event, false);
|
337
|
+
Event.stop(event);
|
311
338
|
}
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
Droppables.reset();
|
339
|
+
}
|
340
|
+
},
|
341
|
+
endDrag: function(event) {
|
342
|
+
if(this.active && this.dragging) {
|
343
|
+
this.finishDrag(event, true);
|
318
344
|
Event.stop(event);
|
319
345
|
}
|
320
346
|
this.active = false;
|
@@ -372,9 +398,32 @@ SortableObserver.prototype = {
|
|
372
398
|
}
|
373
399
|
|
374
400
|
Sortable = {
|
401
|
+
sortables: new Array(),
|
402
|
+
options: function(element){
|
403
|
+
var element = $(element);
|
404
|
+
for(var i=0;i<this.sortables.length;i++)
|
405
|
+
if(this.sortables[i].element == element)
|
406
|
+
return this.sortables[i];
|
407
|
+
return null;
|
408
|
+
},
|
409
|
+
destroy: function(element){
|
410
|
+
var element = $(element);
|
411
|
+
for(var i=0;i<this.sortables.length;i++) {
|
412
|
+
if(this.sortables[i].element == element) {
|
413
|
+
var s = this.sortables[i];
|
414
|
+
Draggables.removeObserver(s.element);
|
415
|
+
for(var j=0;j<s.droppables.length;j++)
|
416
|
+
Droppables.remove(s.droppables[j]);
|
417
|
+
for(var j=0;j<s.draggables.length;j++)
|
418
|
+
s.draggables[j].destroy();
|
419
|
+
this.sortables.splice(i,1);
|
420
|
+
}
|
421
|
+
}
|
422
|
+
},
|
375
423
|
create: function(element) {
|
376
424
|
var element = $(element);
|
377
|
-
var options = {
|
425
|
+
var options = Object.extend({
|
426
|
+
element: element,
|
378
427
|
tag: 'li', // assumes li children, override with tag: 'tagname'
|
379
428
|
overlap: 'vertical', // one of 'vertical', 'horizontal'
|
380
429
|
constraint: 'vertical', // one of 'vertical', 'horizontal', false
|
@@ -384,8 +433,10 @@ Sortable = {
|
|
384
433
|
hoverclass: null,
|
385
434
|
onChange: function() {},
|
386
435
|
onUpdate: function() {}
|
387
|
-
}
|
388
|
-
|
436
|
+
}, arguments[1] || {});
|
437
|
+
|
438
|
+
// clear any old sortable with same element
|
439
|
+
this.destroy(element);
|
389
440
|
|
390
441
|
// build options for the draggables
|
391
442
|
var options_for_draggable = {
|
@@ -435,8 +486,8 @@ Sortable = {
|
|
435
486
|
// fix for gecko engine
|
436
487
|
Element.cleanWhitespace(element);
|
437
488
|
|
438
|
-
|
439
|
-
|
489
|
+
options.draggables = [];
|
490
|
+
options.droppables = [];
|
440
491
|
|
441
492
|
// make it so
|
442
493
|
var elements = element.childNodes;
|
@@ -448,18 +499,28 @@ Sortable = {
|
|
448
499
|
var handle = options.handle ?
|
449
500
|
Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i];
|
450
501
|
|
451
|
-
new Draggable(elements[i],
|
502
|
+
options.draggables.push(new Draggable(elements[i], Object.extend(options_for_draggable, { handle: handle })));
|
503
|
+
|
452
504
|
Droppables.add(elements[i], options_for_droppable);
|
505
|
+
options.droppables.push(elements[i]);
|
506
|
+
|
453
507
|
}
|
454
508
|
|
509
|
+
// keep reference
|
510
|
+
this.sortables.push(options);
|
511
|
+
|
512
|
+
// for onupdate
|
513
|
+
Draggables.addObserver(new SortableObserver(element, options.onUpdate));
|
514
|
+
|
455
515
|
},
|
456
516
|
serialize: function(element) {
|
457
517
|
var element = $(element);
|
458
|
-
var
|
459
|
-
|
460
|
-
|
518
|
+
var sortableOptions = this.options(element);
|
519
|
+
var options = Object.extend({
|
520
|
+
tag: sortableOptions.tag,
|
521
|
+
only: sortableOptions.only,
|
461
522
|
name: element.id
|
462
|
-
}
|
523
|
+
}, arguments[1] || {});
|
463
524
|
|
464
525
|
var items = $(element).childNodes;
|
465
526
|
var queryComponents = new Array();
|