rails 0.12.1 → 0.13.0

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

Potentially problematic release.


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

Files changed (46) hide show
  1. data/CHANGELOG +59 -10
  2. data/Rakefile +23 -11
  3. data/bin/console +6 -5
  4. data/bin/console_sandbox +0 -6
  5. data/bin/listener +86 -0
  6. data/bin/profiler +27 -10
  7. data/bin/rails +3 -1
  8. data/bin/runner +24 -0
  9. data/bin/server +1 -0
  10. data/bin/tracker +69 -0
  11. data/configs/database.yml +3 -0
  12. data/dispatches/dispatch.fcgi +22 -25
  13. data/dispatches/gateway.cgi +97 -0
  14. data/environments/development.rb +2 -0
  15. data/environments/environment.rb +1 -0
  16. data/environments/test.rb +2 -0
  17. data/fresh_rakefile +9 -4
  18. data/helpers/test_helper.rb +16 -5
  19. data/html/404.html +2 -0
  20. data/html/500.html +2 -0
  21. data/html/index.html +3 -0
  22. data/html/javascripts/controls.js +261 -0
  23. data/html/javascripts/dragdrop.js +476 -0
  24. data/html/javascripts/effects.js +570 -0
  25. data/html/javascripts/prototype.js +633 -371
  26. data/lib/console_sandbox.rb +6 -0
  27. data/lib/dispatcher.rb +13 -11
  28. data/lib/fcgi_handler.rb +166 -0
  29. data/lib/rails_generator/generators/applications/app/app_generator.rb +16 -12
  30. data/lib/rails_generator/generators/components/mailer/mailer_generator.rb +2 -2
  31. data/lib/rails_generator/generators/components/mailer/templates/unit_test.rb +3 -1
  32. data/lib/rails_generator/generators/components/migration/USAGE +14 -0
  33. data/lib/rails_generator/generators/components/migration/migration_generator.rb +9 -0
  34. data/lib/rails_generator/generators/components/migration/templates/migration.rb +7 -0
  35. data/lib/rails_generator/generators/components/model/model_generator.rb +1 -1
  36. data/lib/rails_generator/generators/components/scaffold/USAGE +1 -1
  37. data/lib/rails_generator/generators/components/scaffold/templates/controller.rb +11 -11
  38. data/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb +38 -20
  39. data/lib/rails_generator/generators/components/scaffold/templates/layout.rhtml +2 -0
  40. data/lib/rails_generator/generators/components/scaffold/templates/style.css +22 -1
  41. data/lib/rails_generator/generators/components/scaffold/templates/view_edit.rhtml +2 -2
  42. data/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml +5 -5
  43. data/lib/rails_generator/generators/components/scaffold/templates/view_new.rhtml +1 -1
  44. data/lib/rubyprof_ext.rb +35 -0
  45. data/lib/webrick_server.rb +84 -43
  46. metadata +22 -8
@@ -5,6 +5,9 @@ development:
5
5
  username: root
6
6
  password:
7
7
 
8
+ # Warning: The database defined as 'test' will be erased and
9
+ # re-generated from your development database when you run 'rake'.
10
+ # Do not set this db to the same as development or production.
8
11
  test:
9
12
  adapter: mysql
10
13
  database: rails_test
@@ -1,27 +1,24 @@
1
1
  #!/usr/local/bin/ruby
2
+ #
3
+ # You may specify the path to the FastCGI crash log (a log of unhandled
4
+ # exceptions which forced the FastCGI instance to exit, great for debugging)
5
+ # and the number of requests to process before running garbage collection.
6
+ #
7
+ # By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log
8
+ # and the GC period is nil (turned off). A reasonable number of requests
9
+ # could range from 10-100 depending on the memory footprint of your app.
10
+ #
11
+ # Example:
12
+ # # Default log path, normal GC behavior.
13
+ # RailsFCGIHandler.process!
14
+ #
15
+ # # Default log path, 50 requests between GC.
16
+ # RailsFCGIHandler.process! nil, 50
17
+ #
18
+ # # Custom log path, normal GC behavior.
19
+ # RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log'
20
+ #
21
+ require File.dirname(__FILE__) + "/../config/environment"
22
+ require 'fcgi_handler'
2
23
 
