feedreader 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +604 -0
- data/README +190 -0
- data/Rakefile +227 -0
- data/app/controllers/application.rb +4 -0
- data/app/controllers/feed_controller.rb +21 -0
- data/app/helpers/application_helper.rb +3 -0
- data/app/helpers/feed_helper.rb +2 -0
- data/app/views/feed/index.rhtml +2 -0
- data/app/views/feed/view.rhtml +15 -0
- data/app/views/feed/xml.rxml +1 -0
- data/app/views/layouts/common.rhtml +27 -0
- data/config/database.yml +23 -0
- data/config/environment.rb +87 -0
- data/config/environments/development.rb +14 -0
- data/config/environments/production.rb +8 -0
- data/config/environments/test.rb +17 -0
- data/config/routes.rb +19 -0
- data/db/development_structure.sql +0 -0
- data/doc/README_FOR_APP +2 -0
- data/public/.htaccess +32 -0
- data/public/404.html +8 -0
- data/public/500.html +8 -0
- data/public/dispatch.cgi +10 -0
- data/public/dispatch.fcgi +24 -0
- data/public/dispatch.rb +10 -0
- data/public/favicon.ico +0 -0
- data/public/javascripts/controls.js +446 -0
- data/public/javascripts/dragdrop.js +537 -0
- data/public/javascripts/effects.js +612 -0
- data/public/javascripts/prototype.js +1038 -0
- data/script/benchmarker +19 -0
- data/script/breakpointer +4 -0
- data/script/console +23 -0
- data/script/destroy +7 -0
- data/script/generate +7 -0
- data/script/profiler +34 -0
- data/script/runner +29 -0
- data/script/server +49 -0
- data/test/functional/feed_controller_test.rb +18 -0
- data/test/test_helper.rb +26 -0
- metadata +104 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
<div class="contentBox">
|
2
|
+
<h1><a href="<%= h @feed.link %>"><%= @feed.title %></a></h1>
|
3
|
+
<p>
|
4
|
+
<%= @feed.description %>
|
5
|
+
</p><%
|
6
|
+
for feed_item in @feed.items %>
|
7
|
+
<div class="feedItemBox">
|
8
|
+
<h2><a href="<%= h feed_item.link %>"><%= feed_item.title %></a></h2>
|
9
|
+
<p>
|
10
|
+
<%= feed_item.description %>
|
11
|
+
</p>
|
12
|
+
</div><%
|
13
|
+
end
|
14
|
+
%>
|
15
|
+
</div>
|
@@ -0,0 +1 @@
|
|
1
|
+
@feed.build_xml((@feed_type or "atom"), 0.0, xml)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?><%
|
2
|
+
@headers["Content-Type"] = "text/html"
|
3
|
+
unless @request.env['HTTP_ACCEPT'].nil?
|
4
|
+
if @request.env['HTTP_ACCEPT'].include? "application/xhtml+xml"
|
5
|
+
@headers["Content-Type"] = "application/xhtml+xml"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
%>
|
9
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
10
|
+
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
11
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
|
12
|
+
<head>
|
13
|
+
<title>FeedReader</title>
|
14
|
+
<script type="text/javascript"><!-- FOUC eliminator --></script>
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
<div class="urlBox">
|
18
|
+
<form action="<%= url_for :controller => 'feed',
|
19
|
+
:action => 'view' %>" method="get">
|
20
|
+
Feed Url: <input type="text" name="feed_url" value="<%=
|
21
|
+
@params['feed_url'] %>"/>
|
22
|
+
</form>
|
23
|
+
<strong><%= FeedTools.tidy_enabled? ? "Tidy Enabled" : "Tidy Disabled" %></strong>
|
24
|
+
</div>
|
25
|
+
<%= @content_for_layout %>
|
26
|
+
</body>
|
27
|
+
</html>
|
data/config/database.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
development:
|
2
|
+
adapter: mysql
|
3
|
+
database: rails_development
|
4
|
+
host: localhost
|
5
|
+
username: root
|
6
|
+
password:
|
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.
|
11
|
+
test:
|
12
|
+
adapter: mysql
|
13
|
+
database: rails_test
|
14
|
+
host: localhost
|
15
|
+
username: root
|
16
|
+
password:
|
17
|
+
|
18
|
+
production:
|
19
|
+
adapter: mysql
|
20
|
+
database: rails_production
|
21
|
+
host: localhost
|
22
|
+
username: root
|
23
|
+
password:
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# Load the Rails framework and configure your application.
|
2
|
+
# You can include your own configuration at the end of this file.
|
3
|
+
#
|
4
|
+
# Be sure to restart your webserver when you modify this file.
|
5
|
+
|
6
|
+
# The path to the root directory of your application.
|
7
|
+
RAILS_ROOT = File.join(File.dirname(__FILE__), '..')
|
8
|
+
|
9
|
+
# The environment your application is currently running. Don't set
|
10
|
+
# this here; put it in your webserver's configuration as the RAILS_ENV
|
11
|
+
# environment variable instead.
|
12
|
+
#
|
13
|
+
# See config/environments/*.rb for environment-specific configuration.
|
14
|
+
RAILS_ENV = ENV['RAILS_ENV'] || 'development'
|
15
|
+
|
16
|
+
|
17
|
+
# Load the Rails framework. Mock classes for testing come first.
|
18
|
+
ADDITIONAL_LOAD_PATHS = ["#{RAILS_ROOT}/test/mocks/#{RAILS_ENV}"]
|
19
|
+
|
20
|
+
# Then model subdirectories.
|
21
|
+
ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/app/models/[_a-z]*"])
|
22
|
+
ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/components/[_a-z]*"])
|
23
|
+
|
24
|
+
# Followed by the standard includes.
|
25
|
+
ADDITIONAL_LOAD_PATHS.concat %w(
|
26
|
+
app
|
27
|
+
app/models
|
28
|
+
app/controllers
|
29
|
+
app/helpers
|
30
|
+
app/apis
|
31
|
+
components
|
32
|
+
config
|
33
|
+
lib
|
34
|
+
vendor
|
35
|
+
vendor/rails/railties
|
36
|
+
vendor/rails/railties/lib
|
37
|
+
vendor/rails/actionpack/lib
|
38
|
+
vendor/rails/activesupport/lib
|
39
|
+
vendor/rails/activerecord/lib
|
40
|
+
vendor/rails/actionmailer/lib
|
41
|
+
vendor/rails/actionwebservice/lib
|
42
|
+
).map { |dir| "#{RAILS_ROOT}/#{dir}" }.select { |dir| File.directory?(dir) }
|
43
|
+
|
44
|
+
# Prepend to $LOAD_PATH
|
45
|
+
ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) }
|
46
|
+
|
47
|
+
# Require Rails libraries.
|
48
|
+
require 'rubygems' unless File.directory?("#{RAILS_ROOT}/vendor/rails")
|
49
|
+
|
50
|
+
require 'active_support'
|
51
|
+
require 'active_record'
|
52
|
+
require 'action_controller'
|
53
|
+
require 'action_mailer'
|
54
|
+
require 'action_web_service'
|
55
|
+
|
56
|
+
# Environment-specific configuration.
|
57
|
+
require_dependency "environments/#{RAILS_ENV}"
|
58
|
+
ActiveRecord::Base.configurations = File.open("#{RAILS_ROOT}/config/database.yml") { |f| YAML::load(f) }
|
59
|
+
ActiveRecord::Base.establish_connection
|
60
|
+
|
61
|
+
|
62
|
+
# Configure defaults if the included environment did not.
|
63
|
+
begin
|
64
|
+
RAILS_DEFAULT_LOGGER = Logger.new("#{RAILS_ROOT}/log/#{RAILS_ENV}.log")
|
65
|
+
RAILS_DEFAULT_LOGGER.level = (RAILS_ENV == 'production' ? Logger::INFO : Logger::DEBUG)
|
66
|
+
rescue StandardError
|
67
|
+
RAILS_DEFAULT_LOGGER = Logger.new(STDERR)
|
68
|
+
RAILS_DEFAULT_LOGGER.level = Logger::WARN
|
69
|
+
RAILS_DEFAULT_LOGGER.warn(
|
70
|
+
"Rails Error: Unable to access log file. Please ensure that log/#{RAILS_ENV}.log exists and is chmod 0666. " +
|
71
|
+
"The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
[ActiveRecord, ActionController, ActionMailer].each { |mod| mod::Base.logger ||= RAILS_DEFAULT_LOGGER }
|
76
|
+
[ActionController, ActionMailer].each { |mod| mod::Base.template_root ||= "#{RAILS_ROOT}/app/views/" }
|
77
|
+
|
78
|
+
# Set up routes.
|
79
|
+
ActionController::Routing::Routes.reload
|
80
|
+
|
81
|
+
Controllers = Dependencies::LoadingModule.root(
|
82
|
+
File.join(RAILS_ROOT, 'app', 'controllers'),
|
83
|
+
File.join(RAILS_ROOT, 'components')
|
84
|
+
)
|
85
|
+
|
86
|
+
# Include your app's configuration here:
|
87
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# In the development environment your application's code is reloaded on
|
2
|
+
# every request. This slows down response time but is perfect for development
|
3
|
+
# since you don't have to restart the webserver when you make code changes.
|
4
|
+
|
5
|
+
# Log error messages when you accidentally call methods on nil.
|
6
|
+
require 'active_support/whiny_nil'
|
7
|
+
|
8
|
+
# Reload code; show full error reports; disable caching.
|
9
|
+
Dependencies.mechanism = :load
|
10
|
+
ActionController::Base.consider_all_requests_local = true
|
11
|
+
ActionController::Base.perform_caching = false
|
12
|
+
|
13
|
+
# The breakpoint server port that script/breakpointer connects to.
|
14
|
+
BREAKPOINT_SERVER_PORT = 42531
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# The production environment is meant for finished, "live" apps.
|
2
|
+
# Code is not reloaded between requests, full error reports are disabled,
|
3
|
+
# and caching is turned on.
|
4
|
+
|
5
|
+
# Don't reload code; don't show full error reports; enable caching.
|
6
|
+
Dependencies.mechanism = :require
|
7
|
+
ActionController::Base.consider_all_requests_local = false
|
8
|
+
ActionController::Base.perform_caching = true
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# The test environment is used exclusively to run your application's
|
2
|
+
# test suite. You never need to work with it otherwise. Remember that
|
3
|
+
# your test database is "scratch space" for the test suite and is wiped
|
4
|
+
# and recreated between test runs. Don't rely on the data there!
|
5
|
+
|
6
|
+
# Log error messages when you accidentally call methods on nil.
|
7
|
+
require 'active_support/whiny_nil'
|
8
|
+
|
9
|
+
# Don't reload code; show full error reports; disable caching.
|
10
|
+
Dependencies.mechanism = :require
|
11
|
+
ActionController::Base.consider_all_requests_local = true
|
12
|
+
ActionController::Base.perform_caching = false
|
13
|
+
|
14
|
+
# Tell ActionMailer not to deliver emails to the real world.
|
15
|
+
# The :test delivery method accumulates sent emails in the
|
16
|
+
# ActionMailer::Base.deliveries array.
|
17
|
+
ActionMailer::Base.delivery_method = :test
|
data/config/routes.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
ActionController::Routing::Routes.draw do |map|
|
2
|
+
# Add your own custom routes here.
|
3
|
+
# The priority is based upon order of creation: first created -> highest priority.
|
4
|
+
|
5
|
+
# Here's a sample route:
|
6
|
+
# map.connect 'products/:id', :controller => 'catalog', :action => 'view'
|
7
|
+
# Keep in mind you can assign values other than :controller and :action
|
8
|
+
|
9
|
+
# You can have the root of your site routed by hooking up ''
|
10
|
+
# -- just remember to delete public/index.html.
|
11
|
+
# map.connect '', :controller => "welcome"
|
12
|
+
|
13
|
+
# Allow downloading Web Service WSDL as a file with an extension
|
14
|
+
# instead of a file named 'wsdl'
|
15
|
+
map.connect ':controller/service.wsdl', :action => 'wsdl'
|
16
|
+
|
17
|
+
# Install the default route as the lowest priority.
|
18
|
+
map.connect ':controller/:action/:id'
|
19
|
+
end
|
File without changes
|
data/doc/README_FOR_APP
ADDED
data/public/.htaccess
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# General Apache options
|
2
|
+
AddHandler fastcgi-script .fcgi
|
3
|
+
AddHandler cgi-script .cgi
|
4
|
+
Options +FollowSymLinks +ExecCGI
|
5
|
+
|
6
|
+
# If you don't want Rails to look in certain directories,
|
7
|
+
# use the following rewrite rules so that Apache won't rewrite certain requests
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
# RewriteCond %{REQUEST_URI} ^/notrails.*
|
11
|
+
# RewriteRule .* - [L]
|
12
|
+
|
13
|
+
# Redirect all requests not available on the filesystem to Rails
|
14
|
+
# By default the cgi dispatcher is used which is very slow
|
15
|
+
#
|
16
|
+
# For better performance replace the dispatcher with the fastcgi one
|
17
|
+
#
|
18
|
+
# Example:
|
19
|
+
# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
|
20
|
+
RewriteEngine On
|
21
|
+
RewriteRule ^$ index.html [QSA]
|
22
|
+
RewriteRule ^([^.]+)$ $1.html [QSA]
|
23
|
+
RewriteCond %{REQUEST_FILENAME} !-f
|
24
|
+
RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
|
25
|
+
|
26
|
+
# In case Rails experiences terminal errors
|
27
|
+
# Instead of displaying this message you can supply a file here which will be rendered instead
|
28
|
+
#
|
29
|
+
# Example:
|
30
|
+
# ErrorDocument 500 /500.html
|
31
|
+
|
32
|
+
ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
|
data/public/404.html
ADDED
data/public/500.html
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
2
|
+
"http://www.w3.org/TR/html4/loose.dtd">
|
3
|
+
<html>
|
4
|
+
<body>
|
5
|
+
<h1>Application error (Apache)</h1>
|
6
|
+
<p>Change this error message for exceptions thrown outside of an action (like in Dispatcher setups or broken Ruby code) in public/500.html</p>
|
7
|
+
</body>
|
8
|
+
</html>
|
data/public/dispatch.cgi
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
|
4
|
+
|
5
|
+
# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
|
6
|
+
# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
|
7
|
+
require "dispatcher"
|
8
|
+
|
9
|
+
ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
|
10
|
+
Dispatcher.dispatch
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/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'
|
23
|
+
|
24
|
+
RailsFCGIHandler.process!
|
data/public/dispatch.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
|
4
|
+
|
5
|
+
# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
|
6
|
+
# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
|
7
|
+
require "dispatcher"
|
8
|
+
|
9
|
+
ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
|
10
|
+
Dispatcher.dispatch
|
data/public/favicon.ico
ADDED
File without changes
|
@@ -0,0 +1,446 @@
|
|
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)
|
3
|
+
//
|
4
|
+
// Permission is hereby granted, free of charge, to any person obtaining
|
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
|
+
}
|
39
|
+
|
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) {
|
69
|
+
this.element = $(element);
|
70
|
+
this.update = $(update);
|
71
|
+
this.has_focus = false;
|
72
|
+
this.changed = false;
|
73
|
+
this.active = false;
|
74
|
+
this.index = 0;
|
75
|
+
this.entry_count = 0;
|
76
|
+
|
77
|
+
if (this.setOptions)
|
78
|
+
this.setOptions(options);
|
79
|
+
else
|
80
|
+
this.options = {}
|
81
|
+
|
82
|
+
this.options.tokens = this.options.tokens || new Array();
|
83
|
+
this.options.frequency = this.options.frequency || 0.4;
|
84
|
+
this.options.min_chars = this.options.min_chars || 1;
|
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';
|
89
|
+
var offsets = Position.cumulativeOffset(element);
|
90
|
+
update.style.left = offsets[0] + 'px';
|
91
|
+
update.style.top = (offsets[1] + element.offsetHeight) + 'px';
|
92
|
+
update.style.width = element.offsetWidth + 'px';
|
93
|
+
}
|
94
|
+
new Effect.Appear(update,{duration:0.15});
|
95
|
+
};
|
96
|
+
this.options.onHide = this.options.onHide ||
|
97
|
+
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
|
98
|
+
|
99
|
+
if(this.options.indicator)
|
100
|
+
this.indicator = $(this.options.indicator);
|
101
|
+
|
102
|
+
if (typeof(this.options.tokens) == 'string')
|
103
|
+
this.options.tokens = new Array(this.options.tokens);
|
104
|
+
|
105
|
+
this.observer = null;
|
106
|
+
|
107
|
+
Element.hide(this.update);
|
108
|
+
|
109
|
+
Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
|
110
|
+
Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
|
111
|
+
},
|
112
|
+
|
113
|
+
show: function() {
|
114
|
+
if(this.update.style.display=='none') this.options.onShow(this.element, this.update);
|
115
|
+
if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') {
|
116
|
+
new Insertion.After(this.update,
|
117
|
+
'<iframe id="' + this.update.id + '_iefix" '+
|
118
|
+
'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
|
119
|
+
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
|
120
|
+
this.iefix = $(this.update.id+'_iefix');
|
121
|
+
}
|
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
|
+
}
|
128
|
+
},
|
129
|
+
|
130
|
+
hide: function() {
|
131
|
+
if(this.update.style.display=='') this.options.onHide(this.element, this.update);
|
132
|
+
if(this.iefix) Element.hide(this.iefix);
|
133
|
+
},
|
134
|
+
|
135
|
+
startIndicator: function() {
|
136
|
+
if(this.indicator) Element.show(this.indicator);
|
137
|
+
},
|
138
|
+
|
139
|
+
stopIndicator: function() {
|
140
|
+
if(this.indicator) Element.hide(this.indicator);
|
141
|
+
},
|
142
|
+
|
143
|
+
onKeyPress: function(event) {
|
144
|
+
if(this.active)
|
145
|
+
switch(event.keyCode) {
|
146
|
+
case Event.KEY_TAB:
|
147
|
+
case Event.KEY_RETURN:
|
148
|
+
this.select_entry();
|
149
|
+
Event.stop(event);
|
150
|
+
case Event.KEY_ESC:
|
151
|
+
this.hide();
|
152
|
+
this.active = false;
|
153
|
+
return;
|
154
|
+
case Event.KEY_LEFT:
|
155
|
+
case Event.KEY_RIGHT:
|
156
|
+
return;
|
157
|
+
case Event.KEY_UP:
|
158
|
+
this.mark_previous();
|
159
|
+
this.render();
|
160
|
+
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
|
161
|
+
return;
|
162
|
+
case Event.KEY_DOWN:
|
163
|
+
this.mark_next();
|
164
|
+
this.render();
|
165
|
+
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
|
166
|
+
return;
|
167
|
+
}
|
168
|
+
else
|
169
|
+
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
|
170
|
+
return;
|
171
|
+
|
172
|
+
this.changed = true;
|
173
|
+
this.has_focus = true;
|
174
|
+
|
175
|
+
if(this.observer) clearTimeout(this.observer);
|
176
|
+
this.observer =
|
177
|
+
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
|
178
|
+
},
|
179
|
+
|
180
|
+
onHover: function(event) {
|
181
|
+
var element = Event.findElement(event, 'LI');
|
182
|
+
if(this.index != element.autocompleteIndex)
|
183
|
+
{
|
184
|
+
this.index = element.autocompleteIndex;
|
185
|
+
this.render();
|
186
|
+
}
|
187
|
+
Event.stop(event);
|
188
|
+
},
|
189
|
+
|
190
|
+
onClick: function(event) {
|
191
|
+
var element = Event.findElement(event, 'LI');
|
192
|
+
this.index = element.autocompleteIndex;
|
193
|
+
this.select_entry();
|
194
|
+
Event.stop(event);
|
195
|
+
},
|
196
|
+
|
197
|
+
onBlur: function(event) {
|
198
|
+
// needed to make click events working
|
199
|
+
setTimeout(this.hide.bind(this), 250);
|
200
|
+
this.has_focus = false;
|
201
|
+
this.active = false;
|
202
|
+
},
|
203
|
+
|
204
|
+
render: function() {
|
205
|
+
if(this.entry_count > 0) {
|
206
|
+
for (var i = 0; i < this.entry_count; i++)
|
207
|
+
this.index==i ?
|
208
|
+
Element.addClassName(this.get_entry(i),"selected") :
|
209
|
+
Element.removeClassName(this.get_entry(i),"selected");
|
210
|
+
|
211
|
+
if(this.has_focus) {
|
212
|
+
if(this.get_current_entry().scrollIntoView)
|
213
|
+
this.get_current_entry().scrollIntoView(false);
|
214
|
+
|
215
|
+
this.show();
|
216
|
+
this.active = true;
|
217
|
+
}
|
218
|
+
} else this.hide();
|
219
|
+
},
|
220
|
+
|
221
|
+
mark_previous: function() {
|
222
|
+
if(this.index > 0) this.index--
|
223
|
+
else this.index = this.entry_count-1;
|
224
|
+
},
|
225
|
+
|
226
|
+
mark_next: function() {
|
227
|
+
if(this.index < this.entry_count-1) this.index++
|
228
|
+
else this.index = 0;
|
229
|
+
},
|
230
|
+
|
231
|
+
get_entry: function(index) {
|
232
|
+
return this.update.firstChild.childNodes[index];
|
233
|
+
},
|
234
|
+
|
235
|
+
get_current_entry: function() {
|
236
|
+
return this.get_entry(this.index);
|
237
|
+
},
|
238
|
+
|
239
|
+
select_entry: function() {
|
240
|
+
this.active = false;
|
241
|
+
value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML();
|
242
|
+
this.updateElement(value);
|
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 || {});
|
445
|
+
}
|
446
|
+
});
|