actionpack 1.9.1 → 1.10.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 +237 -0
- data/README +12 -12
- data/lib/action_controller.rb +17 -12
- data/lib/action_controller/assertions.rb +119 -67
- data/lib/action_controller/base.rb +184 -102
- data/lib/action_controller/benchmarking.rb +35 -6
- data/lib/action_controller/caching.rb +115 -58
- data/lib/action_controller/cgi_ext/cgi_methods.rb +54 -21
- data/lib/action_controller/cgi_ext/cookie_performance_fix.rb +39 -35
- data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +34 -21
- data/lib/action_controller/cgi_process.rb +23 -20
- data/lib/action_controller/components.rb +11 -2
- data/lib/action_controller/dependencies.rb +0 -5
- data/lib/action_controller/deprecated_redirects.rb +17 -0
- data/lib/action_controller/filters.rb +13 -9
- data/lib/action_controller/flash.rb +7 -7
- data/lib/action_controller/helpers.rb +1 -14
- data/lib/action_controller/layout.rb +40 -29
- data/lib/action_controller/macros/auto_complete.rb +52 -0
- data/lib/action_controller/macros/in_place_editing.rb +32 -0
- data/lib/action_controller/pagination.rb +44 -28
- data/lib/action_controller/request.rb +54 -40
- data/lib/action_controller/rescue.rb +8 -6
- data/lib/action_controller/routing.rb +77 -28
- data/lib/action_controller/scaffolding.rb +10 -14
- data/lib/action_controller/session/active_record_store.rb +36 -7
- data/lib/action_controller/session_management.rb +126 -0
- data/lib/action_controller/streaming.rb +14 -5
- data/lib/action_controller/templates/rescues/_request_and_response.rhtml +1 -1
- data/lib/action_controller/templates/rescues/_trace.rhtml +24 -0
- data/lib/action_controller/templates/rescues/diagnostics.rhtml +2 -13
- data/lib/action_controller/templates/rescues/template_error.rhtml +4 -2
- data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
- data/lib/action_controller/test_process.rb +35 -17
- data/lib/action_controller/upload_progress.rb +52 -0
- data/lib/action_controller/url_rewriter.rb +21 -16
- data/lib/action_controller/vendor/html-scanner/html/document.rb +2 -2
- data/lib/action_controller/vendor/html-scanner/html/node.rb +30 -3
- data/lib/action_pack/version.rb +9 -0
- data/lib/action_view.rb +1 -1
- data/lib/action_view/base.rb +204 -60
- data/lib/action_view/compiled_templates.rb +70 -0
- data/lib/action_view/helpers/active_record_helper.rb +7 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +22 -12
- data/lib/action_view/helpers/capture_helper.rb +2 -10
- data/lib/action_view/helpers/date_helper.rb +21 -13
- data/lib/action_view/helpers/form_helper.rb +14 -10
- data/lib/action_view/helpers/form_options_helper.rb +4 -4
- data/lib/action_view/helpers/form_tag_helper.rb +59 -25
- data/lib/action_view/helpers/java_script_macros_helper.rb +188 -0
- data/lib/action_view/helpers/javascript_helper.rb +68 -133
- data/lib/action_view/helpers/javascripts/controls.js +427 -165
- data/lib/action_view/helpers/javascripts/dragdrop.js +256 -277
- data/lib/action_view/helpers/javascripts/effects.js +766 -277
- data/lib/action_view/helpers/javascripts/prototype.js +906 -218
- data/lib/action_view/helpers/javascripts/slider.js +258 -0
- data/lib/action_view/helpers/number_helper.rb +4 -3
- data/lib/action_view/helpers/pagination_helper.rb +42 -27
- data/lib/action_view/helpers/tag_helper.rb +25 -11
- data/lib/action_view/helpers/text_helper.rb +119 -13
- data/lib/action_view/helpers/upload_progress_helper.rb +2 -2
- data/lib/action_view/helpers/url_helper.rb +68 -21
- data/lib/action_view/partials.rb +17 -6
- data/lib/action_view/template_error.rb +19 -24
- data/rakefile +4 -3
- data/test/abstract_unit.rb +2 -1
- data/test/controller/action_pack_assertions_test.rb +62 -2
- data/test/controller/active_record_assertions_test.rb +5 -6
- data/test/controller/active_record_store_test.rb +23 -1
- data/test/controller/addresses_render_test.rb +4 -0
- data/test/controller/{base_tests.rb → base_test.rb} +4 -3
- data/test/controller/benchmark_test.rb +36 -0
- data/test/controller/caching_filestore.rb +22 -40
- data/test/controller/capture_test.rb +10 -1
- data/test/controller/cgi_test.rb +145 -23
- data/test/controller/components_test.rb +50 -0
- data/test/controller/custom_handler_test.rb +3 -3
- data/test/controller/fake_controllers.rb +24 -0
- data/test/controller/filters_test.rb +6 -6
- data/test/controller/flash_test.rb +6 -6
- data/test/controller/fragment_store_setting_test.rb +45 -0
- data/test/controller/helper_test.rb +1 -3
- data/test/controller/new_render_test.rb +119 -7
- data/test/controller/redirect_test.rb +11 -1
- data/test/controller/render_test.rb +34 -1
- data/test/controller/request_test.rb +14 -5
- data/test/controller/routing_test.rb +238 -42
- data/test/controller/send_file_test.rb +11 -10
- data/test/controller/session_management_test.rb +94 -0
- data/test/controller/test_test.rb +194 -5
- data/test/controller/url_rewriter_test.rb +46 -0
- data/test/fixtures/layouts/talk_from_action.rhtml +2 -0
- data/test/fixtures/layouts/yield.rhtml +2 -0
- data/test/fixtures/multipart/binary_file +0 -0
- data/test/fixtures/multipart/large_text_file +10 -0
- data/test/fixtures/multipart/mixed_files +0 -0
- data/test/fixtures/multipart/single_parameter +5 -0
- data/test/fixtures/multipart/text_file +10 -0
- data/test/fixtures/test/_customer_greeting.rhtml +1 -0
- data/test/fixtures/test/_hash_object.rhtml +1 -0
- data/test/fixtures/test/_person.rhtml +2 -0
- data/test/fixtures/test/action_talk_to_layout.rhtml +2 -0
- data/test/fixtures/test/content_for.rhtml +2 -0
- data/test/fixtures/test/potential_conflicts.rhtml +4 -0
- data/test/template/active_record_helper_test.rb +15 -8
- data/test/template/asset_tag_helper_test.rb +40 -16
- data/test/template/compiled_templates_tests.rb +63 -0
- data/test/template/date_helper_test.rb +80 -4
- data/test/template/form_helper_test.rb +48 -42
- data/test/template/form_options_helper_test.rb +40 -40
- data/test/template/form_tag_helper_test.rb +21 -15
- data/test/template/java_script_macros_helper_test.rb +56 -0
- data/test/template/javascript_helper_test.rb +70 -47
- data/test/template/number_helper_test.rb +2 -0
- data/test/template/tag_helper_test.rb +9 -0
- data/test/template/text_helper_test.rb +146 -1
- data/test/template/upload_progress_helper_testx.rb +11 -147
- data/test/template/url_helper_test.rb +90 -22
- data/test/testing_sandbox.rb +26 -0
- metadata +37 -7
- data/lib/action_controller/auto_complete.rb +0 -47
- data/lib/action_controller/deprecated_renders_and_redirects.rb +0 -76
- data/lib/action_controller/session.rb +0 -14
@@ -1,41 +1,12 @@
|
|
1
1
|
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
2
2
|
// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
|
3
|
+
// (c) 2005 Jon Tirsen (http://www.tirsen.com)
|
4
|
+
// Contributors:
|
5
|
+
// Richard Livsey
|
6
|
+
// Rahul Bhargava
|
7
|
+
// Rob Wills
|
3
8
|
//
|
4
|
-
//
|
5
|
-
// a copy of this software and associated documentation files (the
|
6
|
-
// "Software"), to deal in the Software without restriction, including
|
7
|
-
// without limitation the rights to use, copy, modify, merge, publish,
|
8
|
-
// distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
-
// permit persons to whom the Software is furnished to do so, subject to
|
10
|
-
// the following conditions:
|
11
|
-
//
|
12
|
-
// The above copyright notice and this permission notice shall be
|
13
|
-
// included in all copies or substantial portions of the Software.
|
14
|
-
//
|
15
|
-
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
-
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
-
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
-
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
-
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
-
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
-
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
-
|
23
|
-
Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
|
24
|
-
var children = $(element).childNodes;
|
25
|
-
var text = "";
|
26
|
-
var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i");
|
27
|
-
|
28
|
-
for (var i = 0; i < children.length; i++) {
|
29
|
-
if(children[i].nodeType==3) {
|
30
|
-
text+=children[i].nodeValue;
|
31
|
-
} else {
|
32
|
-
if((!children[i].className.match(classtest)) && children[i].hasChildNodes())
|
33
|
-
text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
|
34
|
-
}
|
35
|
-
}
|
36
|
-
|
37
|
-
return text;
|
38
|
-
}
|
9
|
+
// See scriptaculous.js for full license.
|
39
10
|
|
40
11
|
// Autocompleter.Base handles all the autocompletion functionality
|
41
12
|
// that's independent of the data source for autocompletion. This
|
@@ -46,7 +17,7 @@ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
|
|
46
17
|
// a getUpdatedChoices function that will be invoked every time
|
47
18
|
// the text inside the monitored textbox changes. This method
|
48
19
|
// should get the text for which to provide autocompletion by
|
49
|
-
// invoking this.
|
20
|
+
// invoking this.getToken(), NOT by directly accessing
|
50
21
|
// this.element.value. This is to allow incremental tokenized
|
51
22
|
// autocompletion. Specific auto-completion logic (AJAX, etc)
|
52
23
|
// belongs in getUpdatedChoices.
|
@@ -57,7 +28,7 @@ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
|
|
57
28
|
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
|
58
29
|
// will incrementally autocomplete with a comma as the token.
|
59
30
|
// Additionally, ',' in the above example can be replaced with
|
60
|
-
// a token array, e.g. { tokens:
|
31
|
+
// a token array, e.g. { tokens: [',', '\n'] } which
|
61
32
|
// enables autocompletion on multiple tokens. This is most
|
62
33
|
// useful when one of the tokens is \n (a newline), as it
|
63
34
|
// allows smart autocompletion after linebreaks.
|
@@ -65,79 +36,79 @@ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
|
|
65
36
|
var Autocompleter = {}
|
66
37
|
Autocompleter.Base = function() {};
|
67
38
|
Autocompleter.Base.prototype = {
|
68
|
-
|
39
|
+
baseInitialize: function(element, update, options) {
|
69
40
|
this.element = $(element);
|
70
41
|
this.update = $(update);
|
71
|
-
this.
|
42
|
+
this.hasFocus = false;
|
72
43
|
this.changed = false;
|
73
44
|
this.active = false;
|
74
45
|
this.index = 0;
|
75
|
-
this.
|
46
|
+
this.entryCount = 0;
|
76
47
|
|
77
48
|
if (this.setOptions)
|
78
49
|
this.setOptions(options);
|
79
50
|
else
|
80
|
-
this.options = {}
|
81
|
-
|
82
|
-
this.options.
|
51
|
+
this.options = options || {};
|
52
|
+
|
53
|
+
this.options.paramName = this.options.paramName || this.element.name;
|
54
|
+
this.options.tokens = this.options.tokens || [];
|
83
55
|
this.options.frequency = this.options.frequency || 0.4;
|
84
|
-
this.options.
|
56
|
+
this.options.minChars = this.options.minChars || 1;
|
85
57
|
this.options.onShow = this.options.onShow ||
|
86
58
|
function(element, update){
|
87
59
|
if(!update.style.position || update.style.position=='absolute') {
|
88
60
|
update.style.position = 'absolute';
|
89
|
-
|
90
|
-
update.style.left = offsets[0] + 'px';
|
91
|
-
update.style.top = (offsets[1] + element.offsetHeight) + 'px';
|
92
|
-
update.style.width = element.offsetWidth + 'px';
|
61
|
+
Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
|
93
62
|
}
|
94
|
-
|
63
|
+
Effect.Appear(update,{duration:0.15});
|
95
64
|
};
|
96
65
|
this.options.onHide = this.options.onHide ||
|
97
66
|
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
|
98
|
-
|
99
|
-
if(this.options.indicator)
|
100
|
-
this.indicator = $(this.options.indicator);
|
101
67
|
|
102
68
|
if (typeof(this.options.tokens) == 'string')
|
103
69
|
this.options.tokens = new Array(this.options.tokens);
|
104
|
-
|
70
|
+
|
105
71
|
this.observer = null;
|
106
72
|
|
73
|
+
this.element.setAttribute('autocomplete','off');
|
74
|
+
|
107
75
|
Element.hide(this.update);
|
108
|
-
|
76
|
+
|
109
77
|
Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
|
110
78
|
Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
|
111
79
|
},
|
112
80
|
|
113
81
|
show: function() {
|
114
|
-
if(this.update
|
115
|
-
if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update
|
82
|
+
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
|
83
|
+
if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && (Element.getStyle(this.update, 'position')=='absolute')) {
|
116
84
|
new Insertion.After(this.update,
|
117
85
|
'<iframe id="' + this.update.id + '_iefix" '+
|
118
|
-
'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
|
86
|
+
'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
|
119
87
|
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
|
120
88
|
this.iefix = $(this.update.id+'_iefix');
|
121
89
|
}
|
122
|
-
if(this.iefix)
|
123
|
-
Position.clone(this.update, this.iefix);
|
124
|
-
this.iefix.style.zIndex = 1;
|
125
|
-
this.update.style.zIndex = 2;
|
126
|
-
Element.show(this.iefix);
|
127
|
-
}
|
90
|
+
if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
|
128
91
|
},
|
129
92
|
|
93
|
+
fixIEOverlapping: function() {
|
94
|
+
Position.clone(this.update, this.iefix);
|
95
|
+
this.iefix.style.zIndex = 1;
|
96
|
+
this.update.style.zIndex = 2;
|
97
|
+
Element.show(this.iefix);
|
98
|
+
},
|
99
|
+
|
130
100
|
hide: function() {
|
131
|
-
|
101
|
+
this.stopIndicator();
|
102
|
+
if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
|
132
103
|
if(this.iefix) Element.hide(this.iefix);
|
133
104
|
},
|
134
|
-
|
105
|
+
|
135
106
|
startIndicator: function() {
|
136
|
-
if(this.indicator) Element.show(this.indicator);
|
107
|
+
if(this.options.indicator) Element.show(this.options.indicator);
|
137
108
|
},
|
138
|
-
|
109
|
+
|
139
110
|
stopIndicator: function() {
|
140
|
-
if(this.indicator) Element.hide(this.indicator);
|
111
|
+
if(this.options.indicator) Element.hide(this.options.indicator);
|
141
112
|
},
|
142
113
|
|
143
114
|
onKeyPress: function(event) {
|
@@ -145,22 +116,23 @@ Autocompleter.Base.prototype = {
|
|
145
116
|
switch(event.keyCode) {
|
146
117
|
case Event.KEY_TAB:
|
147
118
|
case Event.KEY_RETURN:
|
148
|
-
this.
|
119
|
+
this.selectEntry();
|
149
120
|
Event.stop(event);
|
150
121
|
case Event.KEY_ESC:
|
151
122
|
this.hide();
|
152
123
|
this.active = false;
|
124
|
+
Event.stop(event);
|
153
125
|
return;
|
154
126
|
case Event.KEY_LEFT:
|
155
127
|
case Event.KEY_RIGHT:
|
156
128
|
return;
|
157
129
|
case Event.KEY_UP:
|
158
|
-
this.
|
130
|
+
this.markPrevious();
|
159
131
|
this.render();
|
160
132
|
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
|
161
133
|
return;
|
162
134
|
case Event.KEY_DOWN:
|
163
|
-
this.
|
135
|
+
this.markNext();
|
164
136
|
this.render();
|
165
137
|
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
|
166
138
|
return;
|
@@ -168,15 +140,15 @@ Autocompleter.Base.prototype = {
|
|
168
140
|
else
|
169
141
|
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
|
170
142
|
return;
|
171
|
-
|
143
|
+
|
172
144
|
this.changed = true;
|
173
|
-
this.
|
174
|
-
|
145
|
+
this.hasFocus = true;
|
146
|
+
|
175
147
|
if(this.observer) clearTimeout(this.observer);
|
176
148
|
this.observer =
|
177
149
|
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
|
178
150
|
},
|
179
|
-
|
151
|
+
|
180
152
|
onHover: function(event) {
|
181
153
|
var element = Event.findElement(event, 'LI');
|
182
154
|
if(this.index != element.autocompleteIndex)
|
@@ -190,92 +162,97 @@ Autocompleter.Base.prototype = {
|
|
190
162
|
onClick: function(event) {
|
191
163
|
var element = Event.findElement(event, 'LI');
|
192
164
|
this.index = element.autocompleteIndex;
|
193
|
-
this.
|
194
|
-
|
165
|
+
this.selectEntry();
|
166
|
+
this.hide();
|
195
167
|
},
|
196
168
|
|
197
169
|
onBlur: function(event) {
|
198
170
|
// needed to make click events working
|
199
171
|
setTimeout(this.hide.bind(this), 250);
|
200
|
-
this.
|
172
|
+
this.hasFocus = false;
|
201
173
|
this.active = false;
|
202
174
|
},
|
203
175
|
|
204
176
|
render: function() {
|
205
|
-
if(this.
|
206
|
-
for (var i = 0; i < this.
|
177
|
+
if(this.entryCount > 0) {
|
178
|
+
for (var i = 0; i < this.entryCount; i++)
|
207
179
|
this.index==i ?
|
208
|
-
Element.addClassName(this.
|
209
|
-
Element.removeClassName(this.
|
210
|
-
|
211
|
-
if(this.has_focus) {
|
212
|
-
if(this.get_current_entry().scrollIntoView)
|
213
|
-
this.get_current_entry().scrollIntoView(false);
|
180
|
+
Element.addClassName(this.getEntry(i),"selected") :
|
181
|
+
Element.removeClassName(this.getEntry(i),"selected");
|
214
182
|
|
183
|
+
if(this.hasFocus) {
|
215
184
|
this.show();
|
216
185
|
this.active = true;
|
217
186
|
}
|
218
187
|
} else this.hide();
|
219
188
|
},
|
220
189
|
|
221
|
-
|
190
|
+
markPrevious: function() {
|
222
191
|
if(this.index > 0) this.index--
|
223
|
-
else this.index = this.
|
192
|
+
else this.index = this.entryCount-1;
|
224
193
|
},
|
225
194
|
|
226
|
-
|
227
|
-
if(this.index < this.
|
195
|
+
markNext: function() {
|
196
|
+
if(this.index < this.entryCount-1) this.index++
|
228
197
|
else this.index = 0;
|
229
198
|
},
|
230
199
|
|
231
|
-
|
200
|
+
getEntry: function(index) {
|
232
201
|
return this.update.firstChild.childNodes[index];
|
233
202
|
},
|
234
203
|
|
235
|
-
|
236
|
-
return this.
|
204
|
+
getCurrentEntry: function() {
|
205
|
+
return this.getEntry(this.index);
|
237
206
|
},
|
238
207
|
|
239
|
-
|
208
|
+
selectEntry: function() {
|
240
209
|
this.active = false;
|
241
|
-
|
242
|
-
this.updateElement(value);
|
243
|
-
this.element.focus();
|
210
|
+
this.updateElement(this.getCurrentEntry());
|
244
211
|
},
|
245
212
|
|
246
|
-
updateElement: function(
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
213
|
+
updateElement: function(selectedElement) {
|
214
|
+
if (this.options.updateElement) {
|
215
|
+
this.options.updateElement(selectedElement);
|
216
|
+
return;
|
217
|
+
}
|
218
|
+
|
219
|
+
var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
|
220
|
+
var lastTokenPos = this.findLastToken();
|
221
|
+
if (lastTokenPos != -1) {
|
222
|
+
var newValue = this.element.value.substr(0, lastTokenPos + 1);
|
223
|
+
var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
|
251
224
|
if (whitespace)
|
252
|
-
|
253
|
-
this.element.value =
|
225
|
+
newValue += whitespace[0];
|
226
|
+
this.element.value = newValue + value;
|
254
227
|
} else {
|
255
228
|
this.element.value = value;
|
256
|
-
}
|
229
|
+
}
|
230
|
+
this.element.focus();
|
231
|
+
|
232
|
+
if (this.options.afterUpdateElement)
|
233
|
+
this.options.afterUpdateElement(this.element, selectedElement);
|
257
234
|
},
|
258
|
-
|
235
|
+
|
259
236
|
updateChoices: function(choices) {
|
260
|
-
if(!this.changed && this.
|
237
|
+
if(!this.changed && this.hasFocus) {
|
261
238
|
this.update.innerHTML = choices;
|
262
239
|
Element.cleanWhitespace(this.update);
|
263
240
|
Element.cleanWhitespace(this.update.firstChild);
|
264
241
|
|
265
242
|
if(this.update.firstChild && this.update.firstChild.childNodes) {
|
266
|
-
this.
|
243
|
+
this.entryCount =
|
267
244
|
this.update.firstChild.childNodes.length;
|
268
|
-
for (var i = 0; i < this.
|
269
|
-
entry = this.
|
245
|
+
for (var i = 0; i < this.entryCount; i++) {
|
246
|
+
var entry = this.getEntry(i);
|
270
247
|
entry.autocompleteIndex = i;
|
271
248
|
this.addObservers(entry);
|
272
249
|
}
|
273
250
|
} else {
|
274
|
-
this.
|
251
|
+
this.entryCount = 0;
|
275
252
|
}
|
276
|
-
|
253
|
+
|
277
254
|
this.stopIndicator();
|
278
|
-
|
255
|
+
|
279
256
|
this.index = 0;
|
280
257
|
this.render();
|
281
258
|
}
|
@@ -288,7 +265,7 @@ Autocompleter.Base.prototype = {
|
|
288
265
|
|
289
266
|
onObserverEvent: function() {
|
290
267
|
this.changed = false;
|
291
|
-
if(this.
|
268
|
+
if(this.getToken().length>=this.options.minChars) {
|
292
269
|
this.startIndicator();
|
293
270
|
this.getUpdatedChoices();
|
294
271
|
} else {
|
@@ -297,58 +274,56 @@ Autocompleter.Base.prototype = {
|
|
297
274
|
}
|
298
275
|
},
|
299
276
|
|
300
|
-
|
301
|
-
var
|
302
|
-
if (
|
303
|
-
var ret = this.element.value.substr(
|
277
|
+
getToken: function() {
|
278
|
+
var tokenPos = this.findLastToken();
|
279
|
+
if (tokenPos != -1)
|
280
|
+
var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
|
304
281
|
else
|
305
282
|
var ret = this.element.value;
|
306
|
-
|
283
|
+
|
307
284
|
return /\n/.test(ret) ? '' : ret;
|
308
285
|
},
|
309
286
|
|
310
287
|
findLastToken: function() {
|
311
|
-
var
|
288
|
+
var lastTokenPos = -1;
|
312
289
|
|
313
290
|
for (var i=0; i<this.options.tokens.length; i++) {
|
314
|
-
var
|
315
|
-
if (
|
316
|
-
|
291
|
+
var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
|
292
|
+
if (thisTokenPos > lastTokenPos)
|
293
|
+
lastTokenPos = thisTokenPos;
|
317
294
|
}
|
318
|
-
return
|
295
|
+
return lastTokenPos;
|
319
296
|
}
|
320
297
|
}
|
321
298
|
|
322
299
|
Ajax.Autocompleter = Class.create();
|
323
|
-
Ajax.Autocompleter.prototype
|
324
|
-
Object.extend(new Ajax.Base(), {
|
300
|
+
Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
|
325
301
|
initialize: function(element, update, url, options) {
|
326
|
-
this.
|
302
|
+
this.baseInitialize(element, update, options);
|
327
303
|
this.options.asynchronous = true;
|
328
|
-
this.options.onComplete = this.onComplete.bind(this)
|
329
|
-
this.options.method = 'post';
|
304
|
+
this.options.onComplete = this.onComplete.bind(this);
|
330
305
|
this.options.defaultParams = this.options.parameters || null;
|
331
306
|
this.url = url;
|
332
307
|
},
|
333
|
-
|
308
|
+
|
334
309
|
getUpdatedChoices: function() {
|
335
|
-
entry = encodeURIComponent(this.
|
336
|
-
encodeURIComponent(this.
|
337
|
-
|
310
|
+
entry = encodeURIComponent(this.options.paramName) + '=' +
|
311
|
+
encodeURIComponent(this.getToken());
|
312
|
+
|
338
313
|
this.options.parameters = this.options.callback ?
|
339
314
|
this.options.callback(this.element, entry) : entry;
|
340
|
-
|
315
|
+
|
341
316
|
if(this.options.defaultParams)
|
342
317
|
this.options.parameters += '&' + this.options.defaultParams;
|
343
|
-
|
318
|
+
|
344
319
|
new Ajax.Request(this.url, this.options);
|
345
320
|
},
|
346
|
-
|
321
|
+
|
347
322
|
onComplete: function(request) {
|
348
323
|
this.updateChoices(request.responseText);
|
349
324
|
}
|
350
325
|
|
351
|
-
})
|
326
|
+
});
|
352
327
|
|
353
328
|
// The local array autocompleter. Used when you'd prefer to
|
354
329
|
// inject an array of autocompletion options into the page, rather
|
@@ -362,22 +337,22 @@ Object.extend(new Ajax.Base(), {
|
|
362
337
|
// Extra local autocompletion options:
|
363
338
|
// - choices - How many autocompletion choices to offer
|
364
339
|
//
|
365
|
-
// -
|
340
|
+
// - partialSearch - If false, the autocompleter will match entered
|
366
341
|
// text only at the beginning of strings in the
|
367
342
|
// autocomplete array. Defaults to true, which will
|
368
343
|
// match text at the beginning of any *word* in the
|
369
344
|
// strings in the autocomplete array. If you want to
|
370
345
|
// search anywhere in the string, additionally set
|
371
|
-
// the option
|
346
|
+
// the option fullSearch to true (default: off).
|
372
347
|
//
|
373
|
-
// -
|
348
|
+
// - fullSsearch - Search anywhere in autocomplete array strings.
|
374
349
|
//
|
375
|
-
// -
|
376
|
-
// a partial match (unlike
|
350
|
+
// - partialChars - How many characters to enter before triggering
|
351
|
+
// a partial match (unlike minChars, which defines
|
377
352
|
// how many characters are required to do any match
|
378
353
|
// at all). Defaults to 2.
|
379
354
|
//
|
380
|
-
// -
|
355
|
+
// - ignoreCase - Whether to ignore case when autocompleting.
|
381
356
|
// Defaults to true.
|
382
357
|
//
|
383
358
|
// It's possible to pass in a custom function as the 'selector'
|
@@ -388,7 +363,7 @@ Object.extend(new Ajax.Base(), {
|
|
388
363
|
Autocompleter.Local = Class.create();
|
389
364
|
Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
|
390
365
|
initialize: function(element, update, array, options) {
|
391
|
-
this.
|
366
|
+
this.baseInitialize(element, update, options);
|
392
367
|
this.options.array = array;
|
393
368
|
},
|
394
369
|
|
@@ -399,41 +374,42 @@ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
|
|
399
374
|
setOptions: function(options) {
|
400
375
|
this.options = Object.extend({
|
401
376
|
choices: 10,
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
377
|
+
partialSearch: true,
|
378
|
+
partialChars: 2,
|
379
|
+
ignoreCase: true,
|
380
|
+
fullSearch: false,
|
406
381
|
selector: function(instance) {
|
407
|
-
var ret =
|
408
|
-
var partial =
|
409
|
-
var entry = instance.
|
382
|
+
var ret = []; // Beginning matches
|
383
|
+
var partial = []; // Inside matches
|
384
|
+
var entry = instance.getToken();
|
410
385
|
var count = 0;
|
411
|
-
|
386
|
+
|
412
387
|
for (var i = 0; i < instance.options.array.length &&
|
413
|
-
|
388
|
+
ret.length < instance.options.choices ; i++) {
|
389
|
+
|
414
390
|
var elem = instance.options.array[i];
|
415
|
-
var
|
391
|
+
var foundPos = instance.options.ignoreCase ?
|
416
392
|
elem.toLowerCase().indexOf(entry.toLowerCase()) :
|
417
393
|
elem.indexOf(entry);
|
418
394
|
|
419
|
-
while (
|
420
|
-
if (
|
395
|
+
while (foundPos != -1) {
|
396
|
+
if (foundPos == 0 && elem.length != entry.length) {
|
421
397
|
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
|
422
398
|
elem.substr(entry.length) + "</li>");
|
423
399
|
break;
|
424
|
-
} else if (entry.length >= instance.options.
|
425
|
-
instance.options.
|
426
|
-
if (instance.options.
|
427
|
-
partial.push("<li>" + elem.substr(0,
|
428
|
-
elem.substr(
|
429
|
-
|
400
|
+
} else if (entry.length >= instance.options.partialChars &&
|
401
|
+
instance.options.partialSearch && foundPos != -1) {
|
402
|
+
if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
|
403
|
+
partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
|
404
|
+
elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
|
405
|
+
foundPos + entry.length) + "</li>");
|
430
406
|
break;
|
431
407
|
}
|
432
408
|
}
|
433
409
|
|
434
|
-
|
435
|
-
elem.toLowerCase().indexOf(entry.toLowerCase(),
|
436
|
-
elem.indexOf(entry,
|
410
|
+
foundPos = instance.options.ignoreCase ?
|
411
|
+
elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
|
412
|
+
elem.indexOf(entry, foundPos + 1);
|
437
413
|
|
438
414
|
}
|
439
415
|
}
|
@@ -444,3 +420,289 @@ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
|
|
444
420
|
}, options || {});
|
445
421
|
}
|
446
422
|
});
|
423
|
+
|
424
|
+
// AJAX in-place editor
|
425
|
+
//
|
426
|
+
// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
|
427
|
+
|
428
|
+
Ajax.InPlaceEditor = Class.create();
|
429
|
+
Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
|
430
|
+
Ajax.InPlaceEditor.prototype = {
|
431
|
+
initialize: function(element, url, options) {
|
432
|
+
this.url = url;
|
433
|
+
this.element = $(element);
|
434
|
+
|
435
|
+
this.options = Object.extend({
|
436
|
+
okText: "ok",
|
437
|
+
cancelText: "cancel",
|
438
|
+
savingText: "Saving...",
|
439
|
+
clickToEditText: "Click to edit",
|
440
|
+
okText: "ok",
|
441
|
+
rows: 1,
|
442
|
+
onComplete: function(transport, element) {
|
443
|
+
new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
|
444
|
+
},
|
445
|
+
onFailure: function(transport) {
|
446
|
+
alert("Error communicating with the server: " + transport.responseText.stripTags());
|
447
|
+
},
|
448
|
+
callback: function(form) {
|
449
|
+
return Form.serialize(form);
|
450
|
+
},
|
451
|
+
handleLineBreaks: true,
|
452
|
+
loadingText: 'Loading...',
|
453
|
+
savingClassName: 'inplaceeditor-saving',
|
454
|
+
loadingClassName: 'inplaceeditor-loading',
|
455
|
+
formClassName: 'inplaceeditor-form',
|
456
|
+
highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
|
457
|
+
highlightendcolor: "#FFFFFF",
|
458
|
+
externalControl: null,
|
459
|
+
ajaxOptions: {}
|
460
|
+
}, options || {});
|
461
|
+
|
462
|
+
if(!this.options.formId && this.element.id) {
|
463
|
+
this.options.formId = this.element.id + "-inplaceeditor";
|
464
|
+
if ($(this.options.formId)) {
|
465
|
+
// there's already a form with that name, don't specify an id
|
466
|
+
this.options.formId = null;
|
467
|
+
}
|
468
|
+
}
|
469
|
+
|
470
|
+
if (this.options.externalControl) {
|
471
|
+
this.options.externalControl = $(this.options.externalControl);
|
472
|
+
}
|
473
|
+
|
474
|
+
this.originalBackground = Element.getStyle(this.element, 'background-color');
|
475
|
+
if (!this.originalBackground) {
|
476
|
+
this.originalBackground = "transparent";
|
477
|
+
}
|
478
|
+
|
479
|
+
this.element.title = this.options.clickToEditText;
|
480
|
+
|
481
|
+
this.onclickListener = this.enterEditMode.bindAsEventListener(this);
|
482
|
+
this.mouseoverListener = this.enterHover.bindAsEventListener(this);
|
483
|
+
this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
|
484
|
+
Event.observe(this.element, 'click', this.onclickListener);
|
485
|
+
Event.observe(this.element, 'mouseover', this.mouseoverListener);
|
486
|
+
Event.observe(this.element, 'mouseout', this.mouseoutListener);
|
487
|
+
if (this.options.externalControl) {
|
488
|
+
Event.observe(this.options.externalControl, 'click', this.onclickListener);
|
489
|
+
Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
|
490
|
+
Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
|
491
|
+
}
|
492
|
+
},
|
493
|
+
enterEditMode: function() {
|
494
|
+
if (this.saving) return;
|
495
|
+
if (this.editing) return;
|
496
|
+
this.editing = true;
|
497
|
+
this.onEnterEditMode();
|
498
|
+
if (this.options.externalControl) {
|
499
|
+
Element.hide(this.options.externalControl);
|
500
|
+
}
|
501
|
+
Element.hide(this.element);
|
502
|
+
this.createForm();
|
503
|
+
this.element.parentNode.insertBefore(this.form, this.element);
|
504
|
+
Field.focus(this.editField);
|
505
|
+
// stop the event to avoid a page refresh in Safari
|
506
|
+
if (arguments.length > 1) {
|
507
|
+
Event.stop(arguments[0]);
|
508
|
+
}
|
509
|
+
},
|
510
|
+
createForm: function() {
|
511
|
+
this.form = document.createElement("form");
|
512
|
+
this.form.id = this.options.formId;
|
513
|
+
Element.addClassName(this.form, this.options.formClassName)
|
514
|
+
this.form.onsubmit = this.onSubmit.bind(this);
|
515
|
+
|
516
|
+
this.createEditField();
|
517
|
+
|
518
|
+
if (this.options.textarea) {
|
519
|
+
var br = document.createElement("br");
|
520
|
+
this.form.appendChild(br);
|
521
|
+
}
|
522
|
+
|
523
|
+
okButton = document.createElement("input");
|
524
|
+
okButton.type = "submit";
|
525
|
+
okButton.value = this.options.okText;
|
526
|
+
this.form.appendChild(okButton);
|
527
|
+
|
528
|
+
cancelLink = document.createElement("a");
|
529
|
+
cancelLink.href = "#";
|
530
|
+
cancelLink.appendChild(document.createTextNode(this.options.cancelText));
|
531
|
+
cancelLink.onclick = this.onclickCancel.bind(this);
|
532
|
+
this.form.appendChild(cancelLink);
|
533
|
+
},
|
534
|
+
hasHTMLLineBreaks: function(string) {
|
535
|
+
if (!this.options.handleLineBreaks) return false;
|
536
|
+
return string.match(/<br/i) || string.match(/<p>/i);
|
537
|
+
},
|
538
|
+
convertHTMLLineBreaks: function(string) {
|
539
|
+
return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
|
540
|
+
},
|
541
|
+
createEditField: function() {
|
542
|
+
var text;
|
543
|
+
if(this.options.loadTextURL) {
|
544
|
+
text = this.options.loadingText;
|
545
|
+
} else {
|
546
|
+
text = this.getText();
|
547
|
+
}
|
548
|
+
|
549
|
+
if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
|
550
|
+
this.options.textarea = false;
|
551
|
+
var textField = document.createElement("input");
|
552
|
+
textField.type = "text";
|
553
|
+
textField.name = "value";
|
554
|
+
textField.value = text;
|
555
|
+
textField.style.backgroundColor = this.options.highlightcolor;
|
556
|
+
var size = this.options.size || this.options.cols || 0;
|
557
|
+
if (size != 0) textField.size = size;
|
558
|
+
this.editField = textField;
|
559
|
+
} else {
|
560
|
+
this.options.textarea = true;
|
561
|
+
var textArea = document.createElement("textarea");
|
562
|
+
textArea.name = "value";
|
563
|
+
textArea.value = this.convertHTMLLineBreaks(text);
|
564
|
+
textArea.rows = this.options.rows;
|
565
|
+
textArea.cols = this.options.cols || 40;
|
566
|
+
this.editField = textArea;
|
567
|
+
}
|
568
|
+
|
569
|
+
if(this.options.loadTextURL) {
|
570
|
+
this.loadExternalText();
|
571
|
+
}
|
572
|
+
this.form.appendChild(this.editField);
|
573
|
+
},
|
574
|
+
getText: function() {
|
575
|
+
return this.element.innerHTML;
|
576
|
+
},
|
577
|
+
loadExternalText: function() {
|
578
|
+
Element.addClassName(this.form, this.options.loadingClassName);
|
579
|
+
this.editField.disabled = true;
|
580
|
+
new Ajax.Request(
|
581
|
+
this.options.loadTextURL,
|
582
|
+
Object.extend({
|
583
|
+
asynchronous: true,
|
584
|
+
onComplete: this.onLoadedExternalText.bind(this)
|
585
|
+
}, this.options.ajaxOptions)
|
586
|
+
);
|
587
|
+
},
|
588
|
+
onLoadedExternalText: function(transport) {
|
589
|
+
Element.removeClassName(this.form, this.options.loadingClassName);
|
590
|
+
this.editField.disabled = false;
|
591
|
+
this.editField.value = transport.responseText.stripTags();
|
592
|
+
},
|
593
|
+
onclickCancel: function() {
|
594
|
+
this.onComplete();
|
595
|
+
this.leaveEditMode();
|
596
|
+
return false;
|
597
|
+
},
|
598
|
+
onFailure: function(transport) {
|
599
|
+
this.options.onFailure(transport);
|
600
|
+
if (this.oldInnerHTML) {
|
601
|
+
this.element.innerHTML = this.oldInnerHTML;
|
602
|
+
this.oldInnerHTML = null;
|
603
|
+
}
|
604
|
+
return false;
|
605
|
+
},
|
606
|
+
onSubmit: function() {
|
607
|
+
// onLoading resets these so we need to save them away for the Ajax call
|
608
|
+
var form = this.form;
|
609
|
+
var value = this.editField.value;
|
610
|
+
|
611
|
+
// do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
|
612
|
+
// which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
|
613
|
+
// to be displayed indefinitely
|
614
|
+
this.onLoading();
|
615
|
+
|
616
|
+
new Ajax.Updater(
|
617
|
+
{
|
618
|
+
success: this.element,
|
619
|
+
// don't update on failure (this could be an option)
|
620
|
+
failure: null
|
621
|
+
},
|
622
|
+
this.url,
|
623
|
+
Object.extend({
|
624
|
+
parameters: this.options.callback(form, value),
|
625
|
+
onComplete: this.onComplete.bind(this),
|
626
|
+
onFailure: this.onFailure.bind(this)
|
627
|
+
}, this.options.ajaxOptions)
|
628
|
+
);
|
629
|
+
// stop the event to avoid a page refresh in Safari
|
630
|
+
if (arguments.length > 1) {
|
631
|
+
Event.stop(arguments[0]);
|
632
|
+
}
|
633
|
+
return false;
|
634
|
+
},
|
635
|
+
onLoading: function() {
|
636
|
+
this.saving = true;
|
637
|
+
this.removeForm();
|
638
|
+
this.leaveHover();
|
639
|
+
this.showSaving();
|
640
|
+
},
|
641
|
+
showSaving: function() {
|
642
|
+
this.oldInnerHTML = this.element.innerHTML;
|
643
|
+
this.element.innerHTML = this.options.savingText;
|
644
|
+
Element.addClassName(this.element, this.options.savingClassName);
|
645
|
+
this.element.style.backgroundColor = this.originalBackground;
|
646
|
+
Element.show(this.element);
|
647
|
+
},
|
648
|
+
removeForm: function() {
|
649
|
+
if(this.form) {
|
650
|
+
if (this.form.parentNode) Element.remove(this.form);
|
651
|
+
this.form = null;
|
652
|
+
}
|
653
|
+
},
|
654
|
+
enterHover: function() {
|
655
|
+
if (this.saving) return;
|
656
|
+
this.element.style.backgroundColor = this.options.highlightcolor;
|
657
|
+
if (this.effect) {
|
658
|
+
this.effect.cancel();
|
659
|
+
}
|
660
|
+
Element.addClassName(this.element, this.options.hoverClassName)
|
661
|
+
},
|
662
|
+
leaveHover: function() {
|
663
|
+
if (this.options.backgroundColor) {
|
664
|
+
this.element.style.backgroundColor = this.oldBackground;
|
665
|
+
}
|
666
|
+
Element.removeClassName(this.element, this.options.hoverClassName)
|
667
|
+
if (this.saving) return;
|
668
|
+
this.effect = new Effect.Highlight(this.element, {
|
669
|
+
startcolor: this.options.highlightcolor,
|
670
|
+
endcolor: this.options.highlightendcolor,
|
671
|
+
restorecolor: this.originalBackground
|
672
|
+
});
|
673
|
+
},
|
674
|
+
leaveEditMode: function() {
|
675
|
+
Element.removeClassName(this.element, this.options.savingClassName);
|
676
|
+
this.removeForm();
|
677
|
+
this.leaveHover();
|
678
|
+
this.element.style.backgroundColor = this.originalBackground;
|
679
|
+
Element.show(this.element);
|
680
|
+
if (this.options.externalControl) {
|
681
|
+
Element.show(this.options.externalControl);
|
682
|
+
}
|
683
|
+
this.editing = false;
|
684
|
+
this.saving = false;
|
685
|
+
this.oldInnerHTML = null;
|
686
|
+
this.onLeaveEditMode();
|
687
|
+
},
|
688
|
+
onComplete: function(transport) {
|
689
|
+
this.leaveEditMode();
|
690
|
+
this.options.onComplete.bind(this)(transport, this.element);
|
691
|
+
},
|
692
|
+
onEnterEditMode: function() {},
|
693
|
+
onLeaveEditMode: function() {},
|
694
|
+
dispose: function() {
|
695
|
+
if (this.oldInnerHTML) {
|
696
|
+
this.element.innerHTML = this.oldInnerHTML;
|
697
|
+
}
|
698
|
+
this.leaveEditMode();
|
699
|
+
Event.stopObserving(this.element, 'click', this.onclickListener);
|
700
|
+
Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
|
701
|
+
Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
|
702
|
+
if (this.options.externalControl) {
|
703
|
+
Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
|
704
|
+
Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
|
705
|
+
Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
|
706
|
+
}
|
707
|
+
}
|
708
|
+
};
|