3
- def dispatcher_error(path,e,msg="")
4
- error_message =
5
- "[#{Time.now}] Dispatcher failed to catch: #{e} (#{e.class})\n #{e.backtrace.join("\n ")}\n#{msg}"
6
- Logger.new(path).fatal(error_message)
7
- rescue Object => log_error
8
- STDERR << "Couldn't write to #{path} (#{e} [#{e.class}])\n" << error_message
9
- end
10
-
11
- begin
12
- require File.dirname(__FILE__) + "/../config/environment"
13
- require 'dispatcher'
14
- require 'fcgi'
15
-
16
- log_file_path = "#{RAILS_ROOT}/log/fastcgi.crash.log"
17
-
18
- FCGI.each_cgi do |cgi|
19
- begin
20
- Dispatcher.dispatch(cgi)
21
- rescue Object => rails_error
22
- dispatcher_error(log_file_path, rails_error)
23
- end
24
- end
25
- rescue Object => fcgi_error
26
- dispatcher_error(log_file_path, fcgi_error, "FCGI process #{$$} killed by this error\n")
27
- end
24
+ RailsFCGIHandler.process!
@@ -0,0 +1,97 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ require 'drb'
4
+
5
+ # This file includes an experimental gateway CGI implementation. It will work
6
+ # only on platforms which support both fork and sockets.
7
+ #
8
+ # To enable it edit public/.htaccess and replace dispatch.cgi with gateway.cgi.
9
+ #
10
+ # Next, create the directory log/drb_gateway and grant the apache user rw access
11
+ # to said directory.
12
+ #
13
+ # On the next request to your server, the gateway tracker should start up, along
14
+ # with a few listener processes. This setup should provide you with much better
15
+ # speeds than dispatch.cgi.
16
+ #
17
+ # Keep in mind that the first request made to the server will be slow, as the
18
+ # tracker and listeners will have to load. Also, the tracker and listeners will
19
+ # shutdown after a period if inactivity. You can set this value below -- the
20
+ # default is 90 seconds.
21
+
22
+ TrackerSocket = File.expand_path(File.join(File.dirname(__FILE__), '../log/drb_gateway/tracker.sock'))
23
+ DieAfter = 90 # Seconds
24
+ Listeners = 3
25
+
26
+ def message(s)
27
+ $stderr.puts "gateway.cgi: #{s}" if ENV && ENV["DEBUG_GATEWAY"]
28
+ end
29
+
30
+ def listener_socket(number)
31
+ File.expand_path(File.join(File.dirname(__FILE__), "../log/drb_gateway/listener_#{number}.sock"))
32
+ end
33
+
34
+ unless File.exists? TrackerSocket
35
+ message "Starting tracker and #{Listeners} listeners"
36
+ fork do
37
+ Process.setsid
38
+ STDIN.reopen "/dev/null"
39
+ STDOUT.reopen "/dev/null", "a"
40
+
41
+ root = File.expand_path(File.dirname(__FILE__) + '/..')
42
+
43
+ message "starting tracker"
44
+ fork do
45
+ ARGV.clear
46
+ ARGV << TrackerSocket << Listeners.to_s << DieAfter.to_s
47
+ load File.join(root, 'script', 'tracker')
48
+ end
49
+
50
+ message "starting listeners"
51
+ require File.join(root, 'config/environment.rb')
52
+ Listeners.times do |number|
53
+ fork do
54
+ ARGV.clear
55
+ ARGV << listener_socket(number) << DieAfter.to_s
56
+ load File.join(root, 'script', 'listener')
57
+ end
58
+ end
59
+ end
60
+
61
+ message "waiting for tracker and listener to arise..."
62
+ ready = false
63
+ 10.times do
64
+ sleep 0.5
65
+ break if (ready = File.exists?(TrackerSocket) && File.exists?(listener_socket(0)))
66
+ end
67
+
68
+ if ready
69
+ message "tracker and listener are ready"
70
+ else
71
+ message "Waited 5 seconds, listener and tracker not ready... dropping request"
72
+ Kernel.exit 1
73
+ end
74
+ end
75
+
76
+ DRb.start_service
77
+
78
+ message "connecting to tracker"
79
+ tracker = DRbObject.new_with_uri("drbunix:#{TrackerSocket}")
80
+
81
+ input = $stdin.read
82
+ $stdin.close
83
+
84
+ env = ENV.inspect
85
+
86
+ output = nil
87
+ tracker.with_listener do |number|
88
+ message "connecting to listener #{number}"
89
+ socket = listener_socket(number)
90
+ listener = DRbObject.new_with_uri("drbunix:#{socket}")
91
+ output = listener.process(env, input)
92
+ message "listener #{number} has finished, writing output"
93
+ end
94
+
95
+ $stdout.write output
96
+ $stdout.flush
97
+ $stdout.close
@@ -1,3 +1,5 @@
1
+ require 'active_support/whiny_nil'
2
+
1
3
  Dependencies.mechanism = :load
2
4
  ActionController::Base.consider_all_requests_local = true
3
5
  ActionController::Base.perform_caching = false
@@ -50,6 +50,7 @@ ActiveRecord::Base.establish_connection
50
50
  # Configure defaults if the included environment did not.
51
51
  begin
52
52
  RAILS_DEFAULT_LOGGER = Logger.new("#{RAILS_ROOT}/log/#{RAILS_ENV}.log")
53
+ RAILS_DEFAULT_LOGGER.level = (RAILS_ENV == 'production' ? Logger::INFO : Logger::DEBUG)
53
54
  rescue StandardError
54
55
  RAILS_DEFAULT_LOGGER = Logger.new(STDERR)
55
56
  RAILS_DEFAULT_LOGGER.level = Logger::WARN
@@ -1,3 +1,5 @@
1
+ require 'active_support/whiny_nil'
2
+
1
3
  Dependencies.mechanism = :require
2
4
  ActionController::Base.consider_all_requests_local = true
3
5
  ActionController::Base.perform_caching = false
@@ -16,7 +16,7 @@ task :environment do
16
16
  end
17
17
  end
18
18
 
19
- desc "Generate API documentatio, show coding stats"
19
+ desc "Generate API documentation, show coding stats"
20
20
  task :doc => [ :appdoc, :stats ]
21
21
 
22
22
 
@@ -131,7 +131,7 @@ task :clone_structure_to_test => [ :db_structure_dump, :purge_test_database ] do
131
131
  when "postgresql"
132
132
  ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"]
133
133
  ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"]
134
- ENV['PGPASSWORD'] = abcs["test"]["password"]
134
+ ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"]
135
135
  `psql -U "#{abcs["test"]["username"]}" -f db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}`
136
136
  when "sqlite", "sqlite3"
137
137
  `#{abcs[RAILS_ENV]["adapter"]} #{abcs["test"]["dbfile"]} < db/#{RAILS_ENV}_structure.sql`
@@ -150,7 +150,7 @@ task :db_structure_dump => :environment do
150
150
  when "postgresql"
151
151
  ENV['PGHOST'] = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"]
152
152
  ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"]
153
- ENV['PGPASSWORD'] = abcs[RAILS_ENV]["password"]
153
+ ENV['PGPASSWORD'] = abcs[RAILS_ENV]["password"].to_s if abcs[RAILS_ENV]["password"]
154
154
  `pg_dump -U "#{abcs[RAILS_ENV]["username"]}" -s -x -O -f db/#{RAILS_ENV}_structure.sql #{abcs[RAILS_ENV]["database"]}`
155
155
  when "sqlite", "sqlite3"
156
156
  `#{abcs[RAILS_ENV]["adapter"]} #{abcs[RAILS_ENV]["dbfile"]} .schema > db/#{RAILS_ENV}_structure.sql`
@@ -169,7 +169,7 @@ task :purge_test_database => :environment do
169
169
  when "postgresql"
170
170
  ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"]
171
171
  ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"]
172
- ENV['PGPASSWORD'] = abcs["test"]["password"]
172
+ ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"]
173
173
  `dropdb -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}`
174
174
  `createdb -T template0 -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}`
175
175
  when "sqlite","sqlite3"
@@ -185,4 +185,9 @@ task :clear_logs => :environment do
185
185
  f = File.open(log_file, "w")
186
186
  f.close
187
187
  end
188
+ end
189
+
190
+ desc "Migrate the database according to the migrate scripts in db/migrate"
191
+ task :migrate => :environment do
192
+ ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/db/migrate/')
188
193
  end
@@ -1,5 +1,8 @@
1
1
  ENV["RAILS_ENV"] = "test"
2
- require File.dirname(__FILE__) + "/../config/environment"
2
+
3
+ # Expand the path to environment so that Ruby does not load it multiple times
4
+ # File.expand_path can be removed if Ruby 1.9 is in use.
5
+ require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
3
6
  require 'application'
4
7
 
5
8
  require 'test/unit'
@@ -8,8 +11,16 @@ require 'action_controller/test_process'
8
11
  require 'action_web_service/test_invoke'
9
12
  require 'breakpoint'
10
13
 
11
- def create_fixtures(*table_names)
12
- Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures", table_names)
13
- end
14
-
15
14
  Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
15
+
16
+ class Test::Unit::TestCase
17
+ # Turn these on to use transactional fixtures with table_name(:fixture_name) instantiation of fixtures
18
+ # self.use_transactional_fixtures = true
19
+ # self.use_instantiated_fixtures = false
20
+
21
+ def create_fixtures(*table_names)
22
+ Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures", table_names)
23
+ end
24
+
25
+ # Add more helper methods to be used by all tests here...
26
+ end
@@ -1,3 +1,5 @@
1
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
2
+ "http://www.w3.org/TR/html4/loose.dtd">
1
3
  <html>
2
4
  <body>
3
5
  <h1>File not found</h1>
@@ -1,3 +1,5 @@
1
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
2
+ "http://www.w3.org/TR/html4/loose.dtd">
1
3
  <html>
2
4
  <body>
3
5
  <h1>Application error (Apache)</h1>
@@ -1,3 +1,5 @@
1
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
2
+ "http://www.w3.org/TR/html4/loose.dtd">
1
3
  <html>
2
4
  <head>
3
5
  <title>Rails: Welcome on board</title>
@@ -58,6 +60,7 @@
58
60
  <li>See all the tests run by running <code>rake</code>.
59
61
  <li>Develop your Rails application!
60
62
  <li>Setup Apache with <a href="http://www.fastcgi.com">FastCGI</a> (and <a href="http://raa.ruby-lang.org/list.rhtml?name=fcgi">Ruby bindings</a>), if you need better performance
63
+ <li>Remove the dispatches you don't use (so if you're on FastCGI, delete/move dispatch.rb, dispatch.cgi and gateway.cgi)</li>
61
64
  </ol>
62
65
 
63
66
  <p>
@@ -0,0 +1,261 @@
1
+ // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining
4
+ // a copy of this software and associated documentation files (the
5
+ // "Software"), to deal in the Software without restriction, including
6
+ // without limitation the rights to use, copy, modify, merge, publish,
7
+ // distribute, sublicense, and/or sell copies of the Software, and to
8
+ // permit persons to whom the Software is furnished to do so, subject to
9
+ // the following conditions:
10
+ //
11
+ // The above copyright notice and this permission notice shall be
12
+ // included in all copies or substantial portions of the Software.
13
+ //
14
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
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
+ }
39
+
40
+ Ajax.Autocompleter = Class.create();
41
+ Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({
42
+ initialize: function(element, update, url, options) {
43
+ this.element = $(element);
44
+ this.update = $(update);
45
+ this.has_focus = false;
46
+ this.changed = false;
47
+ this.active = false;
48
+ this.index = 0;
49
+ this.entry_count = 0;
50
+ this.url = url;
51
+
52
+ this.setOptions(options);
53
+ this.options.asynchronous = true;
54
+ this.options.onComplete = this.onComplete.bind(this)
55
+ this.options.frequency = this.options.frequency || 0.4;
56
+ this.options.min_chars = this.options.min_chars || 1;
57
+ this.options.method = 'post';
58
+
59
+ this.options.onShow = this.options.onShow ||
60
+ function(element, update){
61
+ if(!update.style.position || update.style.position=='absolute') {
62
+ update.style.position = 'absolute';
63
+ var offsets = Position.cumulativeOffset(element);
64
+ update.style.left = offsets[0] + 'px';
65
+ update.style.top = (offsets[1] + element.offsetHeight) + 'px';
66
+ update.style.width = element.offsetWidth + 'px';
67
+ }
68
+ new Effect.Appear(update,{duration:0.3});
69
+ };
70
+ this.options.onHide = this.options.onHide ||
71
+ function(element, update){ new Effect.Fade(update,{duration:0.3}) };
72
+
73
+
74
+ if(this.options.indicator)
75
+ this.indicator = $(this.options.indicator);
76
+
77
+ this.observer = null;
78
+
79
+ Element.hide(this.update);
80
+
81
+ Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
82
+ Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
83
+ },
84
+
85
+ show: function() {
86
+ if(this.update.style.display=='none') this.options.onShow(this.element, this.update);
87
+ if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') {
88
+ new Insertion.After(this.update,
89
+ '<iframe id="' + this.update.id + '_iefix" '+
90
+ 'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(apacity=0);" ' +
91
+ 'src="javascript:;" frameborder="0" scrolling="no"></iframe>');
92
+ this.iefix = $(this.update.id+'_iefix');
93
+ }
94
+ if(this.iefix) {
95
+ Position.clone(this.update, this.iefix);
96
+ this.iefix.style.zIndex = 1;
97
+ this.update.style.zIndex = 2;
98
+ Element.show(this.iefix);
99
+ }
100
+ },
101
+
102
+ hide: function() {
103
+ if(this.update.style.display=='') this.options.onHide(this.element, this.update);
104
+ if(this.iefix) Element.hide(this.iefix);
105
+ },
106
+
107
+ startIndicator: function() {
108
+ if(this.indicator) Element.show(this.indicator);
109
+ },
110
+
111
+ stopIndicator: function() {
112
+ if(this.indicator) Element.hide(this.indicator);
113
+ },
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
+
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
+ onKeyPress: function(event) {
160
+ if(this.active)
161
+ switch(event.keyCode) {
162
+ case Event.KEY_TAB:
163
+ case Event.KEY_RETURN:
164
+ this.select_entry();
165
+ Event.stop(event);
166
+ case Event.KEY_ESC:
167
+ this.hide();
168
+ this.active = false;
169
+ return;
170
+ case Event.KEY_LEFT:
171
+ case Event.KEY_RIGHT:
172
+ return;
173
+ case Event.KEY_UP:
174
+ this.mark_previous();
175
+ this.render();
176
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
177
+ return;
178
+ case Event.KEY_DOWN:
179
+ this.mark_next();
180
+ this.render();
181
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
182
+ return;
183
+ }
184
+ else
185
+ if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
186
+ return;
187
+
188
+ this.changed = true;
189
+ this.has_focus = true;
190
+
191
+ if(this.observer) clearTimeout(this.observer);
192
+ this.observer =
193
+ setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
194
+ },
195
+
196
+ onHover: function(event) {
197
+ var element = Event.findElement(event, 'LI');
198
+ if(this.index != element.autocompleteIndex)
199
+ {
200
+ this.index = element.autocompleteIndex;
201
+ this.render();
202
+ }
203
+ Event.stop(event);
204
+ },
205
+
206
+ onClick: function(event) {
207
+ var element = Event.findElement(event, 'LI');
208
+ this.index = element.autocompleteIndex;
209
+ this.select_entry();
210
+ Event.stop(event);
211
+ },
212
+
213
+ onBlur: function(event) {
214
+ // needed to make click events working
215
+ setTimeout(this.hide.bind(this), 250);
216
+ this.has_focus = false;
217
+ this.active = false;
218
+ },
219
+
220
+ render: function() {
221
+ if(this.entry_count > 0) {
222
+ for (var i = 0; i < this.entry_count; i++)
223
+ this.index==i ?
224
+ Element.addClassName(this.get_entry(i),"selected") :
225
+ Element.removeClassName(this.get_entry(i),"selected");
226
+
227
+ if(this.has_focus) {
228
+ if(this.get_current_entry().scrollIntoView)
229
+ this.get_current_entry().scrollIntoView(false);
230
+
231
+ this.show();
232
+ this.active = true;
233
+ }
234
+ } else this.hide();
235
+ },
236
+
237
+ mark_previous: function() {
238
+ if(this.index > 0) this.index--
239
+ else this.index = this.entry_count-1;
240
+ },
241
+
242
+ mark_next: function() {
243
+ if(this.index < this.entry_count-1) this.index++
244
+ else this.index = 0;
245
+ },
246
+
247
+ get_entry: function(index) {
248
+ return this.update.firstChild.childNodes[index];
249
+ },
250
+
251
+ get_current_entry: function() {
252
+ return this.get_entry(this.index);
253
+ },
254
+
255
+ select_entry: function() {
256
+ this.active = false;
257
+ value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML();
258
+ this.element.value = value;
259
+ this.element.focus();
260
+ }
261
+ });