Introduction
Introduction Statistics Contact Development Disclaimer Help
warvox - VoIP based wardialing tool, forked from rapid7/warvox.
Log
Files
Refs
README
---
commit acb500fd3b153e34fce50504796aab6458f717d8
parent 7ab6be136efbdbc5e12e4e7b44269b575d55878b
Author: HD Moore <[email protected]>
Date: Wed, 3 Nov 2010 08:28:02 +0000
Diffstat:
M Makefile | 12 +++++++++++-
M bin/verify_install.rb | 19 +++++++++++++++++++
M bin/warvox.rb | 30 +++++++++++++++---------------
M docs/BUGS | 6 ++++--
M docs/ChangeLog | 8 ++++++--
M docs/LICENSE | 9 +++------
M docs/README | 6 +++++-
M etc/sigs/01.default.rb | 10 +++++-----
M etc/sigs/99.default.rb | 8 ++++----
M lib/warvox.rb | 2 +-
M lib/warvox/jobs.rb | 5 +++++
M lib/warvox/jobs/analysis.rb | 8 ++++++--
M lib/warvox/phone.rb | 6 +++++-
M src/ruby-kissfft/main.c | 17 +++++++----------
A web/Gemfile | 30 ++++++++++++++++++++++++++++++
A web/Gemfile.lock | 74 +++++++++++++++++++++++++++++…
A web/README | 256 +++++++++++++++++++++++++++++…
A web/Rakefile | 7 +++++++
A web/app/controllers/analyze_contro… | 148 +++++++++++++++++++++++++++…
A web/app/controllers/application_co… | 54 +++++++++++++++++++++++++++…
A web/app/controllers/dial_jobs_cont… | 123 +++++++++++++++++++++++++++…
A web/app/controllers/dial_results_c… | 204 +++++++++++++++++++++++++++…
A web/app/controllers/home_controlle… | 16 ++++++++++++++++
A web/app/controllers/providers_cont… | 91 +++++++++++++++++++++++++++…
A web/app/helpers/analyze_helper.rb | 2 ++
A web/app/helpers/application_helper… | 70 +++++++++++++++++++++++++++…
A web/app/helpers/dial_jobs_helper.rb | 2 ++
A web/app/helpers/dial_results_helpe… | 2 ++
A web/app/helpers/home_helper.rb | 2 ++
A web/app/helpers/providers_helper.rb | 2 ++
A web/app/models/dial_job.rb | 30 ++++++++++++++++++++++++++++++
A web/app/models/dial_result.rb | 4 ++++
A web/app/models/provider.rb | 7 +++++++
A web/app/views/analyze/index.html.e… | 38 +++++++++++++++++++++++++++…
A web/app/views/analyze/show.html.erb | 9 +++++++++
A web/app/views/analyze/view.html.erb | 62 +++++++++++++++++++++++++++++…
A web/app/views/dial_jobs/edit.html.… | 24 ++++++++++++++++++++++++
A web/app/views/dial_jobs/index.html… | 107 +++++++++++++++++++++++++++…
A web/app/views/dial_jobs/new.html.e… | 35 +++++++++++++++++++++++++++…
A web/app/views/dial_jobs/run.html.e… | 5 +++++
A web/app/views/dial_jobs/show.html.… | 39 +++++++++++++++++++++++++++…
A web/app/views/dial_results/analyze… | 25 +++++++++++++++++++++++++
A web/app/views/dial_results/edit.ht… | 48 +++++++++++++++++++++++++++…
A web/app/views/dial_results/index.h… | 41 +++++++++++++++++++++++++++…
A web/app/views/dial_results/new.htm… | 43 +++++++++++++++++++++++++++…
A web/app/views/dial_results/show.ht… | 48 +++++++++++++++++++++++++++…
A web/app/views/dial_results/view.ht… | 44 +++++++++++++++++++++++++++…
A web/app/views/home/about.html.erb | 142 +++++++++++++++++++++++++++++…
A web/app/views/home/index.html.erb | 45 +++++++++++++++++++++++++++++…
A web/app/views/layouts/application.… | 0
A web/app/views/layouts/warvox.html.… | 37 +++++++++++++++++++++++++++…
A web/app/views/providers/edit.html.… | 38 +++++++++++++++++++++++++++…
A web/app/views/providers/index.html… | 72 +++++++++++++++++++++++++++…
A web/app/views/providers/new.html.e… | 34 +++++++++++++++++++++++++++…
A web/app/views/providers/show.html.… | 38 +++++++++++++++++++++++++++…
A web/app/views/shared/_footer.html.… | 5 +++++
A web/app/views/shared/_header.html.… | 17 +++++++++++++++++
A web/config.ru | 4 ++++
A web/config/application.rb | 49 +++++++++++++++++++++++++++++…
A web/config/boot.rb | 13 +++++++++++++
A web/config/database.yml | 22 ++++++++++++++++++++++
A web/config/environment.rb | 5 +++++
A web/config/environments/developmen… | 26 ++++++++++++++++++++++++++
A web/config/environments/production… | 49 +++++++++++++++++++++++++++…
A web/config/environments/test.rb | 35 +++++++++++++++++++++++++++++…
A web/config/initializers/backtrace_… | 7 +++++++
A web/config/initializers/inflection… | 10 ++++++++++
A web/config/initializers/mime_types… | 5 +++++
A web/config/initializers/secret_tok… | 7 +++++++
A web/config/initializers/session_st… | 8 ++++++++
A web/config/initializers/warvox.rb | 0
A web/config/locales/en.yml | 5 +++++
A web/config/routes.rb | 23 +++++++++++++++++++++++
A web/db/migrate/20090228195925_crea… | 18 ++++++++++++++++++
A web/db/migrate/20090228200035_crea… | 20 ++++++++++++++++++++
A web/db/migrate/20090228200141_crea… | 21 +++++++++++++++++++++
A web/db/migrate/20090301084459_add_… | 9 +++++++++
A web/db/migrate/20090303204859_add_… | 9 +++++++++
A web/db/migrate/20090303204917_add_… | 9 +++++++++
A web/db/migrate/20090303225838_add_… | 9 +++++++++
A web/db/migrate/20090304013815_add_… | 9 +++++++++
A web/db/migrate/20090304013839_add_… | 9 +++++++++
A web/db/migrate/20090304013909_add_… | 9 +++++++++
A web/db/migrate/20090304014018_add_… | 9 +++++++++
A web/db/migrate/20090304014033_add_… | 9 +++++++++
A web/db/migrate/20090522202032_add_… | 9 +++++++++
A web/db/migrate/20090526031826_add_… | 11 +++++++++++
A web/db/schema.rb | 65 +++++++++++++++++++++++++++++…
A web/db/seeds.rb | 7 +++++++
A web/public/404.html | 26 ++++++++++++++++++++++++++
A web/public/422.html | 26 ++++++++++++++++++++++++++
A web/public/500.html | 26 ++++++++++++++++++++++++++
A web/public/FusionCharts/FCF_Area2D… | 0
A web/public/FusionCharts/FCF_Bar2D.… | 0
A web/public/FusionCharts/FCF_Candle… | 0
A web/public/FusionCharts/FCF_Column… | 0
A web/public/FusionCharts/FCF_Column… | 0
A web/public/FusionCharts/FCF_Doughn… | 0
A web/public/FusionCharts/FCF_Funnel… | 0
A web/public/FusionCharts/FCF_Gantt.… | 0
A web/public/FusionCharts/FCF_Line.s… | 0
A web/public/FusionCharts/FCF_MSArea… | 0
A web/public/FusionCharts/FCF_MSBar2… | 0
A web/public/FusionCharts/FCF_MSColu… | 0
A web/public/FusionCharts/FCF_MSColu… | 0
A web/public/FusionCharts/FCF_MSColu… | 0
A web/public/FusionCharts/FCF_MSColu… | 0
A web/public/FusionCharts/FCF_MSLine… | 0
A web/public/FusionCharts/FCF_Pie2D.… | 0
A web/public/FusionCharts/FCF_Pie3D.… | 0
A web/public/FusionCharts/FCF_Stacke… | 0
A web/public/FusionCharts/FCF_Stacke… | 0
A web/public/FusionCharts/FCF_Stacke… | 0
A web/public/FusionCharts/FCF_Stacke… | 0
A web/public/favicon.ico | 0
A web/public/images/balloon.png | 0
A web/public/images/bluefade.jpg | 0
A web/public/images/close.gif | 0
A web/public/images/left-round.png | 0
A web/public/images/loading.gif | 0
A web/public/images/logo.png | 0
A web/public/images/logo_raw.xcf | 0
A web/public/images/musicplayer.swf | 0
A web/public/images/rails.png | 0
A web/public/images/right-round.png | 0
A web/public/images/round_bot.png | 0
A web/public/images/round_top.png | 0
A web/public/javascripts/FusionChart… | 362 +++++++++++++++++++++++++++…
A web/public/javascripts/application… | 2 ++
A web/public/javascripts/controls.js | 966 +++++++++++++++++++++++++++++…
A web/public/javascripts/custom.js | 0
A web/public/javascripts/dragdrop.js | 975 +++++++++++++++++++++++++++++…
A web/public/javascripts/effects.js | 1124 +++++++++++++++++++++++++++++…
A web/public/javascripts/lightbox.js | 426 +++++++++++++++++++++++++++++…
A web/public/javascripts/prototype.js | 6001 +++++++++++++++++++++++++++++…
A web/public/javascripts/rails.js | 175 +++++++++++++++++++++++++++++…
A web/public/robots.txt | 5 +++++
A web/public/stylesheets/global.css | 556 ++++++++++++++++++++++++++++++
A web/public/stylesheets/ie7.css | 3 +++
A web/public/stylesheets/lightbox.css | 27 +++++++++++++++++++++++++++
A web/public/stylesheets/overlay.png | 0
A web/public/stylesheets/scaffold.css | 54 +++++++++++++++++++++++++++++…
A web/script/rails | 6 ++++++
A web/test/fixtures/dial_jobs.yml | 11 +++++++++++
A web/test/fixtures/dial_results.yml | 11 +++++++++++
A web/test/fixtures/providers.yml | 11 +++++++++++
A web/test/functional/analyze_contro… | 8 ++++++++
A web/test/functional/dial_jobs_cont… | 8 ++++++++
A web/test/functional/dial_results_c… | 8 ++++++++
A web/test/functional/home_controlle… | 8 ++++++++
A web/test/functional/providers_cont… | 8 ++++++++
A web/test/performance/browsing_test… | 9 +++++++++
A web/test/test_helper.rb | 13 +++++++++++++
A web/test/unit/dial_job_test.rb | 8 ++++++++
A web/test/unit/dial_result_test.rb | 8 ++++++++
A web/test/unit/helpers/analyze_help… | 4 ++++
A web/test/unit/helpers/dial_jobs_he… | 4 ++++
A web/test/unit/helpers/dial_results… | 4 ++++
A web/test/unit/helpers/home_helper_… | 4 ++++
A web/test/unit/helpers/providers_he… | 4 ++++
A web/test/unit/provider_test.rb | 8 ++++++++
A web/vendor/plugins/dynamic_form/MI… | 20 ++++++++++++++++++++
A web/vendor/plugins/dynamic_form/RE… | 13 +++++++++++++
A web/vendor/plugins/dynamic_form/Ra… | 10 ++++++++++
A web/vendor/plugins/dynamic_form/dy… | 12 ++++++++++++
A web/vendor/plugins/dynamic_form/in… | 1 +
A web/vendor/plugins/dynamic_form/li… | 300 +++++++++++++++++++++++++++…
A web/vendor/plugins/dynamic_form/li… | 8 ++++++++
A web/vendor/plugins/dynamic_form/li… | 5 +++++
A web/vendor/plugins/dynamic_form/te… | 43 +++++++++++++++++++++++++++…
A web/vendor/plugins/dynamic_form/te… | 371 +++++++++++++++++++++++++++…
A web/vendor/plugins/dynamic_form/te… | 9 +++++++++
A web/vendor/plugins/ezgraphix/Fusio… | 49 +++++++++++++++++++++++++++…
A web/vendor/plugins/ezgraphix/READM… | 157 +++++++++++++++++++++++++++…
A web/vendor/plugins/ezgraphix/init.… | 3 +++
A web/vendor/plugins/ezgraphix/lib/e… | 199 +++++++++++++++++++++++++++…
A web/vendor/plugins/ezgraphix/lib/e… | 107 +++++++++++++++++++++++++++…
A web/vendor/plugins/ezgraphix/lib/t… | 20 ++++++++++++++++++++
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 0
A web/vendor/plugins/ezgraphix/publi… | 362 +++++++++++++++++++++++++++…
A web/vendor/plugins/ezgraphix/spec/… | 85 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/C… | 139 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/L… | 18 ++++++++++++++++++
A web/vendor/plugins/will_paginate/R… | 107 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/R… | 53 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/e… | 0
A web/vendor/plugins/will_paginate/e… | 69 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/e… | 92 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/e… | 91 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/e… | 91 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/i… | 1 +
A web/vendor/plugins/will_paginate/l… | 90 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/l… | 16 ++++++++++++++++
A web/vendor/plugins/will_paginate/l… | 144 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/l… | 43 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/l… | 264 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/l… | 170 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/l… | 37 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/l… | 9 +++++++++
A web/vendor/plugins/will_paginate/l… | 410 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/t… | 21 +++++++++++++++++++++
A web/vendor/plugins/will_paginate/t… | 143 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/t… | 8 ++++++++
A web/vendor/plugins/will_paginate/t… | 22 ++++++++++++++++++++++
A web/vendor/plugins/will_paginate/t… | 496 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/t… | 3 +++
A web/vendor/plugins/will_paginate/t… | 14 ++++++++++++++
A web/vendor/plugins/will_paginate/t… | 14 ++++++++++++++
A web/vendor/plugins/will_paginate/t… | 17 +++++++++++++++++
A web/vendor/plugins/will_paginate/t… | 6 ++++++
A web/vendor/plugins/will_paginate/t… | 29 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/t… | 7 +++++++
A web/vendor/plugins/will_paginate/t… | 38 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/t… | 12 ++++++++++++
A web/vendor/plugins/will_paginate/t… | 30 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/t… | 2 ++
A web/vendor/plugins/will_paginate/t… | 35 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/t… | 37 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/t… | 43 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/t… | 76 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/t… | 11 +++++++++++
A web/vendor/plugins/will_paginate/t… | 179 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/t… | 59 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/t… | 373 +++++++++++++++++++++++++++…
A web/vendor/plugins/will_paginate/w… | 22 ++++++++++++++++++++++
246 files changed, 18930 insertions(+), 50 deletions(-)
---
diff --git a/Makefile b/Makefile
@@ -3,7 +3,7 @@ all: test
test: install
bin/verify_install.rb
-install: iaxrecord dtmf2num ruby-kissfft db
+install: bundler iaxrecord dtmf2num ruby-kissfft db
cp -a src/iaxrecord/iaxrecord bin/
cp -a src/dtmf2num/dtmf2num bin/
@@ -29,6 +29,16 @@ db_null:
web/db/production.sqlite3: ruby-kissfft-install
(cd web; RAILS_ENV=production rake db:migrate )
+bundler:
+ @echo "Checking for RubyGems and the Bundler gem..."
+ @ruby -rrubygems -e 'require "bundler"; puts "OK"'
+
+ @echo "Validating that 'bundle' is in the path..."
+ which bundle
+
+ @echo "Installing missing gems as needed.."
+ (cd web; bundle install)
+
clean:
( cd src/ruby-kissfft/; ruby extconf.rb )
make -C src/ruby-kissfft/ clean
diff --git a/bin/verify_install.rb b/bin/verify_install.rb
@@ -22,6 +22,25 @@ puts("* …
puts("**********************************************************************")
puts(" ")
+
+begin
+ require 'rubygems'
+ puts "[*] RubyGems have been installed"
+rescue ::LoadError
+ puts "[*] ERROR: The RubyGems package has not been installed:"
+ puts " $ sudo apt-get install rubygems"
+ exit
+end
+
+begin
+ require 'bundler'
+ puts "[*] The Bundler gem has been installed"
+rescue ::LoadError
+ puts "[*] ERROR: The Bundler gem has not been installed:"
+ puts " $ sudo gem install bundler"
+ exit
+end
+
begin
require 'kissfft'
puts "[*] The KissFFT module appears to be available"
diff --git a/bin/warvox.rb b/bin/warvox.rb
@@ -1,6 +1,9 @@
#!/usr/bin/env ruby
###################
+require 'getoptlong'
+
+
#
# Load the library path
#
@@ -10,10 +13,9 @@ while File.symlink?(base)
end
voxroot = File.join(File.dirname(base), '..', 'web')
-Dir.chdir(voxroot)
-require 'getoptlong'
+voxserv = File.join(File.expand_path(voxroot), 'script', 'rails')
-voxserv = File.join('script', 'server')
+Dir.chdir(voxroot)
def usage
$stderr.puts "#{$0} [--address IP] [--port PORT] --background"
@@ -47,20 +49,15 @@ args.each do |opt,arg|
end
end
-
-# Clear ARGV
-while(ARGV.length > 0)
- ARGV.shift
-end
-
-# Rebuild ARGV
-[
+args = [
+ 'server',
'-p', opts['ServerPort'].to_s,
'-b', opts['ServerHost'],
- '-e', 'production',
- (opts['Background'] ? '-d' : '')
-].each do |arg|
- ARGV.push arg
+ '-e', 'development',
+]
+
+if opts['Background']
+ args.push("-d")
end
$browser_url = "http://#{opts['ServerHost']}:#{opts['ServerPort']}/"
@@ -69,4 +66,7 @@ $stderr.puts ""
$stderr.puts "[*] Starting WarVOX on #{$browser_url}"
$stderr.puts ""
+while(ARGV.length > 0); ARGV.shift; end
+args.each {|arg| ARGV.push(arg) }
+
load(voxserv)
diff --git a/docs/BUGS b/docs/BUGS
@@ -4,5 +4,7 @@ KNOWN BUGS
need to be removed and restarted.
* Using the sound card (playing music, videos, etc) while warvox is running
- can cause the dialing process to handle. Systems without sound cards are
- not affected.
+ can cause the dialing process to hang.
+
+ * Systems without sound cards may not be able to record any audio. Reports
+ have varied depending on the Linux distribution and version.
diff --git a/docs/ChangeLog b/docs/ChangeLog
@@ -1,3 +1,9 @@
+2010-11-03 H D Moore <hdm[at]metasploit.com>
+ * bumped the version number to 1.1.0
+ * upgraded the web interface to Rails 3
+ * added support for Ruby 1.9.1
+ * updated README and LICENSE
+
2009-05-25 H D Moore <hdm[at]metasploit.com>
* switched MP3 quality to 32kbps from 8kpbs for better listening
* added bin/warvox.agi as an asterisk plugin to allow job re-dialing
@@ -7,7 +13,6 @@
* added dtmf2num support to decode dtmf tones
2009-05-10 H D Moore <hdm[at]metasploit.com>
-
* release of version 1.0.1
* switched to the BSD license for WarVOX
* swapped the sox -w flag to -2 (deprecated)
@@ -18,5 +23,4 @@
* added getopt parser to iaxrecord.c
2009-03-05 H D Moore <hdm[at]metasploit.com>
-
* initial public release of version 1.0.0
diff --git a/docs/LICENSE b/docs/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2009, Metasploit LLC
+Copyright (c) 2009-2010, Rapid7 LLC
All rights reserved.
Redistribution and use in source and binary forms, with or without modificatio…
@@ -11,7 +11,7 @@ are permitted provided that the following conditions are met:
this list of conditions and the following disclaimer in the document…
and/or other materials provided with the distribution.
- * Neither the name of Metasploit LLC nor the names of its contributors
+ * Neither the name of Rapid7 LLC nor the names of its contributors
may be used to endorse or promote products derived from this softwar…
without specific prior written permission.
@@ -30,7 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
WarVOX is provided under the BSD license above.
-The copyright on this package is held by Metasploit LLC.
+The copyright on this package is held by Rapid7 LLC.
This copyright and license does not apply to the following components:
- The ruby on rails vendor libraries under web/vendor/ (Ruby license)
@@ -39,9 +39,6 @@ This copyright and license does not apply to the following co…
The latest version of this software is available from http://warvox.org/
-Bug tracking and development information can be found at:
- http://trac.metasploit.com/ (component: warvox)
-
Questions and suggestions can be sent to:
warvox[at]metasploit.com
diff --git a/docs/README b/docs/README
@@ -5,8 +5,12 @@ WarVOX Quick Start Guide
Ubuntu 8.10 or BackTrack 4 (beta+)
$ sudo apt-get install libiaxclient-dev sox lame ruby rake \
rubygems libsqlite3-ruby libopenssl-ruby gnuplot ruby-dev
+
+ $ sudo apt-get install rubygems
+
+ $ sudo gem install bundler
- For better performance, install the 'mongrel' gem
+ For better performance, install the 'mongrel' gem (on ruby 1.8.7)
$ gem install mongrel
2. Build the WarVOX tools and modules
diff --git a/etc/sigs/01.default.rb b/etc/sigs/01.default.rb
@@ -44,7 +44,7 @@ data[:ecnt] = ecnt
#
if( (fcnt[2100] > 1.0 or fcnt[2230] > 1.0) and fcnt[2250] > 0.5)
@line_type = 'modem'
- break
+ raise Completed
end
#
@@ -52,7 +52,7 @@ end
#
if(fcnt[2100] > 1.0 and (maxf > 2245.0 and maxf < 2255.0))
@line_type = 'modem'
- break
+ raise Completed
end
#
@@ -60,7 +60,7 @@ end
#
if(fcnt[2100] > 1.0 and (maxf > 2995.0 and maxf < 3005.0))
@line_type = 'modem'
- break
+ raise Completed
end
#
@@ -75,7 +75,7 @@ fax_sum = 0
].map{|x| fax_sum += [x,1.0].min }
if(fax_sum >= 2.0)
@line_type = 'fax'
- break
+ raise Completed
end
#
@@ -83,7 +83,7 @@ end
#
if(fcnt[440] > 1.0 and fcnt[350] > 1.0)
@line_type = 'dialtone'
- break
+ raise Completed
end
#
diff --git a/etc/sigs/99.default.rb b/etc/sigs/99.default.rb
@@ -19,7 +19,7 @@ scnt = data[:scnt]
# is often a different frequency entirely.
if(fcnt[1000] >= 1.0)
@line_type = 'voicemail'
- break
+ raise Completed
end
# Look for voicemail by detecting a peak frequency of
@@ -27,7 +27,7 @@ end
# the fallback script.
if(maxf > 995 and maxf < 1005)
@line_type = 'voicemail'
- break
+ raise Completed
end
#
@@ -35,12 +35,12 @@ end
#
if(freq.map{|f| f.length}.inject(:+) == 0)
@line_type = 'silence'
- break
+ raise Completed
end
if(ecnt == scnt)
@line_type = 'silence'
- break
+ raise Completed
end
#
diff --git a/lib/warvox.rb b/lib/warvox.rb
@@ -11,7 +11,7 @@ require 'warvox/db'
# Global configuration
module WarVOX
- VERSION = '1.0.2'
+ VERSION = '1.1.0'
Base = File.expand_path(File.join(File.dirname(__FILE__), '..'))
Conf = File.expand_path(File.join(Base, 'etc', 'warvox.conf'))
JobManager = WarVOX::JobQueue.new
diff --git a/lib/warvox/jobs.rb b/lib/warvox/jobs.rb
@@ -23,8 +23,13 @@ class JobQueue
end
def schedule(klass, job_id)
+ begin
return false if scheduled?(klass, job_id)
@queue.push(klass.new(job_id))
+ rescue ::Exception => e
+ $stderr.puts "ERROR!!!!!: #{e} #{e.backtrace}"
+ false
+ end
end
def manage_queue
diff --git a/lib/warvox/jobs/analysis.rb b/lib/warvox/jobs/analysis.rb
@@ -15,6 +15,10 @@ class Analysis < Base
end
class SignalProcessor
+
+ class Completed < RuntimeError
+ end
+
attr_accessor :line_type
attr_accessor :signatures
attr_accessor :data
@@ -25,9 +29,9 @@ class Analysis < Base
end
def proc(str)
- while(true);
+ begin
eval(str)
- break
+ rescue Completed
end
end
end
diff --git a/lib/warvox/phone.rb b/lib/warvox/phone.rb
@@ -2,7 +2,11 @@ module WarVOX
class Phone
# Convert 123456XXXX to an array of expanded numbers
- def self.crack_mask(masks)
+ def self.crack_mask(mask)
+ self.crack_masks([mask])
+ end
+
+ def self.crack_masks(masks)
res = {}
masks.each do |mask|
mask = mask.strip
diff --git a/src/ruby-kissfft/main.c b/src/ruby-kissfft/main.c
@@ -1,13 +1,13 @@
/*
ruby-kissfft: a simple ruby module embedding the Kiss FFT library
- Copyright (C) 2009 H D Moore <hdm[at]metasploit.com>
+ Copyright (C) 2009-2010 Rapid7 LLC - H D Moore <hdm[at]metasploit.com>
Derived from "psdpng.c" from the KissFFT tools directory
Copyright (C) 2003-2006 Mark Borgerding
*/
#include "ruby.h"
-#include "rubysig.h"
+
#include <stdlib.h>
#include <math.h>
@@ -39,11 +39,8 @@ rbkiss_s_fftr(VALUE class, VALUE r_nfft, VALUE r_rate, VALUE…
kiss_fft_cpx *fbuf;
float *mag2buf;
int i;
- int n;
- int sidx;
int avgctr=0;
int nrows=0;
- int stereo=0;
int nfft;
int rate;
@@ -78,11 +75,11 @@ rbkiss_s_fftr(VALUE class, VALUE r_nfft, VALUE r_rate, VALU…
return Qnil;
}
- if(RARRAY(r_data)->len == 0) {
+ if(RARRAY_LEN(r_data) == 0) {
return Qnil;
}
- if(TYPE(RARRAY(r_data)->ptr[0]) != T_FIXNUM ) {
+ if(TYPE(RARRAY_PTR(r_data)[0]) != T_FIXNUM ) {
return Qnil;
}
@@ -95,7 +92,7 @@ rbkiss_s_fftr(VALUE class, VALUE r_nfft, VALUE r_rate, VALUE …
memset(mag2buf,0,sizeof(mag2buf)*nfreqs);
- inp_len = RARRAY(r_data)->len;
+ inp_len = RARRAY_LEN(r_data);
inp_idx = 0;
while(inp_idx < inp_len) {
@@ -105,10 +102,10 @@ rbkiss_s_fftr(VALUE class, VALUE r_nfft, VALUE r_rate, VA…
if(inp_idx + i >= inp_len) {
tbuf[i] = 0;
} else {
- if(TYPE(RARRAY(r_data)->ptr[ inp_idx + i ]) !=…
+ if(TYPE(RARRAY_PTR(r_data)[ inp_idx + i ]) != …
tbuf[i] = 0;
} else {
- tbuf[i] = NUM2INT( RARRAY(r_data)->ptr…
+ tbuf[i] = NUM2INT( RARRAY_PTR(r_data)[…
}
}
}
diff --git a/web/Gemfile b/web/Gemfile
@@ -0,0 +1,30 @@
+source 'http://rubygems.org'
+
+gem 'rails', '3.0.1'
+
+# Bundle edge Rails instead:
+# gem 'rails', :git => 'git://github.com/rails/rails.git'
+
+gem 'sqlite3-ruby', :require => 'sqlite3'
+
+# Use unicorn as the web server
+# gem 'unicorn'
+
+# Deploy with Capistrano
+# gem 'capistrano'
+
+# To use debugger
+# gem 'ruby-debug'
+
+# Bundle the extra gems:
+# gem 'bj'
+# gem 'nokogiri'
+# gem 'sqlite3-ruby', :require => 'sqlite3'
+# gem 'aws-s3', :require => 'aws/s3'
+
+# Bundle gems for the local environment. Make sure to
+# put test-only gems in this group so their generators
+# and rake tasks are available in development mode:
+# group :development, :test do
+# gem 'webrat'
+# end
diff --git a/web/Gemfile.lock b/web/Gemfile.lock
@@ -0,0 +1,74 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ abstract (1.0.0)
+ actionmailer (3.0.1)
+ actionpack (= 3.0.1)
+ mail (~> 2.2.5)
+ actionpack (3.0.1)
+ activemodel (= 3.0.1)
+ activesupport (= 3.0.1)
+ builder (~> 2.1.2)
+ erubis (~> 2.6.6)
+ i18n (~> 0.4.1)
+ rack (~> 1.2.1)
+ rack-mount (~> 0.6.12)
+ rack-test (~> 0.5.4)
+ tzinfo (~> 0.3.23)
+ activemodel (3.0.1)
+ activesupport (= 3.0.1)
+ builder (~> 2.1.2)
+ i18n (~> 0.4.1)
+ activerecord (3.0.1)
+ activemodel (= 3.0.1)
+ activesupport (= 3.0.1)
+ arel (~> 1.0.0)
+ tzinfo (~> 0.3.23)
+ activeresource (3.0.1)
+ activemodel (= 3.0.1)
+ activesupport (= 3.0.1)
+ activesupport (3.0.1)
+ arel (1.0.1)
+ activesupport (~> 3.0.0)
+ builder (2.1.2)
+ erubis (2.6.6)
+ abstract (>= 1.0.0)
+ i18n (0.4.2)
+ mail (2.2.9)
+ activesupport (>= 2.3.6)
+ i18n (~> 0.4.1)
+ mime-types (~> 1.16)
+ treetop (~> 1.4.8)
+ mime-types (1.16)
+ polyglot (0.3.1)
+ rack (1.2.1)
+ rack-mount (0.6.13)
+ rack (>= 1.0.0)
+ rack-test (0.5.6)
+ rack (>= 1.0)
+ rails (3.0.1)
+ actionmailer (= 3.0.1)
+ actionpack (= 3.0.1)
+ activerecord (= 3.0.1)
+ activeresource (= 3.0.1)
+ activesupport (= 3.0.1)
+ bundler (~> 1.0.0)
+ railties (= 3.0.1)
+ railties (3.0.1)
+ actionpack (= 3.0.1)
+ activesupport (= 3.0.1)
+ rake (>= 0.8.4)
+ thor (~> 0.14.0)
+ rake (0.8.7)
+ sqlite3-ruby (1.3.2)
+ thor (0.14.3)
+ treetop (1.4.8)
+ polyglot (>= 0.3.1)
+ tzinfo (0.3.23)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ rails (= 3.0.1)
+ sqlite3-ruby
diff --git a/web/README b/web/README
@@ -0,0 +1,256 @@
+== Welcome to Rails
+
+Rails is a web-application framework that includes everything needed to create
+database-backed web applications according to the Model-View-Control pattern.
+
+This pattern splits the view (also called the presentation) into "dumb"
+templates that are primarily responsible for inserting pre-built data in betwe…
+HTML tags. The model contains the "smart" domain objects (such as Account,
+Product, Person, Post) that holds all the business logic and knows how to
+persist themselves to a database. The controller handles the incoming requests
+(such as Save New Account, Update Product, Show Post) by manipulating the model
+and directing data to the view.
+
+In Rails, the model is handled by what's called an object-relational mapping
+layer entitled Active Record. This layer allows you to present the data from
+database rows as objects and embellish these data objects with business logic
+methods. You can read more about Active Record in
+link:files/vendor/rails/activerecord/README.html.
+
+The controller and view are handled by the Action Pack, which handles both
+layers by its two parts: Action View and Action Controller. These two layers
+are bundled in a single package due to their heavy interdependence. This is
+unlike the relationship between the Active Record and Action Pack that is much
+more separate. Each of these packages can be used independently outside of
+Rails. You can read more about Action Pack in
+link:files/vendor/rails/actionpack/README.html.
+
+
+== Getting Started
+
+1. At the command prompt, create a new Rails application:
+ <tt>rails new myapp</tt> (where <tt>myapp</tt> is the application name)
+
+2. Change directory to <tt>myapp</tt> and start the web server:
+ <tt>cd myapp; rails server</tt> (run with --help for options)
+
+3. Go to http://localhost:3000/ and you'll see:
+ "Welcome aboard: You're riding Ruby on Rails!"
+
+4. Follow the guidelines to start developing your application. You can find
+the following resources handy:
+
+* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
+* Ruby on Rails Tutorial Book: http://www.railstutorial.org/
+
+
+== Debugging Rails
+
+Sometimes your application goes wrong. Fortunately there are a lot of tools th…
+will help you debug it and get it back on the rails.
+
+First area to check is the application log files. Have "tail -f" commands
+running on the server.log and development.log. Rails will automatically display
+debugging and runtime information to these files. Debugging info will also be
+shown in the browser on requests from 127.0.0.1.
+
+You can also log your own messages directly into the log file from your code
+using the Ruby logger class from inside your controllers. Example:
+
+ class WeblogController < ActionController::Base
+ def destroy
+ @weblog = Weblog.find(params[:id])
+ @weblog.destroy
+ logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
+ end
+ end
+
+The result will be a message in your log file along the lines of:
+
+ Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1!
+
+More information on how to use the logger is at http://www.ruby-doc.org/core/
+
+Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
+several books available online as well:
+
+* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
+* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
+
+These two books will bring you up to speed on the Ruby language and also on
+programming in general.
+
+
+== Debugger
+
+Debugger support is available through the debugger command when you start your
+Mongrel or WEBrick server with --debugger. This means that you can break out of
+execution at any point in the code, investigate and change the model, and then,
+resume execution! You need to install ruby-debug to run the server in debugging
+mode. With gems, use <tt>sudo gem install ruby-debug</tt>. Example:
+
+ class WeblogController < ActionController::Base
+ def index
+ @posts = Post.find(:all)
+ debugger
+ end
+ end
+
+So the controller will accept the action, run the first line, then present you
+with a IRB prompt in the server window. Here you can do things like:
+
+ >> @posts.inspect
+ => "[#<Post:0x14a6be8
+ @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>,
+ #<Post:0x14a6620
+ @attributes={"title"=>"Rails", "body"=>"Only ten..", "id"=>"2"}>]"
+ >> @posts.first.title = "hello from a debugger"
+ => "hello from a debugger"
+
+...and even better, you can examine how your runtime objects actually work:
+
+ >> f = @posts.first
+ => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
+ >> f.
+ Display all 152 possibilities? (y or n)
+
+Finally, when you're ready to resume execution, you can enter "cont".
+
+
+== Console
+
+The console is a Ruby shell, which allows you to interact with your
+application's domain model. Here you'll have all parts of the application
+configured, just like it is when the application is running. You can inspect
+domain models, change values, and save to the database. Starting the script
+without arguments will launch it in the development environment.
+
+To start the console, run <tt>rails console</tt> from the application
+directory.
+
+Options:
+
+* Passing the <tt>-s, --sandbox</tt> argument will rollback any modifications
+ made to the database.
+* Passing an environment name as an argument will load the corresponding
+ environment. Example: <tt>rails console production</tt>.
+
+To reload your controllers and models after launching the console run
+<tt>reload!</tt>
+
+More information about irb can be found at:
+link:http://www.rubycentral.com/pickaxe/irb.html
+
+
+== dbconsole
+
+You can go to the command line of your database directly through <tt>rails
+dbconsole</tt>. You would be connected to the database with the credentials
+defined in database.yml. Starting the script without arguments will connect you
+to the development database. Passing an argument will connect you to a differe…
+database, like <tt>rails dbconsole production</tt>. Currently works for MySQL,
+PostgreSQL and SQLite 3.
+
+== Description of Contents
+
+The default directory structure of a generated Ruby on Rails application:
+
+ |-- app
+ | |-- controllers
+ | |-- helpers
+ | |-- models
+ | `-- views
+ | `-- layouts
+ |-- config
+ | |-- environments
+ | |-- initializers
+ | `-- locales
+ |-- db
+ |-- doc
+ |-- lib
+ | `-- tasks
+ |-- log
+ |-- public
+ | |-- images
+ | |-- javascripts
+ | `-- stylesheets
+ |-- script
+ | `-- performance
+ |-- test
+ | |-- fixtures
+ | |-- functional
+ | |-- integration
+ | |-- performance
+ | `-- unit
+ |-- tmp
+ | |-- cache
+ | |-- pids
+ | |-- sessions
+ | `-- sockets
+ `-- vendor
+ `-- plugins
+
+app
+ Holds all the code that's specific to this particular application.
+
+app/controllers
+ Holds controllers that should be named like weblogs_controller.rb for
+ automated URL mapping. All controllers should descend from
+ ApplicationController which itself descends from ActionController::Base.
+
+app/models
+ Holds models that should be named like post.rb. Models descend from
+ ActiveRecord::Base by default.
+
+app/views
+ Holds the template files for the view that should be named like
+ weblogs/index.html.erb for the WeblogsController#index action. All views use
+ eRuby syntax by default.
+
+app/views/layouts
+ Holds the template files for layouts to be used with views. This models the
+ common header/footer method of wrapping views. In your views, define a layout
+ using the <tt>layout :default</tt> and create a file named default.html.erb.
+ Inside default.html.erb, call <% yield %> to render the view using this
+ layout.
+
+app/helpers
+ Holds view helpers that should be named like weblogs_helper.rb. These are
+ generated for you automatically when using generators for controllers.
+ Helpers can be used to wrap functionality for your views into methods.
+
+config
+ Configuration files for the Rails environment, the routing map, the database,
+ and other dependencies.
+
+db
+ Contains the database schema in schema.rb. db/migrate contains all the
+ sequence of Migrations for your schema.
+
+doc
+ This directory is where your application documentation will be stored when
+ generated using <tt>rake doc:app</tt>
+
+lib
+ Application specific libraries. Basically, any kind of custom code that
+ doesn't belong under controllers, models, or helpers. This directory is in
+ the load path.
+
+public
+ The directory available for the web server. Contains subdirectories for
+ images, stylesheets, and javascripts. Also contains the dispatchers and the
+ default HTML files. This should be set as the DOCUMENT_ROOT of your web
+ server.
+
+script
+ Helper scripts for automation and generation.
+
+test
+ Unit and functional tests along with fixtures. When using the rails generate
+ command, template test files will be generated for you and placed in this
+ directory.
+
+vendor
+ External libraries that the application depends on. Also includes the plugins
+ subdirectory. If the app has frozen rails, those gems also go here, under
+ vendor/rails/. This directory is in the load path.
diff --git a/web/Rakefile b/web/Rakefile
@@ -0,0 +1,7 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be availa…
+
+require File.expand_path('../config/application', __FILE__)
+require 'rake'
+
+Web::Application.load_tasks
diff --git a/web/app/controllers/analyze_controller.rb b/web/app/controllers/an…
@@ -0,0 +1,148 @@
+class AnalyzeController < ApplicationController
+ layout 'warvox'
+
+ def index
+ @jobs = DialJob.paginate_all_by_processed(
+ true,
+ :page => params[:page],
+ :order => 'id DESC',
+ :per_page => 30
+ )
+ end
+
+ def view
+ @job_id = params[:id]
+ @dial_job = DialJob.find(@job_id)
+ @shown = params[:show]
+
+ @g1 = Ezgraphix::Graphic.new(:c_type => 'col3d', :div_name => 'calls_p…
+ @g1.render_options(:caption => 'Detected Lines by Type', :y_name => 'L…
+
+ ltypes = DialResult.find( :all, :select => 'DISTINCT line_type', :cond…
+ res_types = {}
+
+ ltypes.each do |k|
+ next if not k
+ res_types[k.capitalize.to_sym] = DialResult.count(
+ :conditions => ['dial_job_id = ? and line_type = ?', @…
+ )
+ end
+
+ @g1.data = res_types
+
+ if(@shown and @shown != 'all')
+ @results = DialResult.paginate_all_by_dial_job_id(
+ @job_id,
+ :page => params[:page],
+ :order => 'number ASC',
+ :per_page => 10,
+ :conditions => [ 'completed = ? and processed = ? and …
+ )
+ else
+ @results = DialResult.paginate_all_by_dial_job_id(
+ @job_id,
+ :page => params[:page],
+ :order => 'number ASC',
+ :per_page => 10,
+ :conditions => [ 'completed = ? and processed = ? and …
+ )
+ end
+
+ @filters = []
+ @filters << { :scope => "all", :label => "All" }
+ res_types.keys.each do |t|
+ @filters << { :scope => t.to_s.downcase, :label => t.to_s }
+ end
+
+ end
+
+ def show
+ @job_id = params[:id]
+ @dial_job = DialJob.find(@job_id)
+ @shown = params[:show]
+
+ @g1 = Ezgraphix::Graphic.new(:c_type => 'col3d', :div_name => 'calls_p…
+ @g1.render_options(:caption => 'Detected Lines by Type', :y_name => 'L…
+
+ ltypes = DialResult.find( :all, :select => 'DISTINCT line_type', :cond…
+ res_types = {}
+
+ ltypes.each do |k|
+ next if not k
+ res_types[k.capitalize.to_sym] = DialResult.count(
+ :conditions => ['dial_job_id = ? and line_type = ?', @…
+ )
+ end
+
+ @g1.data = res_types
+
+ if(@shown and @shown != 'all')
+ @results = DialResult.paginate_all_by_dial_job_id(
+ @job_id,
+ :page => params[:page],
+ :order => 'number ASC',
+ :per_page => 20,
+ :conditions => [ 'completed = ? and processed = ? and …
+ )
+ else
+ @results = DialResult.paginate_all_by_dial_job_id(
+ @job_id,
+ :page => params[:page],
+ :order => 'number ASC',
+ :per_page => 20,
+ :conditions => [ 'completed = ? and processed = ? and …
+ )
+ end
+
+ @filters = []
+ @filters << { :scope => "all", :label => "All" }
+ res_types.keys.each do |t|
+ @filters << { :scope => t.to_s.downcase, :label => t.to_s }
+ end
+
+ end
+
+
+ # GET /dial_results/1/resource?id=XXX&type=YYY
+ def resource
+ ctype = 'text/html'
+ cpath = nil
+
+ res = DialResult.find(params[:result_id])
+ if(res and res.processed and res.rawfile)
+ case params[:type]
+ when 'big_sig'
+ ctype = 'image/png'
+ cpath = res.rawfile.gsub(/\..*/, '') + '_big.png'
+ when 'big_sig_dots'
+ ctype = 'image/png'
+ cpath = res.rawfile.gsub(/\..*/, '') + '_big_dots.png'…
+ when 'small_sig'
+ ctype = 'image/png'
+ cpath = res.rawfile.gsub(/\..*/, '') + '.png'
+ when 'mp3'
+ ctype = 'audio/mpeg'
+ cpath = res.rawfile.gsub(/\..*/, '') + '.mp3'
+ when 'sig'
+ ctype = 'text/plain'
+ cpath = res.rawfile.gsub(/\..*/, '') + '.sig'
+ when 'raw'
+ ctype = 'octet/binary-stream'
+ cpath = res.rawfile
+ when 'big_freq'
+ ctype = 'image/png'
+ cpath = res.rawfile.gsub(/\..*/, '') + '_freq_big.png'…
+ when 'small_freq'
+ ctype = 'image/png'
+ cpath = res.rawfile.gsub(/\..*/, '') + '_freq.png' …
+ end
+ end
+
+ cdata = "File not found"
+ if(cpath and File.readable?(cpath))
+ cdata = File.read(cpath, File.size(cpath))
+ end
+
+ send_data(cdata, :type => ctype, :disposition => 'inline')
+ end
+end
diff --git a/web/app/controllers/application_controller.rb b/web/app/controller…
@@ -0,0 +1,54 @@
+class ApplicationController < ActionController::Base
+ helper :all
+ protect_from_forgery
+ before_filter :get_auth
+
+private
+
+ def get_creds
+ username = nil
+ password = nil
+
+ headers = %W{X-HTTP_AUTHORIZATION REDIRECT_X_HTTP_AUTHORIZATIO…
+
+ headers.each do |head|
+ blob = request.env[head]
+ next if not blob
+
+ meth,blob = blob.split(/\s+/)
+ next if not blob
+ next if meth.downcase != 'basic'
+
+ username,password = blob.unpack('m*')[0].split(':', 2)
+ break if (username and username.length > 0)
+ end
+
+ [username, password]
+ end
+
+ def check_auth
+ return true if session.data[:user]
+ user,pass = get_creds
+ return false if not (user and pass)
+
+ if(WarVOX::Config.authenticate(user,pass))
+ session.data[:user] = user
+ return true
+ end
+
+ return false
+ end
+
+ def get_auth
+ return true
+
+ if(not check_auth())
+ response.headers["Status"] = "Unauthorized"
+ response.headers["WWW-Authenticate"] = 'Basic realm="W…
+ render :text => "Authentication Failure", :status => 4…
+ return
+ end
+ true
+ end
+
+end
diff --git a/web/app/controllers/dial_jobs_controller.rb b/web/app/controllers/…
@@ -0,0 +1,123 @@
+class DialJobsController < ApplicationController
+ layout 'warvox'
+
+ # GET /dial_jobs
+ # GET /dial_jobs.xml
+ def index
+ @submitted_jobs = DialJob.find_all_by_status('submitted')
+ @active_jobs = DialJob.find_all_by_status('active')
+ @new_job = DialJob.new
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @active_jobs + @submitted_jobs }
+ end
+ end
+
+ # GET /dial_jobs/new
+ # GET /dial_jobs/new.xml
+ def new
+ @dial_job = DialJob.new
+ respond_to do |format|
+ format.html # new.html.erb
+ format.xml { render :xml => @dial_job }
+ end
+ end
+
+=begin
+ # GET /dial_jobs/1/edit
+ def edit
+ @dial_job = DialJob.find(params[:id])
+ end
+=end
+
+
+ # GET /dial_jobs/1/run
+ def run
+ @dial_job = DialJob.find(params[:id])
+
+ if(@dial_job.status != 'submitted')
+ flash[:notice] = 'Job is already running or completed'
+ return
+ end
+
+ WarVOX::JobManager.schedule(::WarVOX::Jobs::Dialer, @dial_job.id)
+ redirect_to :action => 'index'
+ end
+
+ def stop
+ @dial_job = DialJob.find(params[:id])
+
+ if(@dial_job.status != 'submitted')
+ flash[:notice] = 'Job is already running or completed'
+ return
+ end
+ end
+
+
+ # POST /dial_jobs
+ # POST /dial_jobs.xml
+ def create
+
+ @dial_job = DialJob.new(params[:dial_job])
+
+ if(Provider.find_all_by_enabled(true).length == 0)
+ @dial_job.errors.add("No providers have been configured or ena…
+ respond_to do |format|
+ format.html { render :action => "new" }
+ format.xml { render :xml => @dial_job.errors, :status…
+ end
+ return
+ end
+
+ @dial_job.status = 'submitted'
+ @dial_job.progress = 0
+ @dial_job.started_at = nil
+ @dial_job.completed_at = nil
+ @dial_job.range.gsub!(/[^0-9X:,\n]/, '')
+ @dial_job.cid_mask.gsub!(/[^0-9X]/, '') if @dial_job.cid_mask != "SELF"
+
+ if(@dial_job.range_file.to_s != "")
+ @dial_job.range = @dial_job.range_file.read.gsub!(/[^0-9X:,\n]…
+ end
+
+ respond_to do |format|
+ if @dial_job.save
+ flash[:notice] = 'Job was successfully created.'
+
+ # Launch it
+ WarVOX::JobManager.schedule(::WarVOX::Jobs::Dialer, @dial_job.id)
+
+ format.html { redirect_to(@dial_job) }
+ format.xml { render :xml => @dial_job, :status => :created, :location…
+ else
+ format.html { render :action => "new" }
+ format.xml { render :xml => @dial_job.errors, :status => :unprocessab…
+ end
+ end
+ end
+
+ # DELETE /dial_jobs/1
+ # DELETE /dial_jobs/1.xml
+ def destroy
+ @dial_job = DialJob.find(params[:id])
+ @dial_job.destroy
+
+ respond_to do |format|
+ format.html { redirect_to(dial_jobs_url) }
+ format.xml { head :ok }
+ end
+ end
+
+ # GET /dial_jobs/1
+ # GET /dial_jobs/1.xml
+ def show
+ @dial_job = DialJob.find(params[:id])
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.xml { render :xml => @dial_job }
+ end
+ end
+
+
+end
diff --git a/web/app/controllers/dial_results_controller.rb b/web/app/controlle…
@@ -0,0 +1,204 @@
+class DialResultsController < ApplicationController
+ layout 'warvox'
+
+ # GET /dial_results
+ # GET /dial_results.xml
+ def index
+ @completed_jobs = DialJob.paginate_all_by_status(
+ 'completed',
+ :page => params[:page],
+ :order => 'id DESC',
+ :per_page => 30
+
+ )
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @dial_results }
+ end
+ end
+
+ # GET /dial_results/1/reanalyze
+ def reanalyze
+ DialResult.update_all(['processed = ?', false], ['dial_job_id = ?', …
+ j = DialJob.find(params[:id])
+ j.processed = false
+ j.save
+
+ redirect_to :action => 'analyze'
+ end
+
+ # GET /dial_results/1/process
+ # GET /dial_results/1/process.xml
+ def analyze
+ @job_id = params[:id]
+ @job = DialJob.find(@job_id)
+
+ if(@job.processed)
+ redirect_to :controller => 'analyze', :action => 'view', :id =…
+ return
+ end
+
+ @dial_data_total = DialResult.count(
+ :conditions => [ 'dial_job_id = ? and completed = ?', @job_id,…
+ )
+
+ @dial_data_done = DialResult.count(
+ :conditions => [ 'dial_job_id = ? and processed = ?', @job_id,…
+ )
+
+ @g1 = Ezgraphix::Graphic.new(:c_type => 'col3d', :div_name => 'calls_p…
+ @g1.render_options(:caption => 'Detected Lines by Type', :y_name => 'L…
+
+ ltypes = DialResult.find( :all, :select => 'DISTINCT line_type', :cond…
+ res_types = {}
+
+ ltypes.each do |k|
+ next if not k
+ res_types[k.capitalize.to_sym] = DialResult.count(
+ :conditions => ['dial_job_id = ? and line_type = ?', @…
+ )
+ end
+
+ @g1.data = res_types
+
+ @dial_data_todo = DialResult.paginate_all_by_dial_job_id(
+ @job_id,
+ :page => params[:page],
+ :order => 'number ASC',
+ :per_page => 50,
+ :conditions => [ 'completed = ? and processed = ? and busy = ?…
+ )
+
+ if(@dial_data_todo.length > 0)
+ WarVOX::JobManager.schedule(::WarVOX::Jobs::Analysis, @job_id)
+ end
+ end
+
+ # GET /dial_results/1/view
+ # GET /dial_results/1/view.xml
+ def view
+ @dial_results = DialResult.paginate_all_by_dial_job_id(
+ params[:id],
+ :page => params[:page],
+ :order => 'number ASC',
+ :per_page => 30
+ )
+
+ if(@dial_results)
+ @g1 = Ezgraphix::Graphic.new(:c_type => 'col3d', :div_name => …
+ @g1.render_options(:caption => 'Call Results', :w => 700, :h =…
+
+ @g1.data = {
+ :Timeout => DialResult.count(:conditions =>['dial_job…
+ :Busy => DialResult.count(:conditions =>['dial_job…
+ :Answered => DialResult.count(:conditions =>['dial_job…
+ }
+ end
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @dial_results }
+ end
+ end
+
+ # GET /dial_results/1
+ # GET /dial_results/1.xml
+ def show
+ @dial_result = DialResult.find(params[:id])
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.xml { render :xml => @dial_result }
+ end
+ end
+
+ # GET /dial_results/new
+ # GET /dial_results/new.xml
+ def new
+ @dial_result = DialResult.new
+
+ respond_to do |format|
+ format.html # new.html.erb
+ format.xml { render :xml => @dial_result }
+ end
+ end
+
+ # GET /dial_results/1/edit
+ def edit
+ @dial_result = DialResult.find(params[:id])
+ end
+
+ # POST /dial_results
+ # POST /dial_results.xml
+ def create
+ @dial_result = DialResult.new(params[:dial_result])
+
+ respond_to do |format|
+ if @dial_result.save
+ flash[:notice] = 'DialResult was successfully created.'
+ format.html { redirect_to(@dial_result) }
+ format.xml { render :xml => @dial_result, :status => :created, :locat…
+ else
+ format.html { render :action => "new" }
+ format.xml { render :xml => @dial_result.errors, :status => :unproces…
+ end
+ end
+ end
+
+ # PUT /dial_results/1
+ # PUT /dial_results/1.xml
+ def update
+ @dial_result = DialResult.find(params[:id])
+
+ respond_to do |format|
+ if @dial_result.update_attributes(params[:dial_result])
+ flash[:notice] = 'DialResult was successfully updated.'
+ format.html { redirect_to(@dial_result) }
+ format.xml { head :ok }
+ else
+ format.html { render :action => "edit" }
+ format.xml { render :xml => @dial_result.errors, :status => :unproces…
+ end
+ end
+ end
+
+ # DELETE /dial_results/1
+ # DELETE /dial_results/1.xml
+ def purge
+
+ @job = DialJob.find(params[:id])
+ @job.dial_results.each do |r|
+ r.destroy
+ end
+ @job.destroy
+
+ dir = nil
+ jid = @job.id
+ dfd = Dir.new(WarVOX::Config.data_path)
+ dfd.entries.each do |ent|
+ j,m = ent.split('-', 2)
+ if (m and j == jid)
+ dir = File.join(WarVOX::Config.data_path, ent)
+ end
+ end
+
+ FileUtils.rm_rf(dir) if dir
+
+ respond_to do |format|
+ format.html { redirect_to :action => 'index' }
+ format.xml { head :ok }
+ end
+ end
+
+ # DELETE /dial_results/1
+ # DELETE /dial_results/1.xml
+ def delete
+ @res = DialResult.find(params[:id])
+ @res.destroy
+ respond_to do |format|
+ format.html { redirect_to :action => 'index' }
+ format.xml { head :ok }
+ end
+ end
+end
diff --git a/web/app/controllers/home_controller.rb b/web/app/controllers/home_…
@@ -0,0 +1,16 @@
+class HomeController < ApplicationController
+ layout 'warvox'
+
+ def index
+
+ end
+
+ def about
+ begin
+ @has_kissfft = "MISSING"
+ require 'kissfft'
+ @has_kissfft = $LOADED_FEATURES.grep(/kissfft/)[0]
+ rescue ::LoadError
+ end
+ end
+end
diff --git a/web/app/controllers/providers_controller.rb b/web/app/controllers/…
@@ -0,0 +1,91 @@
+class ProvidersController < ApplicationController
+ layout 'warvox'
+
+ # GET /providers
+ # GET /providers.xml
+ def index
+ @providers = Provider.find(:all)
+ @new_provider = Provider.new
+ @new_provider.enabled = true
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @providers }
+ end
+ end
+
+ # GET /providers/1
+ # GET /providers/1.xml
+ def show
+ @provider = Provider.find(params[:id])
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.xml { render :xml => @provider }
+ end
+ end
+
+ # GET /providers/new
+ # GET /providers/new.xml
+ def new
+ @provider = Provider.new
+ @provider.enabled = true
+
+ respond_to do |format|
+ format.html # new.html.erb
+ format.xml { render :xml => @provider }
+ end
+ end
+
+ # GET /providers/1/edit
+ def edit
+ @provider = Provider.find(params[:id])
+ end
+
+ # POST /providers
+ # POST /providers.xml
+ def create
+ @provider = Provider.new(params[:provider])
+ @provider.enabled = true
+
+ respond_to do |format|
+ if @provider.save
+ flash[:notice] = 'Provider was successfully created.'
+ format.html { redirect_to(@provider) }
+ format.xml { render :xml => @provider, :status => :created, :location…
+ else
+ format.html { render :action => "new" }
+ format.xml { render :xml => @provider.errors, :status => :unprocessab…
+ end
+ end
+ end
+
+ # PUT /providers/1
+ # PUT /providers/1.xml
+ def update
+ @provider = Provider.find(params[:id])
+
+ respond_to do |format|
+ if @provider.update_attributes(params[:provider])
+ flash[:notice] = 'Provider was successfully updated.'
+ format.html { redirect_to(@provider) }
+ format.xml { head :ok }
+ else
+ format.html { render :action => "edit" }
+ format.xml { render :xml => @provider.errors, :status => :unprocessab…
+ end
+ end
+ end
+
+ # DELETE /providers/1
+ # DELETE /providers/1.xml
+ def destroy
+ @provider = Provider.find(params[:id])
+ @provider.destroy
+
+ respond_to do |format|
+ format.html { redirect_to(providers_url) }
+ format.xml { head :ok }
+ end
+ end
+end
diff --git a/web/app/helpers/analyze_helper.rb b/web/app/helpers/analyze_helper…
@@ -0,0 +1,2 @@
+module AnalyzeHelper
+end
diff --git a/web/app/helpers/application_helper.rb b/web/app/helpers/applicatio…
@@ -0,0 +1,70 @@
+# Methods added to this helper will be available to all templates in the appli…
+module ApplicationHelper
+
+ def get_sections
+
+ count = 0
+ html = ""
+ asection = nil
+
+ sections =
+ [
+ { :name => 'Home', :link => '/', …
+
+ ] },
+ { :name => 'Jobs' , :link => '/dial_jobs/', …
+ #
+ ] },
+ { :name => 'Results', :link => '/dial_results/', …
+ #
+ ] },
+ { :name => 'Analysis', :link => '/analyze/', …
+ #
+ ] },
+ { :name => 'Providers', :link => '/providers/', …
+ #
+ ] },
+ { :name => 'About', :link => '/home/about/', …
+ #
+ ] }
+ ]
+
+ html << "<div id='sections_container'>\n"
+ html << "<ul id='sections_ul'>\n"
+ sections.each do |section|
+ lactive = ''
+ if (params[:controller] == section[:controller])
+ lactive = "id='sections_active'"
+ asection = section
+ end
+ html << "<li><a #{lactive} href='#{section[:link]}'>#{…
+ end
+ html << "\n</ul></div>\n"
+
+ count = 0
+ html << "<div id='subsections_container'>\n"
+ html << "<ul id='subsections_ul'>\n"
+ (asection ? asection[:subsections] : [] ).each do |section|
+ html << "<li><a href='#{section[:link]}'>#{section[:na…
+ end
+ html << "\n</ul></div>\n"
+ raw(html)
+ end
+
+ def select_tag_for_filter(nvpairs, params)
+ _url = ( url_for :overwrite_params => { }).split('?')[0]
+ _html = %{<label for="show">Filter: </label>}
+ _html << %{<select name="show" id="show"}
+ _html << %{onchange="window.location='#{_url}' + '?show=' + this.val…
+ nvpairs.each do |pair|
+ _html << %{<option value="#{pair[:scope]}"}
+ if params[:show] == pair[:scope] || ((params[:show].nil? || params…
+ _html << %{ selected="selected"}
+ end
+ _html << %{>#{pair[:label]}}
+ _html << %{</option>}
+ end
+ _html << %{</select>}
+ raw(_html)
+ end
+end
diff --git a/web/app/helpers/dial_jobs_helper.rb b/web/app/helpers/dial_jobs_he…
@@ -0,0 +1,2 @@
+module DialJobsHelper
+end
diff --git a/web/app/helpers/dial_results_helper.rb b/web/app/helpers/dial_resu…
@@ -0,0 +1,2 @@
+module DialResultsHelper
+end
diff --git a/web/app/helpers/home_helper.rb b/web/app/helpers/home_helper.rb
@@ -0,0 +1,2 @@
+module HomeHelper
+end
diff --git a/web/app/helpers/providers_helper.rb b/web/app/helpers/providers_he…
@@ -0,0 +1,2 @@
+module ProvidersHelper
+end
diff --git a/web/app/models/dial_job.rb b/web/app/models/dial_job.rb
@@ -0,0 +1,30 @@
+class DialJob < ActiveRecord::Base
+ attr_accessor :range_file
+
+ has_many :dial_results
+
+ validates_presence_of :range, :lines, :seconds
+ validates_numericality_of :lines, :less_than => 256, :greater_than => 0
+ validates_numericality_of :seconds, :less_than => 301, :greater_than =…
+
+
+ validate :validate_range
+
+ def validate_range
+ if(range.gsub(/[^0-9X:,\n]/, '').empty?)
+ errors.add(:range, "must be at least 1 character long …
+ end
+
+ if(range.scan(/X/).length > 5)
+ errors.add(:range, "must contain no more than 5 mask d…
+ end
+
+ if(cid_mask != "SELF" and cid_mask.gsub(/[^0-9X]/, '').empty?)
+ errors.add(:range, "The Caller ID must be at least 1 c…
+ end
+
+ if(cid_mask != "SELF" and cid_mask.scan(/X/).length > 5)
+ errors.add(:range, "The Caller ID must contain no more…
+ end
+ end
+end
diff --git a/web/app/models/dial_result.rb b/web/app/models/dial_result.rb
@@ -0,0 +1,4 @@
+class DialResult < ActiveRecord::Base
+ belongs_to :provider
+ belongs_to :dial_job
+end
diff --git a/web/app/models/provider.rb b/web/app/models/provider.rb
@@ -0,0 +1,7 @@
+class Provider < ActiveRecord::Base
+ has_many :dial_results
+
+ validates_presence_of :name, :host, :port, :user, :pass, :lines
+ validates_numericality_of :port, :less_than => 65536, :greater_than =>…
+ validates_numericality_of :lines, :less_than => 255, :greater_than => 0
+end
diff --git a/web/app/views/analyze/index.html.erb b/web/app/views/analyze/index…
@@ -0,0 +1,38 @@
+<% if @jobs.length > 0 %>
+<h1 class='title'>Analyzed Jobs</h1>
+
+<%= will_paginate @jobs %>
+<table class='table_scaffold' width='100%'>
+ <tr>
+ <th>ID</th>
+ <th>Range</th>
+ <th>CallerID</th>
+ <th>Connected</th>
+ <th>Date</th>
+ </tr>
+
+<% @jobs.sort{|a,b| b.id <=> a.id}.each do |dial_job| %>
+ <tr>
+ <td><%=h dial_job.id %></td>
+ <td><%=h dial_job.range %></td>
+ <td><%=h dial_job.cid_mask %></td>
+ <td><%=h (
+ DialResult.count(:conditions => ['dial_job_id = ? and processe…
+ "/" +
+ DialResult.count(:conditions => ['dial_job_id = ?', dial_job.i…
+ )%></td>
+ <td><%=h dial_job.started_at.localtime.strftime("%Y-%m-%d %H:%M:%S") %></t…
+ <!-- <td><%= link_to 'Overview', show_analyze_path(dial_job) %></td> -->
+ <td><%= link_to 'Browse', view_analyze_path(dial_job) %></td>
+ <td><%= link_to 'ReAnalyze', reanalyze_dial_result_path(dial_job), :co…
+ </tr>
+<% end %>
+</table>
+
+<%= will_paginate @jobs %>
+
+<% else %>
+
+<h1 class='title'>No Analyzed Jobs</h1>
+
+<% end %>
diff --git a/web/app/views/analyze/show.html.erb b/web/app/views/analyze/show.h…
@@ -0,0 +1,9 @@
+<h1 class='title'>Overview of Job ID <%= @job_id %></h1>
+
+<table width='100%' align='center' border=0 cellspacing=0 cellpadding=6>
+<tr>
+ <td align='center'><%= render_ezgraphix @g1 %></td>
+</tr>
+</table>
+
+
diff --git a/web/app/views/analyze/view.html.erb b/web/app/views/analyze/view.h…
@@ -0,0 +1,62 @@
+<h1 class='title'>Analysis of Job ID <%= @job_id %></h1>
+
+<%= select_tag_for_filter(@filters, params) %>
+
+<table width='100%' align='center' border=0 cellspacing=0 cellpadding=6>
+<tr>
+ <td align='center'><%= raw(render_ezgraphix @g1) %></td>
+</tr>
+</table>
+
+<%= will_paginate @results %>
+
+<table class='table_scaffold' width='100%'>
+ <tr>
+ <th>Number</th>
+ <th>Signal</th>
+ </tr>
+
+<% @results.each do |dial_result| %>
+ <tr>
+ <td align='center'>
+
+ <object
+ type="application/x-shockwave-flash"
+ data="/images/musicplayer.swf?song_url=<%=resource_ana…
+ width="20"
+ height="17"
+ style="margin-bottom: -5px;"
+ >
+ <param name="movie" value="/musicplayer.swf?song_url=<…
+ <param name="wmode" value="transparent"></param>
+ </object>
+ <b><%= dial_result.number %></b>
+ <hr width='100%' size='1'/>
+ CallerID: <%= dial_result.cid%><br/>
+ Provider: <%=h dial_result.provider.name %><br/>
+ Audio: <%=h dial_result.seconds %> Seconds<br/>
+ Ringer: <%=h dial_result.ringtime %> Seconds<br/>
+ <% if(dial_result.dtmf and dial_result.dtmf.length > 0) %>
+ DTMF: <%=h dial_result.dtmf %><br/>
+ <% end %>
+ <% if(dial_result.mf and dial_result.mf.length > 0) %>
+ MF: <%=h dial_result.mf %><br/>
+ <% end %>
+ </td>
+ <td align='center'>
+ <b><%=h dial_result.line_type.upcase %></b><br/>
+ <a href="<%=resource_analyze_path(@job_id, dial_result.id, "bi…
+ <a href="<%=resource_analyze_path(@job_id, dial_result.id, "bi…
+ <% (dial_result.signatures||"").split("\n").each do |s|
+ sid,mat,name = s.split(':', 3)
+ str = [mat.to_i * 6.4, 255].min
+ col = ("%.2x" % (255 - str)) * 3
+ %>
+ <div style="color: #<%= col%>;"><%=h name%> (<%=h sid …
+ <% end %>
+ </td>
+ </tr>
+<% end %>
+</table>
+
+<%= will_paginate @results %>
diff --git a/web/app/views/dial_jobs/edit.html.erb b/web/app/views/dial_jobs/ed…
@@ -0,0 +1,24 @@
+<h1 class='title'>Modify Job</h1>
+
+<% form_for(@dial_job) do |f| %>
+ <%= f.error_messages %>
+
+ <p>
+ <%= f.label :range %><br />
+ <%= f.text_area :range, :size => "35x5" %>
+ </p>
+ <p>
+ <%= f.label :seconds %><br />
+ <%= f.text_field :seconds %>
+ </p>
+ <p>
+ <%= f.label :lines %><br />
+ <%= f.text_field :lines %>
+ </p>
+ <p>
+ <%= f.submit "Update" %>
+ </p>
+<% end %>
+
+<%= link_to 'Show', @dial_job %> |
+<%= link_to 'Back', dial_jobs_path %>
diff --git a/web/app/views/dial_jobs/index.html.erb b/web/app/views/dial_jobs/i…
@@ -0,0 +1,107 @@
+<% if(@submitted_jobs.length > 0) %>
+
+<h1 class='title'>Submitted Jobs</h1>
+
+<table class='table_scaffold' width='100%'>
+ <tr>
+ <th>ID</th>
+ <th>Range</th>
+ <th>CallerID</th>
+ <th>Seconds</th>
+ <th>Lines</th>
+ <th>Submitted Time</th>
+ </tr>
+
+<% @submitted_jobs.each do |dial_job| %>
+ <tr>
+ <td><%=h dial_job.id %></td>
+ <td><%=h dial_job.range %></td>
+ <td><%=h dial_job.cid_mask %></td>
+ <td><%=h dial_job.seconds %></td>
+ <td><%=h dial_job.lines %></td>
+ <td><%=h dial_job.created_at.localtime.strftime("%Y-%m-%d %H:%M:%S %Z") %>…
+ <td><%= link_to 'Execute', run_dial_job_path(dial_job), :confirm => 'Launc…
+ <td><%= link_to 'Modify', edit_dial_job_path(dial_job)%></td>
+ <td><%= link_to 'Delete', dial_job, :confirm => 'Are you sure?', :method =…
+ </tr>
+<% end %>
+</table>
+<br />
+<% end %>
+
+<% if(@active_jobs.length > 0) %>
+
+<h1 class='title'>Active Jobs</h1>
+
+<table class='table_scaffold' width='100%'>
+ <tr>
+ <th>ID</th>
+ <th>Range</th>
+ <th>CallerID</th>
+ <th>Seconds</th>
+ <th>Lines</th>
+ <th>Status</th>
+ <th>Progress</th>
+ <th>Start Time</th>
+ </tr>
+
+<% @active_jobs.each do |dial_job| %>
+ <tr class='active_job_row'>
+ <td><%=h dial_job.id %></td>
+ <td><%=h dial_job.range %></td>
+ <td><%=h dial_job.cid_mask %></td>
+ <td><%=h dial_job.seconds %></td>
+ <td><%=h dial_job.lines %></td>
+ <td><%=h dial_job.status %></td>
+ <td><%=h dial_job.progress %>%</td>
+ <td><%=h dial_job.started_at.localtime.strftime("%Y-%m-%d %H:%M:%S %Z") %>…
+ <td colspan='3'><%= link_to 'Stop', dial_job, :confirm => 'Are you sure?',…
+ </tr>
+<% end %>
+</table>
+<br />
+
+<script language="javascript">
+ setTimeout("location.reload(true);", 20000);
+</script>
+
+<% end %>
+
+<% if (@active_jobs.length + @submitted_jobs.length == 0) %>
+<h1 class='title'>No Active or Submitted Jobs</h1>
+<br/>
+
+<% end %>
+<h1 class='title'>Submit a New Job</h1>
+
+<%= form_for(@new_job, :html => { :multipart => true }) do |f| %>
+ <%= f.error_messages %>
+ <p>
+ <%= f.label :range, 'Specify target telephone range(s) (1-123-456-7890 or …
+ <%= f.text_area :range, :size => "35x5" %>
+ </p>
+
+ <p>
+ <%= f.label :range_file, 'Or upload a file containing the target ranges' %…
+ <%= f.file_field :range_file %>
+ </p>
+
+ <p>
+ <%= f.label :seconds, 'Seconds of audio to capture' %><br />
+ <%= f.text_field :seconds, :value => 53 %>
+ </p>
+
+ <p>
+ <%= f.label :lines, 'Maximum number of outgoing lines' %><br />
+ <%= f.text_field :lines, :value => 10 %>
+ </p>
+
+ <p>
+ <%= f.label :lines, 'The source Caller ID range (1-555-555-5555 or 1-555-5…
+ <%= f.text_field :cid_mask, :value => '1-123-456-XXXX' %>
+ </p>
+
+ <p>
+ <%= f.submit "Create" %>
+ </p>
+<% end %>
diff --git a/web/app/views/dial_jobs/new.html.erb b/web/app/views/dial_jobs/new…
@@ -0,0 +1,35 @@
+<h1 class='title'>Submit a New Job</h1>
+
+<% form_for(@dial_job, :html => { :multipart => true }) do |f| %>
+ <%= f.error_messages %>
+ <p>
+ <%= f.label :range, 'Specify target telephone range(s) (1-123-456-7890 or …
+ <%= f.text_area :range, :size => "35x5" %>
+ </p>
+
+ <p>
+ <%= f.label :range_file, 'Or upload a file containing the target ranges' %…
+ <%= f.file_field :range_file %>
+ </p>
+
+ <p>
+ <%= f.label :seconds, 'Seconds of audio to capture' %><br />
+ <%= f.text_field :seconds, :value => 53 %>
+ </p>
+
+ <p>
+ <%= f.label :lines, 'Maximum number of outgoing lines' %><br />
+ <%= f.text_field :lines, :value => 10 %>
+ </p>
+
+ <p>
+ <%= f.label :lines, 'The source Caller ID range (1-555-555-55XX or SELF)' …
+ <%= f.text_field :cid_mask, :value => '1-123-456-XXXX' %>
+ </p>
+
+ <p>
+ <%= f.submit "Create" %>
+ </p>
+<% end %>
+
+<%= link_to 'Back', dial_jobs_path %>
diff --git a/web/app/views/dial_jobs/run.html.erb b/web/app/views/dial_jobs/run…
@@ -0,0 +1,5 @@
+<h1 class='title'>Run Job</h1>
+
+Running this job...<br/>
+
+<%= link_to 'Back', dial_jobs_path %>
diff --git a/web/app/views/dial_jobs/show.html.erb b/web/app/views/dial_jobs/sh…
@@ -0,0 +1,39 @@
+<h1 class='title'>Show Job</h1>
+<p>
+ <b>Range:</b>
+ <%=h @dial_job.range %>
+</p>
+
+<p>
+ <b>Seconds:</b>
+ <%=h @dial_job.seconds %>
+</p>
+
+<p>
+ <b>Lines:</b>
+ <%=h @dial_job.lines %>
+</p>
+
+<p>
+ <b>Status:</b>
+ <%=h @dial_job.status %>
+</p>
+
+<p>
+ <b>Progress:</b>
+ <%=h @dial_job.progress %>
+</p>
+
+<p>
+ <b>Started at:</b>
+ <%=h @dial_job.started_at %>
+</p>
+
+<p>
+ <b>Completed at:</b>
+ <%=h @dial_job.completed_at %>
+</p>
+
+
+<%= link_to 'Edit', edit_dial_job_path(@dial_job) %> |
+<%= link_to 'Back', dial_jobs_path %>
diff --git a/web/app/views/dial_results/analyze.html.erb b/web/app/views/dial_r…
@@ -0,0 +1,25 @@
+<% if @dial_data_todo.length > 0 %>
+
+<h1 class='title'>
+ Analyzing Audio for <%= @dial_data_total-@dial_data_done %> of <%= @di…
+</h1>
+
+<table width='100%' align='center' border=0 cellspacing=0 cellpadding=6>
+<tr>
+<% if @dial_data_done > 0 %>
+ <td align='center'><%= render_ezgraphix @g1 %></td>
+<% end %>
+</tr>
+</table>
+
+<script language="javascript">
+ setTimeout("location.reload(true);", 10000);
+</script>
+
+<% else %>
+
+<h1 class='title'>No Completed Calls Found</h1>
+
+<% end %>
+
+<br />
diff --git a/web/app/views/dial_results/edit.html.erb b/web/app/views/dial_resu…
@@ -0,0 +1,48 @@
+<h1>Editing dial_result</h1>
+
+<% form_for(@dial_result) do |f| %>
+ <%= f.error_messages %>
+
+ <p>
+ <%= f.label :number %><br />
+ <%= f.text_field :number %>
+ </p>
+ <p>
+ <%= f.label :cid %><br />
+ <%= f.text_field :cid %>
+ </p>
+ <p>
+ <%= f.label :dial_job_id %><br />
+ <%= f.text_field :dial_job_id %>
+ </p>
+ <p>
+ <%= f.label :provider %><br />
+ <%= f.text_field :provider %>
+ </p>
+ <p>
+ <%= f.label :completed %><br />
+ <%= f.check_box :completed %>
+ </p>
+ <p>
+ <%= f.label :busy %><br />
+ <%= f.check_box :busy %>
+ </p>
+ <p>
+ <%= f.label :seconds %><br />
+ <%= f.text_field :seconds %>
+ </p>
+ <p>
+ <%= f.label :ringtime %><br />
+ <%= f.text_field :ringtime %>
+ </p>
+ <p>
+ <%= f.label :rawfile %><br />
+ <%= f.text_field :rawfile %>
+ </p>
+ <p>
+ <%= f.submit "Update" %>
+ </p>
+<% end %>
+
+<%= link_to 'Show', @dial_result %> |
+<%= link_to 'Back', dial_results_path %>
diff --git a/web/app/views/dial_results/index.html.erb b/web/app/views/dial_res…
@@ -0,0 +1,41 @@
+<% if @completed_jobs.length > 0 %>
+<h1 class='title'>Completed Jobs</h1>
+
+<table class='table_scaffold' width='100%'>
+ <tr>
+ <th>ID</th>
+ <th>Range</th>
+ <th>CID</th>
+ <th>Answered</th>
+ <th>Time</th>
+ </tr>
+
+<% @completed_jobs.sort{|a,b| b.id <=> a.id}.each do |dial_job| %>
+ <tr>
+ <td><%=h dial_job.id %></td>
+ <td><%=h dial_job.range %></td>
+ <td><%=h dial_job.cid_mask %></td>
+ <td><%=h (
+ DialResult.count(:conditions => ['dial_job_id = ? and complete…
+ "/" +
+ DialResult.count(:conditions => ['dial_job_id = ?', dial_job.i…
+ )%></td>
+ <td><%=h dial_job.started_at.localtime.strftime("%Y-%m-%d %H:%M:%S") %></t…
+ <td><%= link_to 'View', view_dial_result_path(dial_job) %></td>
+ <% if(dial_job.processed) %>
+ <td><%= link_to 'View Analysis', analyze_dial_result_path(dial…
+ <% else %>
+ <td><%= link_to 'Analyze Calls', analyze_dial_result_path(dial…
+ <% end %>
+ <td><%= link_to 'Purge', purge_dial_result_path(dial_job), :confirm =>…
+ </tr>
+<% end %>
+</table>
+
+<%= will_paginate @completed_jobs %>
+
+<% else %>
+
+<h1 class='title'>No Completed Jobs</h1>
+
+<% end %>
diff --git a/web/app/views/dial_results/new.html.erb b/web/app/views/dial_resul…
@@ -0,0 +1,43 @@
+<h1>New dial_result</h1>
+
+<% form_for(@dial_result) do |f| %>
+ <%= f.error_messages %>
+
+ <p>
+ <%= f.label :number %><br />
+ <%= f.text_field :number %>
+ </p>
+ <p>
+ <%= f.label :dial_job_id %><br />
+ <%= f.text_field :dial_job_id %>
+ </p>
+ <p>
+ <%= f.label :provider %><br />
+ <%= f.text_field :provider %>
+ </p>
+ <p>
+ <%= f.label :completed %><br />
+ <%= f.check_box :completed %>
+ </p>
+ <p>
+ <%= f.label :busy %><br />
+ <%= f.check_box :busy %>
+ </p>
+ <p>
+ <%= f.label :seconds %><br />
+ <%= f.text_field :seconds %>
+ </p>
+ <p>
+ <%= f.label :ringtime %><br />
+ <%= f.text_field :ringtime %>
+ </p>
+ <p>
+ <%= f.label :rawfile %><br />
+ <%= f.text_field :rawfile %>
+ </p>
+ <p>
+ <%= f.submit "Create" %>
+ </p>
+<% end %>
+
+<%= link_to 'Back', dial_results_path %>
diff --git a/web/app/views/dial_results/show.html.erb b/web/app/views/dial_resu…
@@ -0,0 +1,48 @@
+<p>
+ <b>Number:</b>
+ <%=h @dial_result.number %>
+</p>
+
+<p>
+ <b>CallerID:</b>
+ <%=h @dial_result.cid %>
+</p>
+
+<p>
+ <b>Dial job:</b>
+ <%=h @dial_result.dial_job_id %>
+</p>
+
+<p>
+ <b>Provider:</b>
+ <%=h @dial_result.provider %>
+</p>
+
+<p>
+ <b>Completed:</b>
+ <%=h @dial_result.completed %>
+</p>
+
+<p>
+ <b>Busy:</b>
+ <%=h @dial_result.busy %>
+</p>
+
+<p>
+ <b>Seconds:</b>
+ <%=h @dial_result.seconds %>
+</p>
+
+<p>
+ <b>Ringtime:</b>
+ <%=h @dial_result.ringtime %>
+</p>
+
+<p>
+ <b>Rawfile:</b>
+ <%=h @dial_result.rawfile %>
+</p>
+
+
+<%= link_to 'Edit', edit_dial_result_path(@dial_result) %> |
+<%= link_to 'Back', dial_results_path %>
diff --git a/web/app/views/dial_results/view.html.erb b/web/app/views/dial_resu…
@@ -0,0 +1,44 @@
+<% if @dial_results %>
+
+
+<h1 class='title'>Dial Results for Job <%=@dial_results[0].dial_job_id%></h1>
+
+<%= will_paginate @dial_results %>
+<table width='100%' align='center' border=0 cellspacing=0 cellpadding=6>
+<tr>
+ <td align='center'><%= raw(render_ezgraphix @g1) %></td>
+</tr>
+</table>
+
+<table class='table_scaffold' width='100%'>
+ <tr>
+ <th>Number</th>
+ <th>CallerID</th>
+ <th>Provider</th>
+ <th>Completed</th>
+ <th>Busy</th>
+ <th>Seconds</th>
+ <th>Ring Time</th>
+ </tr>
+
+
+<% for dial_result in @dial_results.sort{|a,b| a.number <=> b.number } %>
+ <tr>
+ <td><%=h dial_result.number %></td>
+ <td><%=h dial_result.cid %></td>
+ <td><%=h dial_result.provider.name %></td>
+ <td><%=h dial_result.completed %></td>
+ <td><%=h dial_result.busy %></td>
+ <td><%=h dial_result.seconds %></td>
+ <td><%=h dial_result.ringtime.to_i %></td>
+ </tr>
+<% end %>
+</table>
+<%= will_paginate @dial_results %>
+
+<% else %>
+
+<h1 class='title'>No Dial Results</h1>
+
+<% end %>
+<br />
diff --git a/web/app/views/home/about.html.erb b/web/app/views/home/about.html.…
@@ -0,0 +1,142 @@
+<table width='100%' align='center' border='0' cellpadding='9' cellspacing='0'>
+<tr><td valign='top'>
+
+<h1 class='title'>About WarVOX</h1>
+
+<b>WarVOX</b> is a product of <a href="http://www.rapid7.com/">Rapid7 LLC</a> …
+free software under the BSD license. WarVOX is intended for legal security ass…
+and research purposes only. The latest version of WarVOX can be found at
+<a href="http://warvox.org/">http://warvox.org/</a>. The WarVOX development
+team can be reached by email at warvox[at]metasploit.com.
+
+</td><td valign='top' align='center'>
+
+
+<h1 class='title'>Statistics</h1>
+
+<table id="warvox_stats" cellspacing="0" width=200>
+
+<tr>
+ <td valign="top" align="right" class="header_item">
+ WarVOX Version:
+ </td>
+ <td><%= WarVOX::VERSION %></td>
+</tr>
+
+<tr>
+ <td valign="top" align="right" class="header_item">
+ Providers:
+ </td>
+ <td><%= Provider.count %></td>
+</tr>
+
+
+<tr>
+ <td valign="top" align="right" class="header_item">
+ Active Jobs:
+ </td>
+ <td><%= DialJob.count(:conditions => ['status = ?', 'active']) %></td>
+</tr>
+
+<tr>
+ <td valign="top" align="right" class="header_item">
+ Total Jobs:
+ </td>
+ <td><%= DialJob.count %></td>
+</tr>
+
+<tr>
+ <td valign="top" align="right" class="header_item">
+ Results:
+ </td>
+ <td><%= DialResult.count %></td>
+</tr>
+</table>
+
+
+</td></tr>
+<tr><td valign='top' colspan='2'>
+
+<h1 class='title'>Configuration</h1>
+
+
+<table id="warvox_conf" cellspacing="0" width='100%'>
+
+<tr>
+ <td valign="top" align="right" class="header_item" width='200'>
+ Base Directory:
+ </td>
+ <td><%= WarVOX::Base %></td>
+</tr>
+
+
+<tr>
+ <td valign="top" align="right" class="header_item" width='200'>
+ Configuration File:
+ </td>
+ <td><%= WarVOX::Conf %></td>
+</tr>
+
+
+<tr>
+ <td valign="top" align="right" class="header_item" width='200'>
+ Data Storage:
+ </td>
+ <td><%= WarVOX::Config.data_path %></td>
+</tr>
+
+<tr>
+ <td valign="top" align="right" class="header_item" width='200'>
+ Admin User:
+ </td>
+ <td><%= WarVOX::Config.authentication_creds[0] %></td>
+</tr>
+
+<tr>
+ <td valign="top" align="right" class="header_item" width='200'>
+ GNUPlot
+ </td>
+ <td><%= WarVOX::Config.tool_path('gnuplot') || "MISSING" %></td>
+</tr>
+
+<tr>
+ <td valign="top" align="right" class="header_item" width='200'>
+ SOX
+ </td>
+ <td><%= WarVOX::Config.tool_path('sox') || "MISSING" %></td>
+</tr>
+
+<tr>
+ <td valign="top" align="right" class="header_item" width='200'>
+ LAME
+ </td>
+ <td><%= WarVOX::Config.tool_path('lame') || "MISSING" %></td>
+</tr>
+
+<tr>
+ <td valign="top" align="right" class="header_item" width='200'>
+ IAXRECORD
+ </td>
+ <td><%= WarVOX::Config.tool_path('iaxrecord') || "MISSING" %></td>
+</tr>
+
+<tr>
+ <td valign="top" align="right" class="header_item" width='200'>
+ KissFFT
+ </td>
+ <td><%= @has_kissfft %></td>
+</tr>
+</table>
+
+<br/><br/>
+
+<h1 class='title'>Dial Exclusions (Blacklist)</h1>
+<table id="warvox_blacklist" cellspacing="0" width='100%'>
+<tr>
+ <td valign="top" align="left" class="header_item" width='200'>
+ <pre><%=h File.read(WarVOX::Config.blacklist_path) %></pre>
+ </td>
+</tr>
+</table>
+
+</td></tr></table>
diff --git a/web/app/views/home/index.html.erb b/web/app/views/home/index.html.…
@@ -0,0 +1,45 @@
+<h1 class='title'>Introduction</h1>
+
+<p>
+WarVOX is a suite of tools for exploring, classifying, and auditing telephone …
+WarVOX works with the actual audio from each call and does not use a modem dir…
+and classify a wide range of interesting lines, including modems, faxes, voice…
+and forwarders. WarVOX provides the unique ability to classify all telephone l…
+connected to modems, allowing for a comprehensive audit of a telephone system.
+</p>
+
+<h1 class='title'>Getting Started</h1>
+<p>
+In order to make phone calls, WarVOX needs to be configured with one or more s…
+</p>
+
+<p>Once one or more service providers have been configured, click the <a href=…
+</p>
+
+<p>The phone number range is specified by entering the phone number (including…
+</p>
+
+<p>
+The seconds field indicates the number of seconds to spend on each call, inclu…
+</p>
+
+<p>
+The outgoing line count is limited by the number of providers available and th…
+</p>
+
+<p>The Caller ID is specified by entering the phone number (including country …
+</p>
+
+<p>
+Once the job parameters have been specified, click the <b>Create</b> button to…
+</p>
+
+<p>
+After the job completes, access the <a href="/dial_results/">Results</a> link …
+</p>
+
+
+<h1 class='title'>Troubleshooting</h1>
+<p>
+If for some reason WarVOX is not working correctly, or if you have any questio…
+</p>
diff --git a/web/app/views/layouts/application.html.erb b/web/app/views/layouts…
diff --git a/web/app/views/layouts/warvox.html.erb b/web/app/views/layouts/warv…
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title><%= @title || "WarVOX" %></title>
+ <%= stylesheet_link_tag :all %>
+ <%= javascript_include_tag :defaults %>
+ <%= csrf_meta_tag %>
+ <%= stylesheet_link_tag 'global', 'lightbox' %>
+ <!--[if IE 7]><%= stylesheet_link_tag 'ie7' %><![endif]-->
+ <%= javascript_include_tag 'custom', 'prototype', 'effects', 'FusionCharts',…
+</head>
+<body>
+
+<div id="header">
+ <%= render :partial => 'shared/header' %>
+</div>
+
+<div id="content">
+ <div class="box_full">
+ <img src="/images/round_top.png" id="round_top" alt=""/>
+ <div id="main">
+<%= yield %>
+ <br/><br/>
+
+ <div id="footer">
+ <%= render :partial => 'shared/footer' %>
+ </div>
+
+ </div>
+ <img src="/images/round_bot.png" id="round_bot" alt=""/>
+ </div>
+</div>
+
+<br/><br/>
+
+</body>
+</html>
diff --git a/web/app/views/providers/edit.html.erb b/web/app/views/providers/ed…
@@ -0,0 +1,38 @@
+<h1 class='title'>Edit Provider</h1>
+
+<% form_for(@provider) do |f| %>
+ <%= f.error_messages %>
+ <p>
+ <%= f.label :enabled %><br />
+ <%= f.check_box :enabled %>
+ </p>
+ <p>
+ <%= f.label :name %><br />
+ <%= f.text_field :name %>
+ </p>
+ <p>
+ <%= f.label :host %><br />
+ <%= f.text_field :host %>
+ </p>
+ <p>
+ <%= f.label :port %><br />
+ <%= f.text_field :port %>
+ </p>
+ <p>
+ <%= f.label :user %><br />
+ <%= f.text_field :user %>
+ </p>
+ <p>
+ <%= f.label :pass %><br />
+ <%= f.text_field :pass %>
+ </p>
+ <p>
+ <%= f.label :lines %><br />
+ <%= f.text_field :lines %>
+ </p>
+ <p>
+ <%= f.submit "Update" %>
+ </p>
+<% end %>
+
+<%= link_to 'Back', providers_path %>
diff --git a/web/app/views/providers/index.html.erb b/web/app/views/providers/i…
@@ -0,0 +1,72 @@
+<% if @providers.length > 0 %>
+<h1 class='title'>Providers</h1>
+<table class='table_scaffold' width='100%'>
+ <tr>
+ <th>Enabled</th>
+ <th>Name</th>
+ <th>Host</th>
+ <th>Port</th>
+ <th>User</th>
+ <th>Pass</th>
+ <th>Lines</th>
+ </tr>
+
+<% for provider in @providers %>
+ <tr>
+ <td><%=h provider.enabled %></td>
+ <td><%=h provider.name %></td>
+ <td><%=h provider.host %></td>
+ <td><%=h provider.port %></td>
+ <td><%=h provider.user %></td>
+ <td>********</td>
+ <td><%=h provider.lines %></td>
+ <td><%= link_to 'Modify', edit_provider_path(provider) %></td>
+ <td><%= link_to 'Delete', provider, :confirm => 'Are you sure?', :method =…
+ </tr>
+<% end %>
+</table>
+
+<br />
+
+<%= link_to 'New Provider', new_provider_path %>
+
+<% else %>
+
+<h1 class='title'>No Configured Providers</h1>
+<br/>
+
+<h1 class='title'>New Provider</h1>
+
+<% form_for(@new_provider) do |f| %>
+ <%= f.error_messages %>
+ <p>
+ <%= f.label :name, 'The nickname for this provider' %><br />
+ <%= f.text_field :name %>
+ </p>
+ <p>
+ <%= f.label :host, 'The IAX2 server name' %><br />
+ <%= f.text_field :host %>
+ </p>
+ <p>
+ <%= f.label :port, 'The IAX2 port (normally 4569)' %><br />
+ <%= f.text_field :port, :value => 4569 %>
+ </p>
+ <p>
+ <%= f.label :user, 'The username to access the provider' %><br />
+ <%= f.text_field :user %>
+ </p>
+ <p>
+ <%= f.label :pass, 'The password to access the provider' %><br />
+ <%= f.text_field :pass %>
+ </p>
+ <p>
+ <%= f.label :lines, 'The number of available outbound lines' %><br />
+ <%= f.text_field :lines, :value => 1 %>
+ </p>
+ <p>
+ <%= f.submit "Create" %>
+ </p>
+<% end %>
+
+<% end %>
+
diff --git a/web/app/views/providers/new.html.erb b/web/app/views/providers/new…
@@ -0,0 +1,34 @@
+<h1 class='title'>New Provider</h1>
+
+<% form_for(@provider) do |f| %>
+ <%= f.error_messages %>
+ <p>
+ <%= f.label :name, 'The nickname for this provider' %><br />
+ <%= f.text_field :name %>
+ </p>
+ <p>
+ <%= f.label :host, 'The IAX2 server name' %><br />
+ <%= f.text_field :host %>
+ </p>
+ <p>
+ <%= f.label :port, 'The IAX2 port (normally 4569)' %><br />
+ <%= f.text_field :port, :value => 4569 %>
+ </p>
+ <p>
+ <%= f.label :user, 'The username to access the provider' %><br />
+ <%= f.text_field :user %>
+ </p>
+ <p>
+ <%= f.label :pass, 'The password to access the provider' %><br />
+ <%= f.text_field :pass %>
+ </p>
+ <p>
+ <%= f.label :lines, 'The number of available outbound lines' %><br />
+ <%= f.text_field :lines, :value => 1 %>
+ </p>
+ <p>
+ <%= f.submit "Create" %>
+ </p>
+<% end %>
+
+<%= link_to 'Back', providers_path %>
diff --git a/web/app/views/providers/show.html.erb b/web/app/views/providers/sh…
@@ -0,0 +1,38 @@
+<h1 class='title'>View Provider</h1>
+<p>
+ <b>Enabled:</b>
+ <%=h @provider.enabled %>
+</p>
+<p>
+ <b>Name:</b>
+ <%=h @provider.name %>
+</p>
+
+<p>
+ <b>Host:</b>
+ <%=h @provider.host %>
+</p>
+
+<p>
+ <b>Port:</b>
+ <%=h @provider.port %>
+</p>
+
+<p>
+ <b>User:</b>
+ <%=h @provider.user %>
+</p>
+
+<p>
+ <b>Pass:</b>
+ ********
+</p>
+
+<p>
+ <b>Lines:</b>
+ <%=h @provider.lines %>
+</p>
+
+
+<%= link_to 'Edit', edit_provider_path(@provider) %> |
+<%= link_to 'Back', providers_path %>
diff --git a/web/app/views/shared/_footer.html.erb b/web/app/views/shared/_foot…
@@ -0,0 +1,5 @@
+<div id="footer">
+ <div id='copyright'>
+ Copyright &copy;2009-2010 Rapid7 LLC<br/>
+ </div>
+</div>
diff --git a/web/app/views/shared/_header.html.erb b/web/app/views/shared/_head…
@@ -0,0 +1,17 @@
+<div id="header">
+
+ <span width='50%'>
+ <a href="/"><img id="logo" src="/images/logo.png" alt="WarVOX …
+ </span>
+
+ <span width='50%'>
+ <div id="navwrap">
+ <div id="nav">
+ <%= get_sections() %>
+ </div>
+ </div>
+ </span>
+
+ <br/>
+ <hr width='100%' size=5 color='black'/>
+</div>
diff --git a/web/config.ru b/web/config.ru
@@ -0,0 +1,4 @@
+# This file is used by Rack-based servers to start the application.
+
+require ::File.expand_path('../config/environment', __FILE__)
+run Web::Application
diff --git a/web/config/application.rb b/web/config/application.rb
@@ -0,0 +1,49 @@
+require File.expand_path('../boot', __FILE__)
+
+# Bootstrap the WarVOX code base
+$:.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
+require 'warvox'
+
+require 'rails/all'
+
+# If you have a Gemfile, require the gems listed there, including any gems
+# you've limited to :test, :development, or :production.
+Bundler.require(:default, Rails.env) if defined?(Bundler)
+
+module Web
+ class Application < Rails::Application
+ # Settings in config/environments/* take precedence over those specified h…
+ # Application configuration should go into files in config/initializers
+ # -- all .rb files in that directory are automatically loaded.
+
+ # Custom directories with classes and modules you want to be autoloadable.
+ # config.autoload_paths += %W(#{config.root}/extras)
+
+ # Only load the plugins named here, in the order given (default is alphabe…
+ # :all can be used as a placeholder for all plugins not explicitly named.
+ # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
+
+ # Activate observers that should always be running.
+ # config.active_record.observers = :cacher, :garbage_collector, :forum_obs…
+
+ # Set Time.zone default to the specified zone and make Active Record auto-…
+ # Run "rake -D time" for a list of tasks for finding time zone names. Defa…
+ # config.time_zone = 'Central Time (US & Canada)'
+
+ # The default locale is :en and all translations from config/locales/*.rb,…
+ # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml…
+ # config.i18n.default_locale = :de
+
+ # JavaScript files you want as :defaults (application.js is always include…
+ # config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
+
+ # Configure the default encoding used in templates for Ruby 1.9.
+ config.encoding = "utf-8"
+
+ # Configure sensitive parameters which will be filtered from the log file.
+ config.filter_parameters += [:password, :pass]
+
+ config.session_store :cookie_store, :key => "_warvox"
+ config.secret_token = WarVOX::Config.load_session_key
+ end
+end
diff --git a/web/config/boot.rb b/web/config/boot.rb
@@ -0,0 +1,13 @@
+require 'rubygems'
+
+# Set up gems listed in the Gemfile.
+gemfile = File.expand_path('../../Gemfile', __FILE__)
+begin
+ ENV['BUNDLE_GEMFILE'] = gemfile
+ require 'bundler'
+ Bundler.setup
+rescue Bundler::GemNotFound => e
+ STDERR.puts e.message
+ STDERR.puts "Try running `bundle install`."
+ exit!
+end if File.exist?(gemfile)
diff --git a/web/config/database.yml b/web/config/database.yml
@@ -0,0 +1,22 @@
+# SQLite version 3.x
+# gem install sqlite3-ruby (not necessary on OS X Leopard)
+development:
+ adapter: sqlite3
+ database: db/development.sqlite3
+ pool: 5
+ timeout: 5000
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ adapter: sqlite3
+ database: db/test.sqlite3
+ pool: 5
+ timeout: 5000
+
+production:
+ adapter: sqlite3
+ database: db/production.sqlite3
+ pool: 5
+ timeout: 5000
diff --git a/web/config/environment.rb b/web/config/environment.rb
@@ -0,0 +1,5 @@
+# Load the rails application
+require File.expand_path('../application', __FILE__)
+
+# Initialize the rails application
+Web::Application.initialize!
diff --git a/web/config/environments/development.rb b/web/config/environments/d…
@@ -0,0 +1,26 @@
+Web::Application.configure do
+ # Settings specified here will take precedence over those in config/environm…
+
+ # In the development environment your application's code is reloaded on
+ # every request. This slows down response time but is perfect for developme…
+ # since you don't have to restart the webserver when you make code changes.
+ config.cache_classes = false
+
+ # Log error messages when you accidentally call methods on nil.
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_view.debug_rjs = true
+ config.action_controller.perform_caching = false
+
+ # Don't care if the mailer can't send
+ config.action_mailer.raise_delivery_errors = false
+
+ # Print deprecation notices to the Rails logger
+ config.active_support.deprecation = :log
+
+ # Only use best-standards-support built into browsers
+ config.action_dispatch.best_standards_support = :builtin
+end
+
diff --git a/web/config/environments/production.rb b/web/config/environments/pr…
@@ -0,0 +1,49 @@
+Web::Application.configure do
+ # Settings specified here will take precedence over those in config/environm…
+
+ # The production environment is meant for finished, "live" apps.
+ # Code is not reloaded between requests
+ config.cache_classes = true
+
+ # Full error reports are disabled and caching is turned on
+ config.consider_all_requests_local = false
+ config.action_controller.perform_caching = true
+
+ # Specifies the header that your server uses for sending files
+ config.action_dispatch.x_sendfile_header = "X-Sendfile"
+
+ # For nginx:
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
+
+ # If you have no front-end server that supports something like X-Sendfile,
+ # just comment this out and Rails will serve the files
+
+ # See everything in the log (default is :info)
+ # config.log_level = :debug
+
+ # Use a different logger for distributed setups
+ # config.logger = SyslogLogger.new
+
+ # Use a different cache store in production
+ # config.cache_store = :mem_cache_store
+
+ # Disable Rails's static asset server
+ # In production, Apache or nginx will already do this
+ config.serve_static_assets = false
+
+ # Enable serving of images, stylesheets, and javascripts from an asset server
+ # config.action_controller.asset_host = "http://assets.example.com"
+
+ # Disable delivery errors, bad email addresses will be ignored
+ # config.action_mailer.raise_delivery_errors = false
+
+ # Enable threaded mode
+ # config.threadsafe!
+
+ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
+ # the I18n.default_locale when a translation can not be found)
+ config.i18n.fallbacks = true
+
+ # Send deprecation notices to registered listeners
+ config.active_support.deprecation = :notify
+end
diff --git a/web/config/environments/test.rb b/web/config/environments/test.rb
@@ -0,0 +1,35 @@
+Web::Application.configure do
+ # Settings specified here will take precedence over those in config/environm…
+
+ # The test environment is used exclusively to run your application's
+ # test suite. You never need to work with it otherwise. Remember that
+ # your test database is "scratch space" for the test suite and is wiped
+ # and recreated between test runs. Don't rely on the data there!
+ config.cache_classes = true
+
+ # Log error messages when you accidentally call methods on nil.
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Raise exceptions instead of rendering exception templates
+ config.action_dispatch.show_exceptions = false
+
+ # Disable request forgery protection in test environment
+ config.action_controller.allow_forgery_protection = false
+
+ # Tell Action Mailer not to deliver emails to the real world.
+ # The :test delivery method accumulates sent emails in the
+ # ActionMailer::Base.deliveries array.
+ config.action_mailer.delivery_method = :test
+
+ # Use SQL instead of Active Record's schema dumper when creating the test da…
+ # This is necessary if your schema can't be completely dumped by the schema …
+ # like if you have constraints or database-specific column types
+ # config.active_record.schema_format = :sql
+
+ # Print deprecation notices to the stderr
+ config.active_support.deprecation = :stderr
+end
diff --git a/web/config/initializers/backtrace_silencers.rb b/web/config/initia…
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# You can add backtrace silencers for libraries that you're using but don't wi…
+# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+
+# You can also remove all the silencers if you're trying to debug a problem th…
+# Rails.backtrace_cleaner.remove_silencers!
diff --git a/web/config/initializers/inflections.rb b/web/config/initializers/i…
@@ -0,0 +1,10 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new inflection rules using the following format
+# (all these examples are active by default):
+# ActiveSupport::Inflector.inflections do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
diff --git a/web/config/initializers/mime_types.rb b/web/config/initializers/mi…
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new mime types for use in respond_to blocks:
+# Mime::Type.register "text/richtext", :rtf
+# Mime::Type.register_alias "text/html", :iphone
diff --git a/web/config/initializers/secret_token.rb b/web/config/initializers/…
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# Your secret key for verifying the integrity of signed cookies.
+# If you change this key, all old signed cookies will become invalid!
+# Make sure the secret is at least 30 characters and all random,
+# no regular words or you'll be exposed to dictionary attacks.
+Web::Application.config.secret_token = '1bd648becb978b46fd65fe255a047ea09d9a06…
diff --git a/web/config/initializers/session_store.rb b/web/config/initializers…
@@ -0,0 +1,8 @@
+# Be sure to restart your server when you modify this file.
+
+Web::Application.config.session_store :cookie_store, :key => '_web_session'
+
+# Use the database for sessions instead of the cookie-based default,
+# which shouldn't be used to store highly confidential information
+# (create the session table with "rake db:sessions:create")
+# Web::Application.config.session_store :active_record_store
diff --git a/web/config/initializers/warvox.rb b/web/config/initializers/warvox…
diff --git a/web/config/locales/en.yml b/web/config/locales/en.yml
@@ -0,0 +1,5 @@
+# Sample localization file for English. Add more files in this directory for o…
+# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for st…
+
+en:
+ hello: "Hello world"
diff --git a/web/config/routes.rb b/web/config/routes.rb
@@ -0,0 +1,23 @@
+Web::Application.routes.draw do
+
+ resources :dial_jobs
+ resources :dial_results
+ resources :providers
+
+ match '/dial_jobs/:id/run' => 'dial_jobs#run', :as => :run_dial_job
+ match '/dial_results/:id/view' => 'dial_results#view', :as => :view_dial…
+ match '/dial_results/:id/analyze' => 'dial_results#analyze', :as => :analyz…
+ match '/dial_results/:id/reanalyze' => 'dial_results#reanalyze', :as => :re…
+ match '/dial_results/:id/purge' => 'dial_results#purge', :as => :purge_di…
+
+ match '/analyze/:id/resource/:result_id/:type' => 'analyze#resource', :as =>…
+ match '/analyze/:id/view' => 'analyze#view', :as => :view_analyze
+ match '/analyze/:id/show' => 'analyze#show', :as => :show_analyze
+ match '/analyze' => 'analyze#index'
+
+ match '/about' => 'home#about'
+ match '/home/about' => 'home#about'
+
+ root :to => "home#index"
+
+end
diff --git a/web/db/migrate/20090228195925_create_providers.rb b/web/db/migrate…
@@ -0,0 +1,18 @@
+class CreateProviders < ActiveRecord::Migration
+ def self.up
+ create_table :providers do |t|
+ t.string :name
+ t.string :host
+ t.integer :port
+ t.string :user
+ t.string :pass
+ t.integer :lines
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :providers
+ end
+end
diff --git a/web/db/migrate/20090228200035_create_dial_jobs.rb b/web/db/migrate…
@@ -0,0 +1,20 @@
+class CreateDialJobs < ActiveRecord::Migration
+ def self.up
+ create_table :dial_jobs do |t|
+ t.string :range
+ t.integer :seconds
+ t.integer :lines
+ t.string :status
+ t.integer :progress
+ t.datetime :started_at
+ t.datetime :completed_at
+ t.boolean :processed
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :dial_jobs
+ end
+end
diff --git a/web/db/migrate/20090228200141_create_dial_results.rb b/web/db/migr…
@@ -0,0 +1,21 @@
+class CreateDialResults < ActiveRecord::Migration
+ def self.up
+ create_table :dial_results do |t|
+ t.integer :number
+ t.integer :dial_job_id
+ t.integer :provider_id
+ t.boolean :completed
+ t.boolean :busy
+ t.integer :seconds
+ t.integer :ringtime
+ t.string :rawfile
+ t.boolean :processed
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :dial_results
+ end
+end
diff --git a/web/db/migrate/20090301084459_add_processed_at_to_dial_result.rb b…
@@ -0,0 +1,9 @@
+class AddProcessedAtToDialResult < ActiveRecord::Migration
+ def self.up
+ add_column :dial_results, :processed_at, :datetime
+ end
+
+ def self.down
+ remove_column :dial_results, :processed_at
+ end
+end
diff --git a/web/db/migrate/20090303204859_add_cid_mask_to_dial_jobs.rb b/web/d…
@@ -0,0 +1,9 @@
+class AddCidMaskToDialJobs < ActiveRecord::Migration
+ def self.up
+ add_column :dial_jobs, :cid_mask, :string
+ end
+
+ def self.down
+ remove_column :dial_jobs, :cid_mask
+ end
+end
diff --git a/web/db/migrate/20090303204917_add_cid_to_dial_results.rb b/web/db/…
@@ -0,0 +1,9 @@
+class AddCidToDialResults < ActiveRecord::Migration
+ def self.up
+ add_column :dial_results, :cid, :string
+ end
+
+ def self.down
+ remove_column :dial_results, :cid
+ end
+end
diff --git a/web/db/migrate/20090303225838_add_enabled_to_providers.rb b/web/db…
@@ -0,0 +1,9 @@
+class AddEnabledToProviders < ActiveRecord::Migration
+ def self.up
+ add_column :providers, :enabled, :boolean
+ end
+
+ def self.down
+ remove_column :providers, :enabled
+ end
+end
diff --git a/web/db/migrate/20090304013815_add_peak_freq_to_dial_results.rb b/w…
@@ -0,0 +1,9 @@
+class AddPeakFreqToDialResults < ActiveRecord::Migration
+ def self.up
+ add_column :dial_results, :peak_freq, :number
+ end
+
+ def self.down
+ remove_column :dial_results, :peak_freq
+ end
+end
diff --git a/web/db/migrate/20090304013839_add_peak_freq_data_to_dial_results.r…
@@ -0,0 +1,9 @@
+class AddPeakFreqDataToDialResults < ActiveRecord::Migration
+ def self.up
+ add_column :dial_results, :peak_freq_data, :string
+ end
+
+ def self.down
+ remove_column :dial_results, :peak_freq_data
+ end
+end
diff --git a/web/db/migrate/20090304013909_add_sig_data_to_dial_results.rb b/we…
@@ -0,0 +1,9 @@
+class AddSigDataToDialResults < ActiveRecord::Migration
+ def self.up
+ add_column :dial_results, :sig_data, :string
+ end
+
+ def self.down
+ remove_column :dial_results, :sig_data
+ end
+end
diff --git a/web/db/migrate/20090304014018_add_line_type_to_dial_results.rb b/w…
@@ -0,0 +1,9 @@
+class AddLineTypeToDialResults < ActiveRecord::Migration
+ def self.up
+ add_column :dial_results, :line_type, :string
+ end
+
+ def self.down
+ remove_column :dial_results, :line_type
+ end
+end
diff --git a/web/db/migrate/20090304014033_add_notes_to_dial_results.rb b/web/d…
@@ -0,0 +1,9 @@
+class AddNotesToDialResults < ActiveRecord::Migration
+ def self.up
+ add_column :dial_results, :notes, :string
+ end
+
+ def self.down
+ remove_column :dial_results, :notes
+ end
+end
diff --git a/web/db/migrate/20090522202032_add_signatures_to_dial_results.rb b/…
@@ -0,0 +1,9 @@
+class AddSignaturesToDialResults < ActiveRecord::Migration
+ def self.up
+ add_column :dial_results, :signatures, :string
+ end
+
+ def self.down
+ remove_column :dial_results, :signatures
+ end
+end
diff --git a/web/db/migrate/20090526031826_add_mf_and_dtmf_to_dial_results.rb b…
@@ -0,0 +1,11 @@
+class AddMfAndDtmfToDialResults < ActiveRecord::Migration
+ def self.up
+ add_column :dial_results, :dtmf, :string
+ add_column :dial_results, :mf, :string
+ end
+
+ def self.down
+ remove_column :dial_results, :dtmf
+ remove_column :dial_results, :mf
+ end
+end
diff --git a/web/db/schema.rb b/web/db/schema.rb
@@ -0,0 +1,65 @@
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definiti…
+#
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more mi…
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended to check this file into your version control syste…
+
+ActiveRecord::Schema.define(:version => 20090526031826) do
+
+ create_table "dial_jobs", :force => true do |t|
+ t.string "range"
+ t.integer "seconds"
+ t.integer "lines"
+ t.string "status"
+ t.integer "progress"
+ t.datetime "started_at"
+ t.datetime "completed_at"
+ t.boolean "processed"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "cid_mask"
+ end
+
+ create_table "dial_results", :force => true do |t|
+ t.integer "number"
+ t.integer "dial_job_id"
+ t.integer "provider_id"
+ t.boolean "completed"
+ t.boolean "busy"
+ t.integer "seconds"
+ t.integer "ringtime"
+ t.string "rawfile"
+ t.boolean "processed"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.datetime "processed_at"
+ t.string "cid"
+ t.decimal "peak_freq"
+ t.string "peak_freq_data"
+ t.string "sig_data"
+ t.string "line_type"
+ t.string "notes"
+ t.string "signatures"
+ t.string "dtmf"
+ t.string "mf"
+ end
+
+ create_table "providers", :force => true do |t|
+ t.string "name"
+ t.string "host"
+ t.integer "port"
+ t.string "user"
+ t.string "pass"
+ t.integer "lines"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.boolean "enabled"
+ end
+
+end
diff --git a/web/db/seeds.rb b/web/db/seeds.rb
@@ -0,0 +1,7 @@
+# This file should contain all the record creation needed to seed the database…
+# The data can then be loaded with the rake db:seed (or created alongside the …
+#
+# Examples:
+#
+# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
+# Mayor.create(:name => 'Daley', :city => cities.first)
diff --git a/web/public/404.html b/web/public/404.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The page you were looking for doesn't exist (404)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-famil…
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/404.html -->
+ <div class="dialog">
+ <h1>The page you were looking for doesn't exist.</h1>
+ <p>You may have mistyped the address or the page may have moved.</p>
+ </div>
+</body>
+</html>
diff --git a/web/public/422.html b/web/public/422.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The change you wanted was rejected (422)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-famil…
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/422.html -->
+ <div class="dialog">
+ <h1>The change you wanted was rejected.</h1>
+ <p>Maybe you tried to change something you didn't have access to.</p>
+ </div>
+</body>
+</html>
diff --git a/web/public/500.html b/web/public/500.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>We're sorry, but something went wrong (500)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-famil…
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/500.html -->
+ <div class="dialog">
+ <h1>We're sorry, but something went wrong.</h1>
+ <p>We've been notified about this issue and we'll take a look at it shortl…
+ </div>
+</body>
+</html>
diff --git a/web/public/FusionCharts/FCF_Area2D.swf b/web/public/FusionCharts/F…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_Bar2D.swf b/web/public/FusionCharts/FC…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_Candlestick.swf b/web/public/FusionCha…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_Column2D.swf b/web/public/FusionCharts…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_Column3D.swf b/web/public/FusionCharts…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_Doughnut2D.swf b/web/public/FusionChar…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_Funnel.swf b/web/public/FusionCharts/F…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_Gantt.swf b/web/public/FusionCharts/FC…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_Line.swf b/web/public/FusionCharts/FCF…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_MSArea2D.swf b/web/public/FusionCharts…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_MSBar2D.swf b/web/public/FusionCharts/…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_MSColumn2D.swf b/web/public/FusionChar…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_MSColumn2DLineDY.swf b/web/public/Fusi…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_MSColumn3D.swf b/web/public/FusionChar…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_MSColumn3DLineDY.swf b/web/public/Fusi…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_MSLine.swf b/web/public/FusionCharts/F…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_Pie2D.swf b/web/public/FusionCharts/FC…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_Pie3D.swf b/web/public/FusionCharts/FC…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_StackedArea2D.swf b/web/public/FusionC…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_StackedBar2D.swf b/web/public/FusionCh…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_StackedColumn2D.swf b/web/public/Fusio…
Binary files differ.
diff --git a/web/public/FusionCharts/FCF_StackedColumn3D.swf b/web/public/Fusio…
Binary files differ.
diff --git a/web/public/favicon.ico b/web/public/favicon.ico
diff --git a/web/public/images/balloon.png b/web/public/images/balloon.png
Binary files differ.
diff --git a/web/public/images/bluefade.jpg b/web/public/images/bluefade.jpg
Binary files differ.
diff --git a/web/public/images/close.gif b/web/public/images/close.gif
Binary files differ.
diff --git a/web/public/images/left-round.png b/web/public/images/left-round.png
Binary files differ.
diff --git a/web/public/images/loading.gif b/web/public/images/loading.gif
Binary files differ.
diff --git a/web/public/images/logo.png b/web/public/images/logo.png
Binary files differ.
diff --git a/web/public/images/logo_raw.xcf b/web/public/images/logo_raw.xcf
Binary files differ.
diff --git a/web/public/images/musicplayer.swf b/web/public/images/musicplayer.…
Binary files differ.
diff --git a/web/public/images/rails.png b/web/public/images/rails.png
Binary files differ.
diff --git a/web/public/images/right-round.png b/web/public/images/right-round.…
Binary files differ.
diff --git a/web/public/images/round_bot.png b/web/public/images/round_bot.png
Binary files differ.
diff --git a/web/public/images/round_top.png b/web/public/images/round_top.png
Binary files differ.
diff --git a/web/public/javascripts/FusionCharts.js b/web/public/javascripts/Fu…
@@ -0,0 +1,361 @@
+/**
+ * FusionCharts: Flash Player detection and Chart embedding.
+ * Version 1.2.3F ( 22 November 2008) - Specialized for FusionChartsFREE
+ * Checking Flash Version >=6 and adde…
+ * Version: 1.2.3 (1st September, 2008) - Added Fix for % and & characters, sc…
+ * Version: 1.2.2 (10th July, 2008) - Added Fix for % scaled dimensions, fixes…
+ * Version: 1.2.1 (21st December, 2007) - Added setting up Transparent/opaque …
+ * Version: 1.2 (1st November, 2007) - Added FORM fixes for IE
+ * Version: 1.1 (29th June, 2007) - Added Player detection, New conditional fi…
+ *
+ * Morphed from SWFObject (http://blog.deconcept.com/swfobject/) under MIT Lic…
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ */
+if(typeof infosoftglobal == "undefined") var infosoftglobal = new Object();
+if(typeof infosoftglobal.FusionChartsUtil == "undefined") infosoftglobal.Fusio…
+infosoftglobal.FusionCharts = function(swf, id, w, h, debugMode, registerWithJ…
+ if (!document.getElementById) { return; }
+
+ //Flag to see whether data has been set initially
+ this.initialDataSet = false;
+
+ //Create container objects
+ this.params = new Object();
+ this.variables = new Object();
+ this.attributes = new Array();
+
+ //Set attributes for the SWF
+ if(swf) { this.setAttribute('swf', swf); }
+ if(id) { this.setAttribute('id', id); }
+
+ w=w.toString().replace(/\%$/,"%25");
+ if(w) { this.setAttribute('width', w); }
+ h=h.toString().replace(/\%$/,"%25");
+ if(h) { this.setAttribute('height', h); }
+
+
+ //Set background color
+ if(c) { this.addParam('bgcolor', c); }
+
+ //Set Quality
+ this.addParam('quality', 'high');
+
+ //Add scripting access parameter
+ this.addParam('allowScriptAccess', 'always');
+
+ //Pass width and height to be appended as chartWidth and chartHeight
+ this.addVariable('chartWidth', w);
+ this.addVariable('chartHeight', h);
+
+ //Whether in debug mode
+ debugMode = debugMode ? debugMode : 0;
+ this.addVariable('debugMode', debugMode);
+ //Pass DOM ID to Chart
+ this.addVariable('DOMId', id);
+ //Whether to registed with JavaScript
+ registerWithJS = registerWithJS ? registerWithJS : 0;
+ this.addVariable('registerWithJS', registerWithJS);
+
+ //Scale Mode of chart
+ scaleMode = scaleMode ? scaleMode : 'noScale';
+ this.addVariable('scaleMode', scaleMode);
+
+ //Application Message Language
+ lang = lang ? lang : 'EN';
+ this.addVariable('lang', lang);
+
+ //Whether to auto detect and re-direct to Flash Player installation
+ this.detectFlashVersion = detectFlashVersion?detectFlashVersion:1;
+ this.autoInstallRedirect = autoInstallRedirect?autoInstallRedirect:1;
+
+ //Ger Flash Player version
+ this.installedVer = infosoftglobal.FusionChartsUtil.getPlayerVersion();
+
+ if (!window.opera && document.all && this.installedVer.major > 7) {
+ // Only add the onunload cleanup if the Flash Player version s…
+ infosoftglobal.FusionCharts.doPrepUnload = true;
+ }
+}
+
+infosoftglobal.FusionCharts.prototype = {
+ setAttribute: function(name, value){
+ this.attributes[name] = value;
+ },
+ getAttribute: function(name){
+ return this.attributes[name];
+ },
+ addParam: function(name, value){
+ this.params[name] = value;
+ },
+ getParams: function(){
+ return this.params;
+ },
+ addVariable: function(name, value){
+ this.variables[name] = value;
+ },
+ getVariable: function(name){
+ return this.variables[name];
+ },
+ getVariables: function(){
+ return this.variables;
+ },
+ getVariablePairs: function(){
+ var variablePairs = new Array();
+ var key;
+ var variables = this.getVariables();
+ for(key in variables){
+ variablePairs.push(key +"="+ variables[key]);
+ }
+ return variablePairs;
+ },
+ getSWFHTML: function() {
+ var swfNode = "";
+ if (navigator.plugins && navigator.mimeTypes && navigator.mime…
+ // netscape plugin architecture
+ swfNode = '<embed type="application/x-shockwave-flash"…
+ swfNode += ' id="'+ this.getAttribute('id') +'" name="…
+ var params = this.getParams();
+ for(var key in params){ swfNode += [key] +'="'+ param…
+ var pairs = this.getVariablePairs().join("&");
+ if (pairs.length > 0){ swfNode += 'flashvars="'+ pair…
+ swfNode += '/>';
+ } else { // PC IE
+ swfNode = '<object id="'+ this.getAttribute('id') +'" …
+ swfNode += '<param name="movie" value="'+ this.getAttr…
+ var params = this.getParams();
+ for(var key in params) {
+ swfNode += '<param name="'+ key +'" value="'+ params[…
+ }
+ var pairs = this.getVariablePairs().join("&"); …
+ if(pairs.length > 0) {swfNode += '<param name="flashva…
+ swfNode += "</object>";
+ }
+ return swfNode;
+ },
+ setDataURL: function(strDataURL){
+ //This method sets the data URL for the chart.
+ //If being set initially
+ if (this.initialDataSet==false){
+ this.addVariable('dataURL',strDataURL);
+ //Update flag
+ this.initialDataSet = true;
+ }else{
+ //Else, we update the chart data using External Interf…
+ //Get reference to chart object
+ var chartObj = infosoftglobal.FusionChartsUtil.getChar…
+
+ if (!chartObj.setDataURL)
+ {
+ __flash__addCallback(chartObj, "setDataURL");
+ }
+
+ chartObj.setDataURL(strDataURL);
+ }
+ },
+ //This function :
+ //fixes the double quoted attributes to single quotes
+ //Encodes all quotes inside attribute values
+ //Encodes % to %25 and & to %26;
+ encodeDataXML: function(strDataXML){
+
+ var regExpReservedCharacters=["\\$","\\+"];
+ var arrDQAtt=strDataXML.match(/=\s*\".*?\"/g);
+ if (arrDQAtt){
+ for(var i=0;i<arrDQAtt.length;i++){
+ var repStr=arrDQAtt[i].replace(/^=\s*\…
+ repStr=repStr.replace(/\'/g,"%26apos;"…
+ var strTo=strDataXML.indexOf(arrDQAtt[…
+ var repStrr="='"+repStr+"'";
+ var strStart=strDataXML.substring(0,st…
+ var strEnd=strDataXML.substring(strTo+…
+ var strDataXML=strStart+repStrr+strEnd;
+ }
+ }
+
+ strDataXML=strDataXML.replace(/\"/g,"%26quot;");
+ strDataXML=strDataXML.replace(/%(?![\da-f]{2}|[\da-f]{…
+ strDataXML=strDataXML.replace(/\&/g,"%26");
+
+ return strDataXML;
+
+ },
+ setDataXML: function(strDataXML){
+ //If being set initially
+ if (this.initialDataSet==false){
+ //This method sets the data XML for the chart INITIALL…
+ this.addVariable('dataXML',this.encodeDataXML(strDataX…
+ //Update flag
+ this.initialDataSet = true;
+ }else{
+ //Else, we update the chart data using External Interf…
+ //Get reference to chart object
+ var chartObj = infosoftglobal.FusionChartsUtil.getChar…
+ chartObj.setDataXML(strDataXML);
+ }
+ },
+ setTransparent: function(isTransparent){
+ //Sets chart to transparent mode when isTransparent is true (d…
+ //When no parameter is passed, we assume transparent to be tru…
+ if(typeof isTransparent=="undefined") {
+ isTransparent=true;
+ }
+ //Set the property
+ if(isTransparent)
+ this.addParam('WMode', 'transparent');
+ else
+ this.addParam('WMode', 'Opaque');
+ },
+
+ render: function(elementId){
+ //First check for installed version of Flash Player - we need …
+ if((this.detectFlashVersion==1) && (this.installedVer.major < …
+ if (this.autoInstallRedirect==1){
+ //If we can auto redirect to install the playe…
+ var installationConfirm = window.confirm("You …
+ if (installationConfirm){
+ window.location = "http://www.adobe.co…
+ }else{
+ return false;
+ }
+ }else{
+ //Else, do not take an action. It means the de…
+ //So, expect the developers to provide a cours…
+ //window.alert("You need Adobe Flash Player 8 …
+ return false;
+ }
+ }else{
+ //Render the chart
+ var n = (typeof elementId == 'string') ? document.getE…
+ n.innerHTML = this.getSWFHTML();
+
+ //Added <FORM> compatibility
+ //Check if it's added in Mozilla embed array or if alr…
+ if(!document.embeds[this.getAttribute('id')] && !windo…
+ window[this.getAttribute('id')]=document.getElem…
+ //or else document.forms[formName/formIndex][c…
+ return true;
+ }
+ }
+}
+
+/* ---- detection functions ---- */
+infosoftglobal.FusionChartsUtil.getPlayerVersion = function(){
+ var PlayerVersion = new infosoftglobal.PlayerVersion([0,0,0]);
+ if(navigator.plugins && navigator.mimeTypes.length){
+ var x = navigator.plugins["Shockwave Flash"];
+ if(x && x.description) {
+ PlayerVersion = new infosoftglobal.PlayerVersion(x.des…
+ }
+ }else if (navigator.userAgent && navigator.userAgent.indexOf("Windows …
+ //If Windows CE
+ var axo = 1;
+ var counter = 3;
+ while(axo) {
+ try {
+ counter++;
+ axo = new ActiveXObject("ShockwaveFlash.Shockw…
+ PlayerVersion = new infosoftglobal.PlayerVersi…
+ } catch (e) {
+ axo = null;
+ }
+ }
+ } else {
+ // Win IE (non mobile)
+ // Do minor version lookup in IE, but avoid Flash Player 6 cra…
+ try{
+ var axo = new ActiveXObject("ShockwaveFlash.ShockwaveF…
+ }catch(e){
+ try {
+ var axo = new ActiveXObject("ShockwaveFlash.Sh…
+ PlayerVersion = new infosoftglobal.PlayerVersi…
+ axo.AllowScriptAccess = "always"; // error if …
+ } catch(e) {
+ if (PlayerVersion.major == 6) {
+ return PlayerVersion;
+ }
+ }
+ try {
+ axo = new ActiveXObject("ShockwaveFlash.Shockw…
+ } catch(e) {}
+ }
+ if (axo != null) {
+ PlayerVersion = new infosoftglobal.PlayerVersion(axo.G…
+ }
+ }
+ return PlayerVersion;
+}
+infosoftglobal.PlayerVersion = function(arrVersion){
+ this.major = arrVersion[0] != null ? parseInt(arrVersion[0]) : 0;
+ this.minor = arrVersion[1] != null ? parseInt(arrVersion[1]) : 0;
+ this.rev = arrVersion[2] != null ? parseInt(arrVersion[2]) : 0;
+}
+// ------------ Fix for Out of Memory Bug in IE in FP9 ---------------//
+/* Fix for video streaming bug */
+infosoftglobal.FusionChartsUtil.cleanupSWFs = function() {
+ var objects = document.getElementsByTagName("OBJECT");
+ for (var i = objects.length - 1; i >= 0; i--) {
+ objects[i].style.display = 'none';
+ for (var x in objects[i]) {
+ if (typeof objects[i][x] == 'function') {
+ objects[i][x] = function(){};
+ }
+ }
+ }
+}
+// Fixes bug in fp9
+if (infosoftglobal.FusionCharts.doPrepUnload) {
+ if (!infosoftglobal.unloadSet) {
+ infosoftglobal.FusionChartsUtil.prepUnload = function() {
+ __flash_unloadHandler = function(){};
+ __flash_savedUnloadHandler = function(){};
+ window.attachEvent("onunload", infosoftglobal.FusionCh…
+ }
+ window.attachEvent("onbeforeunload", infosoftglobal.FusionChar…
+ infosoftglobal.unloadSet = true;
+ }
+}
+/* Add document.getElementById if needed (mobile IE < 5) */
+if (!document.getElementById && document.all) { document.getElementById = func…
+/* Add Array.push if needed (ie5) */
+if (Array.prototype.push == null) { Array.prototype.push = function(item) { th…
+
+/* Function to return Flash Object from ID */
+infosoftglobal.FusionChartsUtil.getChartObject = function(id)
+{
+ var chartRef=null;
+ if (navigator.appName.indexOf("Microsoft Internet")==-1) {
+ if (document.embeds && document.embeds[id])
+ chartRef = document.embeds[id];
+ else
+ chartRef = window.document[id];
+ }
+ else {
+ chartRef = window[id];
+ }
+ if (!chartRef)
+ chartRef = document.getElementById(id);
+
+ return chartRef;
+}
+/*
+ Function to update chart's data at client side (FOR FusionCharts vFREE and 2.x
+*/
+infosoftglobal.FusionChartsUtil.updateChartXML = function(chartId, strXML){
+ //Get reference to chart object
+ var chartObj = infosoftglobal.FusionChartsUtil.getChartObject(chartId)…
+ //Set dataURL to null
+ chartObj.SetVariable("_root.dataURL","");
+ //Set the flag
+ chartObj.SetVariable("_root.isNewData","1");
+ //Set the actual data
+ chartObj.SetVariable("_root.newData",strXML);
+ //Go to the required frame
+ chartObj.TGotoLabel("/", "JavaScriptHandler");
+}
+
+
+/* Aliases for easy usage */
+var getChartFromId = infosoftglobal.FusionChartsUtil.getChartObject;
+var updateChartXML = infosoftglobal.FusionChartsUtil.updateChartXML;
+var FusionCharts = infosoftglobal.FusionCharts;
+\ No newline at end of file
diff --git a/web/public/javascripts/application.js b/web/public/javascripts/app…
@@ -0,0 +1,2 @@
+// Place your application-specific JavaScript functions and classes here
+// This file is automatically included by javascript_include_tag :defaults
diff --git a/web/public/javascripts/controls.js b/web/public/javascripts/contro…
@@ -0,0 +1,965 @@
+// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
+
+// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.ac…
+// (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+// (c) 2005-2009 Jon Tirsen (http://www.tirsen.com)
+// Contributors:
+// Richard Livsey
+// Rahul Bhargava
+// Rob Wills
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style lic…
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// Autocompleter.Base handles all the autocompletion functionality
+// that's independent of the data source for autocompletion. This
+// includes drawing the autocompletion menu, observing keyboard
+// and mouse events, and similar.
+//
+// Specific autocompleters need to provide, at the very least,
+// a getUpdatedChoices function that will be invoked every time
+// the text inside the monitored textbox changes. This method
+// should get the text for which to provide autocompletion by
+// invoking this.getToken(), NOT by directly accessing
+// this.element.value. This is to allow incremental tokenized
+// autocompletion. Specific auto-completion logic (AJAX, etc)
+// belongs in getUpdatedChoices.
+//
+// Tokenized incremental autocompletion is enabled automatically
+// when an autocompleter is instantiated with the 'tokens' option
+// in the options parameter, e.g.:
+// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
+// will incrementally autocomplete with a comma as the token.
+// Additionally, ',' in the above example can be replaced with
+// a token array, e.g. { tokens: [',', '\n'] } which
+// enables autocompletion on multiple tokens. This is most
+// useful when one of the tokens is \n (a newline), as it
+// allows smart autocompletion after linebreaks.
+
+if(typeof Effect == 'undefined')
+ throw("controls.js requires including script.aculo.us' effects.js library");
+
+var Autocompleter = { };
+Autocompleter.Base = Class.create({
+ baseInitialize: function(element, update, options) {
+ element = $(element);
+ this.element = element;
+ this.update = $(update);
+ this.hasFocus = false;
+ this.changed = false;
+ this.active = false;
+ this.index = 0;
+ this.entryCount = 0;
+ this.oldElementValue = this.element.value;
+
+ if(this.setOptions)
+ this.setOptions(options);
+ else
+ this.options = options || { };
+
+ this.options.paramName = this.options.paramName || this.element.name;
+ this.options.tokens = this.options.tokens || [];
+ this.options.frequency = this.options.frequency || 0.4;
+ this.options.minChars = this.options.minChars || 1;
+ this.options.onShow = this.options.onShow ||
+ function(element, update){
+ if(!update.style.position || update.style.position=='absolute') {
+ update.style.position = 'absolute';
+ Position.clone(element, update, {
+ setHeight: false,
+ offsetTop: element.offsetHeight
+ });
+ }
+ Effect.Appear(update,{duration:0.15});
+ };
+ this.options.onHide = this.options.onHide ||
+ function(element, update){ new Effect.Fade(update,{duration:0.15}) };
+
+ if(typeof(this.options.tokens) == 'string')
+ this.options.tokens = new Array(this.options.tokens);
+ // Force carriage returns as token delimiters anyway
+ if (!this.options.tokens.include('\n'))
+ this.options.tokens.push('\n');
+
+ this.observer = null;
+
+ this.element.setAttribute('autocomplete','off');
+
+ Element.hide(this.update);
+
+ Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
+ Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener…
+ },
+
+ show: function() {
+ if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(t…
+ if(!this.iefix &&
+ (Prototype.Browser.IE) &&
+ (Element.getStyle(this.update, 'position')=='absolute')) {
+ new Insertion.After(this.update,
+ '<iframe id="' + this.update.id + '_iefix" '+
+ 'style="display:none;position:absolute;filter:progid:DXImageTransform.M…
+ 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
+ this.iefix = $(this.update.id+'_iefix');
+ }
+ if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+ },
+
+ fixIEOverlapping: function() {
+ Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height…
+ this.iefix.style.zIndex = 1;
+ this.update.style.zIndex = 2;
+ Element.show(this.iefix);
+ },
+
+ hide: function() {
+ this.stopIndicator();
+ if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(t…
+ if(this.iefix) Element.hide(this.iefix);
+ },
+
+ startIndicator: function() {
+ if(this.options.indicator) Element.show(this.options.indicator);
+ },
+
+ stopIndicator: function() {
+ if(this.options.indicator) Element.hide(this.options.indicator);
+ },
+
+ onKeyPress: function(event) {
+ if(this.active)
+ switch(event.keyCode) {
+ case Event.KEY_TAB:
+ case Event.KEY_RETURN:
+ this.selectEntry();
+ Event.stop(event);
+ case Event.KEY_ESC:
+ this.hide();
+ this.active = false;
+ Event.stop(event);
+ return;
+ case Event.KEY_LEFT:
+ case Event.KEY_RIGHT:
+ return;
+ case Event.KEY_UP:
+ this.markPrevious();
+ this.render();
+ Event.stop(event);
+ return;
+ case Event.KEY_DOWN:
+ this.markNext();
+ this.render();
+ Event.stop(event);
+ return;
+ }
+ else
+ if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
+ (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
+
+ this.changed = true;
+ this.hasFocus = true;
+
+ if(this.observer) clearTimeout(this.observer);
+ this.observer =
+ setTimeout(this.onObserverEvent.bind(this), this.options.frequency*100…
+ },
+
+ activate: function() {
+ this.changed = false;
+ this.hasFocus = true;
+ this.getUpdatedChoices();
+ },
+
+ onHover: function(event) {
+ var element = Event.findElement(event, 'LI');
+ if(this.index != element.autocompleteIndex)
+ {
+ this.index = element.autocompleteIndex;
+ this.render();
+ }
+ Event.stop(event);
+ },
+
+ onClick: function(event) {
+ var element = Event.findElement(event, 'LI');
+ this.index = element.autocompleteIndex;
+ this.selectEntry();
+ this.hide();
+ },
+
+ onBlur: function(event) {
+ // needed to make click events working
+ setTimeout(this.hide.bind(this), 250);
+ this.hasFocus = false;
+ this.active = false;
+ },
+
+ render: function() {
+ if(this.entryCount > 0) {
+ for (var i = 0; i < this.entryCount; i++)
+ this.index==i ?
+ Element.addClassName(this.getEntry(i),"selected") :
+ Element.removeClassName(this.getEntry(i),"selected");
+ if(this.hasFocus) {
+ this.show();
+ this.active = true;
+ }
+ } else {
+ this.active = false;
+ this.hide();
+ }
+ },
+
+ markPrevious: function() {
+ if(this.index > 0) this.index--;
+ else this.index = this.entryCount-1;
+ this.getEntry(this.index).scrollIntoView(true);
+ },
+
+ markNext: function() {
+ if(this.index < this.entryCount-1) this.index++;
+ else this.index = 0;
+ this.getEntry(this.index).scrollIntoView(false);
+ },
+
+ getEntry: function(index) {
+ return this.update.firstChild.childNodes[index];
+ },
+
+ getCurrentEntry: function() {
+ return this.getEntry(this.index);
+ },
+
+ selectEntry: function() {
+ this.active = false;
+ this.updateElement(this.getCurrentEntry());
+ },
+
+ updateElement: function(selectedElement) {
+ if (this.options.updateElement) {
+ this.options.updateElement(selectedElement);
+ return;
+ }
+ var value = '';
+ if (this.options.select) {
+ var nodes = $(selectedElement).select('.' + this.options.select) || [];
+ if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.optio…
+ } else
+ value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
+
+ var bounds = this.getTokenBounds();
+ if (bounds[0] != -1) {
+ var newValue = this.element.value.substr(0, bounds[0]);
+ var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
+ if (whitespace)
+ newValue += whitespace[0];
+ this.element.value = newValue + value + this.element.value.substr(bounds…
+ } else {
+ this.element.value = value;
+ }
+ this.oldElementValue = this.element.value;
+ this.element.focus();
+
+ if (this.options.afterUpdateElement)
+ this.options.afterUpdateElement(this.element, selectedElement);
+ },
+
+ updateChoices: function(choices) {
+ if(!this.changed && this.hasFocus) {
+ this.update.innerHTML = choices;
+ Element.cleanWhitespace(this.update);
+ Element.cleanWhitespace(this.update.down());
+
+ if(this.update.firstChild && this.update.down().childNodes) {
+ this.entryCount =
+ this.update.down().childNodes.length;
+ for (var i = 0; i < this.entryCount; i++) {
+ var entry = this.getEntry(i);
+ entry.autocompleteIndex = i;
+ this.addObservers(entry);
+ }
+ } else {
+ this.entryCount = 0;
+ }
+
+ this.stopIndicator();
+ this.index = 0;
+
+ if(this.entryCount==1 && this.options.autoSelect) {
+ this.selectEntry();
+ this.hide();
+ } else {
+ this.render();
+ }
+ }
+ },
+
+ addObservers: function(element) {
+ Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)…
+ Event.observe(element, "click", this.onClick.bindAsEventListener(this));
+ },
+
+ onObserverEvent: function() {
+ this.changed = false;
+ this.tokenBounds = null;
+ if(this.getToken().length>=this.options.minChars) {
+ this.getUpdatedChoices();
+ } else {
+ this.active = false;
+ this.hide();
+ }
+ this.oldElementValue = this.element.value;
+ },
+
+ getToken: function() {
+ var bounds = this.getTokenBounds();
+ return this.element.value.substring(bounds[0], bounds[1]).strip();
+ },
+
+ getTokenBounds: function() {
+ if (null != this.tokenBounds) return this.tokenBounds;
+ var value = this.element.value;
+ if (value.strip().empty()) return [-1, 0];
+ var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementVa…
+ var offset = (diff == this.oldElementValue.length ? 1 : 0);
+ var prevTokenPos = -1, nextTokenPos = value.length;
+ var tp;
+ for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
+ tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
+ if (tp > prevTokenPos) prevTokenPos = tp;
+ tp = value.indexOf(this.options.tokens[index], diff + offset);
+ if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
+ }
+ return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
+ }
+});
+
+Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(n…
+ var boundary = Math.min(newS.length, oldS.length);
+ for (var index = 0; index < boundary; ++index)
+ if (newS[index] != oldS[index])
+ return index;
+ return boundary;
+};
+
+Ajax.Autocompleter = Class.create(Autocompleter.Base, {
+ initialize: function(element, update, url, options) {
+ this.baseInitialize(element, update, options);
+ this.options.asynchronous = true;
+ this.options.onComplete = this.onComplete.bind(this);
+ this.options.defaultParams = this.options.parameters || null;
+ this.url = url;
+ },
+
+ getUpdatedChoices: function() {
+ this.startIndicator();
+
+ var entry = encodeURIComponent(this.options.paramName) + '=' +
+ encodeURIComponent(this.getToken());
+
+ this.options.parameters = this.options.callback ?
+ this.options.callback(this.element, entry) : entry;
+
+ if(this.options.defaultParams)
+ this.options.parameters += '&' + this.options.defaultParams;
+
+ new Ajax.Request(this.url, this.options);
+ },
+
+ onComplete: function(request) {
+ this.updateChoices(request.responseText);
+ }
+});
+
+// The local array autocompleter. Used when you'd prefer to
+// inject an array of autocompletion options into the page, rather
+// than sending out Ajax queries, which can be quite slow sometimes.
+//
+// The constructor takes four parameters. The first two are, as usual,
+// the id of the monitored textbox, and id of the autocompletion menu.
+// The third is the array you want to autocomplete from, and the fourth
+// is the options block.
+//
+// Extra local autocompletion options:
+// - choices - How many autocompletion choices to offer
+//
+// - partialSearch - If false, the autocompleter will match entered
+// text only at the beginning of strings in the
+// autocomplete array. Defaults to true, which will
+// match text at the beginning of any *word* in the
+// strings in the autocomplete array. If you want to
+// search anywhere in the string, additionally set
+// the option fullSearch to true (default: off).
+//
+// - fullSsearch - Search anywhere in autocomplete array strings.
+//
+// - partialChars - How many characters to enter before triggering
+// a partial match (unlike minChars, which defines
+// how many characters are required to do any match
+// at all). Defaults to 2.
+//
+// - ignoreCase - Whether to ignore case when autocompleting.
+// Defaults to true.
+//
+// It's possible to pass in a custom function as the 'selector'
+// option, if you prefer to write your own autocompletion logic.
+// In that case, the other options above will not apply unless
+// you support them.
+
+Autocompleter.Local = Class.create(Autocompleter.Base, {
+ initialize: function(element, update, array, options) {
+ this.baseInitialize(element, update, options);
+ this.options.array = array;
+ },
+
+ getUpdatedChoices: function() {
+ this.updateChoices(this.options.selector(this));
+ },
+
+ setOptions: function(options) {
+ this.options = Object.extend({
+ choices: 10,
+ partialSearch: true,
+ partialChars: 2,
+ ignoreCase: true,
+ fullSearch: false,
+ selector: function(instance) {
+ var ret = []; // Beginning matches
+ var partial = []; // Inside matches
+ var entry = instance.getToken();
+ var count = 0;
+
+ for (var i = 0; i < instance.options.array.length &&
+ ret.length < instance.options.choices ; i++) {
+
+ var elem = instance.options.array[i];
+ var foundPos = instance.options.ignoreCase ?
+ elem.toLowerCase().indexOf(entry.toLowerCase()) :
+ elem.indexOf(entry);
+
+ while (foundPos != -1) {
+ if (foundPos == 0 && elem.length != entry.length) {
+ ret.push("<li><strong>" + elem.substr(0, entry.length) + "</stro…
+ elem.substr(entry.length) + "</li>");
+ break;
+ } else if (entry.length >= instance.options.partialChars &&
+ instance.options.partialSearch && foundPos != -1) {
+ if (instance.options.fullSearch || /\s/.test(elem.substr(foundPo…
+ partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
+ elem.substr(foundPos, entry.length) + "</strong>" + elem.sub…
+ foundPos + entry.length) + "</li>");
+ break;
+ }
+ }
+
+ foundPos = instance.options.ignoreCase ?
+ elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
+ elem.indexOf(entry, foundPos + 1);
+
+ }
+ }
+ if (partial.length)
+ ret = ret.concat(partial.slice(0, instance.options.choices - ret.len…
+ return "<ul>" + ret.join('') + "</ul>";
+ }
+ }, options || { });
+ }
+});
+
+// AJAX in-place editor and collection editor
+// Full rewrite by Christophe Porteneuve <[email protected]> (April 2007).
+
+// Use this if you notice weird scrolling problems on some browsers,
+// the DOM might be a bit confused when this gets called so do this
+// waits 1 ms (with setTimeout) until it does the activation
+Field.scrollFreeActivate = function(field) {
+ setTimeout(function() {
+ Field.activate(field);
+ }, 1);
+};
+
+Ajax.InPlaceEditor = Class.create({
+ initialize: function(element, url, options) {
+ this.url = url;
+ this.element = element = $(element);
+ this.prepareOptions();
+ this._controls = { };
+ arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!…
+ Object.extend(this.options, options || { });
+ if (!this.options.formId && this.element.id) {
+ this.options.formId = this.element.id + '-inplaceeditor';
+ if ($(this.options.formId))
+ this.options.formId = '';
+ }
+ if (this.options.externalControl)
+ this.options.externalControl = $(this.options.externalControl);
+ if (!this.options.externalControl)
+ this.options.externalControlOnly = false;
+ this._originalBackground = this.element.getStyle('background-color') || 't…
+ this.element.title = this.options.clickToEditText;
+ this._boundCancelHandler = this.handleFormCancellation.bind(this);
+ this._boundComplete = (this.options.onComplete || Prototype.emptyFunction)…
+ this._boundFailureHandler = this.handleAJAXFailure.bind(this);
+ this._boundSubmitHandler = this.handleFormSubmission.bind(this);
+ this._boundWrapperHandler = this.wrapUp.bind(this);
+ this.registerListeners();
+ },
+ checkForEscapeOrReturn: function(e) {
+ if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
+ if (Event.KEY_ESC == e.keyCode)
+ this.handleFormCancellation(e);
+ else if (Event.KEY_RETURN == e.keyCode)
+ this.handleFormSubmission(e);
+ },
+ createControl: function(mode, handler, extraClasses) {
+ var control = this.options[mode + 'Control'];
+ var text = this.options[mode + 'Text'];
+ if ('button' == control) {
+ var btn = document.createElement('input');
+ btn.type = 'submit';
+ btn.value = text;
+ btn.className = 'editor_' + mode + '_button';
+ if ('cancel' == mode)
+ btn.onclick = this._boundCancelHandler;
+ this._form.appendChild(btn);
+ this._controls[mode] = btn;
+ } else if ('link' == control) {
+ var link = document.createElement('a');
+ link.href = '#';
+ link.appendChild(document.createTextNode(text));
+ link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._bound…
+ link.className = 'editor_' + mode + '_link';
+ if (extraClasses)
+ link.className += ' ' + extraClasses;
+ this._form.appendChild(link);
+ this._controls[mode] = link;
+ }
+ },
+ createEditField: function() {
+ var text = (this.options.loadTextURL ? this.options.loadingText : this.get…
+ var fld;
+ if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
+ fld = document.createElement('input');
+ fld.type = 'text';
+ var size = this.options.size || this.options.cols || 0;
+ if (0 < size) fld.size = size;
+ } else {
+ fld = document.createElement('textarea');
+ fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.option…
+ fld.cols = this.options.cols || 40;
+ }
+ fld.name = this.options.paramName;
+ fld.value = text; // No HTML breaks conversion anymore
+ fld.className = 'editor_field';
+ if (this.options.submitOnBlur)
+ fld.onblur = this._boundSubmitHandler;
+ this._controls.editor = fld;
+ if (this.options.loadTextURL)
+ this.loadExternalText();
+ this._form.appendChild(this._controls.editor);
+ },
+ createForm: function() {
+ var ipe = this;
+ function addText(mode, condition) {
+ var text = ipe.options['text' + mode + 'Controls'];
+ if (!text || condition === false) return;
+ ipe._form.appendChild(document.createTextNode(text));
+ };
+ this._form = $(document.createElement('form'));
+ this._form.id = this.options.formId;
+ this._form.addClassName(this.options.formClassName);
+ this._form.onsubmit = this._boundSubmitHandler;
+ this.createEditField();
+ if ('textarea' == this._controls.editor.tagName.toLowerCase())
+ this._form.appendChild(document.createElement('br'));
+ if (this.options.onFormCustomization)
+ this.options.onFormCustomization(this, this._form);
+ addText('Before', this.options.okControl || this.options.cancelControl);
+ this.createControl('ok', this._boundSubmitHandler);
+ addText('Between', this.options.okControl && this.options.cancelControl);
+ this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
+ addText('After', this.options.okControl || this.options.cancelControl);
+ },
+ destroy: function() {
+ if (this._oldInnerHTML)
+ this.element.innerHTML = this._oldInnerHTML;
+ this.leaveEditMode();
+ this.unregisterListeners();
+ },
+ enterEditMode: function(e) {
+ if (this._saving || this._editing) return;
+ this._editing = true;
+ this.triggerCallback('onEnterEditMode');
+ if (this.options.externalControl)
+ this.options.externalControl.hide();
+ this.element.hide();
+ this.createForm();
+ this.element.parentNode.insertBefore(this._form, this.element);
+ if (!this.options.loadTextURL)
+ this.postProcessEditField();
+ if (e) Event.stop(e);
+ },
+ enterHover: function(e) {
+ if (this.options.hoverClassName)
+ this.element.addClassName(this.options.hoverClassName);
+ if (this._saving) return;
+ this.triggerCallback('onEnterHover');
+ },
+ getText: function() {
+ return this.element.innerHTML.unescapeHTML();
+ },
+ handleAJAXFailure: function(transport) {
+ this.triggerCallback('onFailure', transport);
+ if (this._oldInnerHTML) {
+ this.element.innerHTML = this._oldInnerHTML;
+ this._oldInnerHTML = null;
+ }
+ },
+ handleFormCancellation: function(e) {
+ this.wrapUp();
+ if (e) Event.stop(e);
+ },
+ handleFormSubmission: function(e) {
+ var form = this._form;
+ var value = $F(this._controls.editor);
+ this.prepareSubmission();
+ var params = this.options.callback(form, value) || '';
+ if (Object.isString(params))
+ params = params.toQueryParams();
+ params.editorId = this.element.id;
+ if (this.options.htmlResponse) {
+ var options = Object.extend({ evalScripts: true }, this.options.ajaxOpti…
+ Object.extend(options, {
+ parameters: params,
+ onComplete: this._boundWrapperHandler,
+ onFailure: this._boundFailureHandler
+ });
+ new Ajax.Updater({ success: this.element }, this.url, options);
+ } else {
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: params,
+ onComplete: this._boundWrapperHandler,
+ onFailure: this._boundFailureHandler
+ });
+ new Ajax.Request(this.url, options);
+ }
+ if (e) Event.stop(e);
+ },
+ leaveEditMode: function() {
+ this.element.removeClassName(this.options.savingClassName);
+ this.removeForm();
+ this.leaveHover();
+ this.element.style.backgroundColor = this._originalBackground;
+ this.element.show();
+ if (this.options.externalControl)
+ this.options.externalControl.show();
+ this._saving = false;
+ this._editing = false;
+ this._oldInnerHTML = null;
+ this.triggerCallback('onLeaveEditMode');
+ },
+ leaveHover: function(e) {
+ if (this.options.hoverClassName)
+ this.element.removeClassName(this.options.hoverClassName);
+ if (this._saving) return;
+ this.triggerCallback('onLeaveHover');
+ },
+ loadExternalText: function() {
+ this._form.addClassName(this.options.loadingClassName);
+ this._controls.editor.disabled = true;
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
+ onComplete: Prototype.emptyFunction,
+ onSuccess: function(transport) {
+ this._form.removeClassName(this.options.loadingClassName);
+ var text = transport.responseText;
+ if (this.options.stripLoadedTextTags)
+ text = text.stripTags();
+ this._controls.editor.value = text;
+ this._controls.editor.disabled = false;
+ this.postProcessEditField();
+ }.bind(this),
+ onFailure: this._boundFailureHandler
+ });
+ new Ajax.Request(this.options.loadTextURL, options);
+ },
+ postProcessEditField: function() {
+ var fpc = this.options.fieldPostCreation;
+ if (fpc)
+ $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
+ },
+ prepareOptions: function() {
+ this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
+ Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
+ [this._extraDefaultOptions].flatten().compact().each(function(defs) {
+ Object.extend(this.options, defs);
+ }.bind(this));
+ },
+ prepareSubmission: function() {
+ this._saving = true;
+ this.removeForm();
+ this.leaveHover();
+ this.showSaving();
+ },
+ registerListeners: function() {
+ this._listeners = { };
+ var listener;
+ $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
+ listener = this[pair.value].bind(this);
+ this._listeners[pair.key] = listener;
+ if (!this.options.externalControlOnly)
+ this.element.observe(pair.key, listener);
+ if (this.options.externalControl)
+ this.options.externalControl.observe(pair.key, listener);
+ }.bind(this));
+ },
+ removeForm: function() {
+ if (!this._form) return;
+ this._form.remove();
+ this._form = null;
+ this._controls = { };
+ },
+ showSaving: function() {
+ this._oldInnerHTML = this.element.innerHTML;
+ this.element.innerHTML = this.options.savingText;
+ this.element.addClassName(this.options.savingClassName);
+ this.element.style.backgroundColor = this._originalBackground;
+ this.element.show();
+ },
+ triggerCallback: function(cbName, arg) {
+ if ('function' == typeof this.options[cbName]) {
+ this.options[cbName](this, arg);
+ }
+ },
+ unregisterListeners: function() {
+ $H(this._listeners).each(function(pair) {
+ if (!this.options.externalControlOnly)
+ this.element.stopObserving(pair.key, pair.value);
+ if (this.options.externalControl)
+ this.options.externalControl.stopObserving(pair.key, pair.value);
+ }.bind(this));
+ },
+ wrapUp: function(transport) {
+ this.leaveEditMode();
+ // Can't use triggerCallback due to backward compatibility: requires
+ // binding + direct element
+ this._boundComplete(transport, this.element);
+ }
+});
+
+Object.extend(Ajax.InPlaceEditor.prototype, {
+ dispose: Ajax.InPlaceEditor.prototype.destroy
+});
+
+Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
+ initialize: function($super, element, url, options) {
+ this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
+ $super(element, url, options);
+ },
+
+ createEditField: function() {
+ var list = document.createElement('select');
+ list.name = this.options.paramName;
+ list.size = 1;
+ this._controls.editor = list;
+ this._collection = this.options.collection || [];
+ if (this.options.loadCollectionURL)
+ this.loadCollection();
+ else
+ this.checkForExternalText();
+ this._form.appendChild(this._controls.editor);
+ },
+
+ loadCollection: function() {
+ this._form.addClassName(this.options.loadingClassName);
+ this.showLoadingText(this.options.loadingCollectionText);
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
+ onComplete: Prototype.emptyFunction,
+ onSuccess: function(transport) {
+ var js = transport.responseText.strip();
+ if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
+ throw('Server returned an invalid collection representation.');
+ this._collection = eval(js);
+ this.checkForExternalText();
+ }.bind(this),
+ onFailure: this.onFailure
+ });
+ new Ajax.Request(this.options.loadCollectionURL, options);
+ },
+
+ showLoadingText: function(text) {
+ this._controls.editor.disabled = true;
+ var tempOption = this._controls.editor.firstChild;
+ if (!tempOption) {
+ tempOption = document.createElement('option');
+ tempOption.value = '';
+ this._controls.editor.appendChild(tempOption);
+ tempOption.selected = true;
+ }
+ tempOption.update((text || '').stripScripts().stripTags());
+ },
+
+ checkForExternalText: function() {
+ this._text = this.getText();
+ if (this.options.loadTextURL)
+ this.loadExternalText();
+ else
+ this.buildOptionList();
+ },
+
+ loadExternalText: function() {
+ this.showLoadingText(this.options.loadingText);
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
+ onComplete: Prototype.emptyFunction,
+ onSuccess: function(transport) {
+ this._text = transport.responseText.strip();
+ this.buildOptionList();
+ }.bind(this),
+ onFailure: this.onFailure
+ });
+ new Ajax.Request(this.options.loadTextURL, options);
+ },
+
+ buildOptionList: function() {
+ this._form.removeClassName(this.options.loadingClassName);
+ this._collection = this._collection.map(function(entry) {
+ return 2 === entry.length ? entry : [entry, entry].flatten();
+ });
+ var marker = ('value' in this.options) ? this.options.value : this._text;
+ var textFound = this._collection.any(function(entry) {
+ return entry[0] == marker;
+ }.bind(this));
+ this._controls.editor.update('');
+ var option;
+ this._collection.each(function(entry, index) {
+ option = document.createElement('option');
+ option.value = entry[0];
+ option.selected = textFound ? entry[0] == marker : 0 == index;
+ option.appendChild(document.createTextNode(entry[1]));
+ this._controls.editor.appendChild(option);
+ }.bind(this));
+ this._controls.editor.disabled = false;
+ Field.scrollFreeActivate(this._controls.editor);
+ }
+});
+
+//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
+//**** This only exists for a while, in order to let ****
+//**** users adapt to the new API. Read up on the new ****
+//**** API and convert your code to it ASAP! ****
+
+Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(o…
+ if (!options) return;
+ function fallback(name, expr) {
+ if (name in options || expr === undefined) return;
+ options[name] = expr;
+ };
+ fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButt…
+ options.cancelLink == options.cancelButton == false ? false : undefined)));
+ fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button…
+ options.okLink == options.okButton == false ? false : undefined)));
+ fallback('highlightColor', options.highlightcolor);
+ fallback('highlightEndColor', options.highlightendcolor);
+};
+
+Object.extend(Ajax.InPlaceEditor, {
+ DefaultOptions: {
+ ajaxOptions: { },
+ autoRows: 3, // Use when multi-line w/ rows…
+ cancelControl: 'link', // 'link'|'button'|false
+ cancelText: 'cancel',
+ clickToEditText: 'Click to edit',
+ externalControl: null, // id|elt
+ externalControlOnly: false,
+ fieldPostCreation: 'activate', // 'activate'|'focus'|false
+ formClassName: 'inplaceeditor-form',
+ formId: null, // id|elt
+ highlightColor: '#ffff99',
+ highlightEndColor: '#ffffff',
+ hoverClassName: '',
+ htmlResponse: true,
+ loadingClassName: 'inplaceeditor-loading',
+ loadingText: 'Loading...',
+ okControl: 'button', // 'link'|'button'|false
+ okText: 'ok',
+ paramName: 'value',
+ rows: 1, // If 1 and multi-line, uses a…
+ savingClassName: 'inplaceeditor-saving',
+ savingText: 'Saving...',
+ size: 0,
+ stripLoadedTextTags: false,
+ submitOnBlur: false,
+ textAfterControls: '',
+ textBeforeControls: '',
+ textBetweenControls: ''
+ },
+ DefaultCallbacks: {
+ callback: function(form) {
+ return Form.serialize(form);
+ },
+ onComplete: function(transport, element) {
+ // For backward compatibility, this one is bound to the IPE, and passes
+ // the element directly. It was too often customized, so we don't break…
+ new Effect.Highlight(element, {
+ startcolor: this.options.highlightColor, keepBackgroundImage: true });
+ },
+ onEnterEditMode: null,
+ onEnterHover: function(ipe) {
+ ipe.element.style.backgroundColor = ipe.options.highlightColor;
+ if (ipe._effect)
+ ipe._effect.cancel();
+ },
+ onFailure: function(transport, ipe) {
+ alert('Error communication with the server: ' + transport.responseText.s…
+ },
+ onFormCustomization: null, // Takes the IPE and its generated form, after …
+ onLeaveEditMode: null,
+ onLeaveHover: function(ipe) {
+ ipe._effect = new Effect.Highlight(ipe.element, {
+ startcolor: ipe.options.highlightColor, endcolor: ipe.options.highligh…
+ restorecolor: ipe._originalBackground, keepBackgroundImage: true
+ });
+ }
+ },
+ Listeners: {
+ click: 'enterEditMode',
+ keydown: 'checkForEscapeOrReturn',
+ mouseover: 'enterHover',
+ mouseout: 'leaveHover'
+ }
+});
+
+Ajax.InPlaceCollectionEditor.DefaultOptions = {
+ loadingCollectionText: 'Loading options...'
+};
+
+// Delayed observer, like Form.Element.Observer,
+// but waits for delay after last key input
+// Ideal for live-search fields
+
+Form.Element.DelayedObserver = Class.create({
+ initialize: function(element, delay, callback) {
+ this.delay = delay || 0.5;
+ this.element = $(element);
+ this.callback = callback;
+ this.timer = null;
+ this.lastValue = $F(this.element);
+ Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListene…
+ },
+ delayedListener: function(event) {
+ if(this.lastValue == $F(this.element)) return;
+ if(this.timer) clearTimeout(this.timer);
+ this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
+ this.lastValue = $F(this.element);
+ },
+ onTimerEvent: function() {
+ this.timer = null;
+ this.callback(this.element, $F(this.element));
+ }
+});
+\ No newline at end of file
diff --git a/web/public/javascripts/custom.js b/web/public/javascripts/custom.js
diff --git a/web/public/javascripts/dragdrop.js b/web/public/javascripts/dragdr…
@@ -0,0 +1,974 @@
+// script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
+
+// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.ac…
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style lic…
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+if(Object.isUndefined(Effect))
+ throw("dragdrop.js requires including script.aculo.us' effects.js library");
+
+var Droppables = {
+ drops: [],
+
+ remove: function(element) {
+ this.drops = this.drops.reject(function(d) { return d.element==$(element) …
+ },
+
+ add: function(element) {
+ element = $(element);
+ var options = Object.extend({
+ greedy: true,
+ hoverclass: null,
+ tree: false
+ }, arguments[1] || { });
+
+ // cache containers
+ if(options.containment) {
+ options._containers = [];
+ var containment = options.containment;
+ if(Object.isArray(containment)) {
+ containment.each( function(c) { options._containers.push($(c)) });
+ } else {
+ options._containers.push($(containment));
+ }
+ }
+
+ if(options.accept) options.accept = [options.accept].flatten();
+
+ Element.makePositioned(element); // fix IE
+ options.element = element;
+
+ this.drops.push(options);
+ },
+
+ findDeepestChild: function(drops) {
+ deepest = drops[0];
+
+ for (i = 1; i < drops.length; ++i)
+ if (Element.isParent(drops[i].element, deepest.element))
+ deepest = drops[i];
+
+ return deepest;
+ },
+
+ isContained: function(element, drop) {
+ var containmentNode;
+ if(drop.tree) {
+ containmentNode = element.treeNode;
+ } else {
+ containmentNode = element.parentNode;
+ }
+ return drop._containers.detect(function(c) { return containmentNode == c }…
+ },
+
+ isAffected: function(point, element, drop) {
+ return (
+ (drop.element!=element) &&
+ ((!drop._containers) ||
+ this.isContained(element, drop)) &&
+ ((!drop.accept) ||
+ (Element.classNames(element).detect(
+ function(v) { return drop.accept.include(v) } ) )) &&
+ Position.within(drop.element, point[0], point[1]) );
+ },
+
+ deactivate: function(drop) {
+ if(drop.hoverclass)
+ Element.removeClassName(drop.element, drop.hoverclass);
+ this.last_active = null;
+ },
+
+ activate: function(drop) {
+ if(drop.hoverclass)
+ Element.addClassName(drop.element, drop.hoverclass);
+ this.last_active = drop;
+ },
+
+ show: function(point, element) {
+ if(!this.drops.length) return;
+ var drop, affected = [];
+
+ this.drops.each( function(drop) {
+ if(Droppables.isAffected(point, element, drop))
+ affected.push(drop);
+ });
+
+ if(affected.length>0)
+ drop = Droppables.findDeepestChild(affected);
+
+ if(this.last_active && this.last_active != drop) this.deactivate(this.last…
+ if (drop) {
+ Position.within(drop.element, point[0], point[1]);
+ if(drop.onHover)
+ drop.onHover(element, drop.element, Position.overlap(drop.overlap, dro…
+
+ if (drop != this.last_active) Droppables.activate(drop);
+ }
+ },
+
+ fire: function(event, element) {
+ if(!this.last_active) return;
+ Position.prepare();
+
+ if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], elemen…
+ if (this.last_active.onDrop) {
+ this.last_active.onDrop(element, this.last_active.element, event);
+ return true;
+ }
+ },
+
+ reset: function() {
+ if(this.last_active)
+ this.deactivate(this.last_active);
+ }
+};
+
+var Draggables = {
+ drags: [],
+ observers: [],
+
+ register: function(draggable) {
+ if(this.drags.length == 0) {
+ this.eventMouseUp = this.endDrag.bindAsEventListener(this);
+ this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
+ this.eventKeypress = this.keyPress.bindAsEventListener(this);
+
+ Event.observe(document, "mouseup", this.eventMouseUp);
+ Event.observe(document, "mousemove", this.eventMouseMove);
+ Event.observe(document, "keypress", this.eventKeypress);
+ }
+ this.drags.push(draggable);
+ },
+
+ unregister: function(draggable) {
+ this.drags = this.drags.reject(function(d) { return d==draggable });
+ if(this.drags.length == 0) {
+ Event.stopObserving(document, "mouseup", this.eventMouseUp);
+ Event.stopObserving(document, "mousemove", this.eventMouseMove);
+ Event.stopObserving(document, "keypress", this.eventKeypress);
+ }
+ },
+
+ activate: function(draggable) {
+ if(draggable.options.delay) {
+ this._timeout = setTimeout(function() {
+ Draggables._timeout = null;
+ window.focus();
+ Draggables.activeDraggable = draggable;
+ }.bind(this), draggable.options.delay);
+ } else {
+ window.focus(); // allows keypress events if window isn't currently focu…
+ this.activeDraggable = draggable;
+ }
+ },
+
+ deactivate: function() {
+ this.activeDraggable = null;
+ },
+
+ updateDrag: function(event) {
+ if(!this.activeDraggable) return;
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ // Mozilla-based browsers fire successive mousemove events with
+ // the same coordinates, prevent needless redrawing (moz bug?)
+ if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())…
+ this._lastPointer = pointer;
+
+ this.activeDraggable.updateDrag(event, pointer);
+ },
+
+ endDrag: function(event) {
+ if(this._timeout) {
+ clearTimeout(this._timeout);
+ this._timeout = null;
+ }
+ if(!this.activeDraggable) return;
+ this._lastPointer = null;
+ this.activeDraggable.endDrag(event);
+ this.activeDraggable = null;
+ },
+
+ keyPress: function(event) {
+ if(this.activeDraggable)
+ this.activeDraggable.keyPress(event);
+ },
+
+ addObserver: function(observer) {
+ this.observers.push(observer);
+ this._cacheObserverCallbacks();
+ },
+
+ removeObserver: function(element) { // element instead of observer fixes me…
+ this.observers = this.observers.reject( function(o) { return o.element==el…
+ this._cacheObserverCallbacks();
+ },
+
+ notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onD…
+ if(this[eventName+'Count'] > 0)
+ this.observers.each( function(o) {
+ if(o[eventName]) o[eventName](eventName, draggable, event);
+ });
+ if(draggable.options[eventName]) draggable.options[eventName](draggable, e…
+ },
+
+ _cacheObserverCallbacks: function() {
+ ['onStart','onEnd','onDrag'].each( function(eventName) {
+ Draggables[eventName+'Count'] = Draggables.observers.select(
+ function(o) { return o[eventName]; }
+ ).length;
+ });
+ }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Draggable = Class.create({
+ initialize: function(element) {
+ var defaults = {
+ handle: false,
+ reverteffect: function(element, top_offset, left_offset) {
+ var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.…
+ new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: …
+ queue: {scope:'_draggable', position:'end'}
+ });
+ },
+ endeffect: function(element) {
+ var toOpacity = Object.isNumber(element._opacity) ? element._opacity :…
+ new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
+ queue: {scope:'_draggable', position:'end'},
+ afterFinish: function(){
+ Draggable._dragging[element] = false
+ }
+ });
+ },
+ zindex: 1000,
+ revert: false,
+ quiet: false,
+ scroll: false,
+ scrollSensitivity: 20,
+ scrollSpeed: 15,
+ snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
+ delay: 0
+ };
+
+ if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
+ Object.extend(defaults, {
+ starteffect: function(element) {
+ element._opacity = Element.getOpacity(element);
+ Draggable._dragging[element] = true;
+ new Effect.Opacity(element, {duration:0.2, from:element._opacity, to…
+ }
+ });
+
+ var options = Object.extend(defaults, arguments[1] || { });
+
+ this.element = $(element);
+
+ if(options.handle && Object.isString(options.handle))
+ this.handle = this.element.down('.'+options.handle, 0);
+
+ if(!this.handle) this.handle = $(options.handle);
+ if(!this.handle) this.handle = this.element;
+
+ if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML…
+ options.scroll = $(options.scroll);
+ this._isScrollChild = Element.childOf(this.element, options.scroll);
+ }
+
+ Element.makePositioned(this.element); // fix IE
+
+ this.options = options;
+ this.dragging = false;
+
+ this.eventMouseDown = this.initDrag.bindAsEventListener(this);
+ Event.observe(this.handle, "mousedown", this.eventMouseDown);
+
+ Draggables.register(this);
+ },
+
+ destroy: function() {
+ Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
+ Draggables.unregister(this);
+ },
+
+ currentDelta: function() {
+ return([
+ parseInt(Element.getStyle(this.element,'left') || '0'),
+ parseInt(Element.getStyle(this.element,'top') || '0')]);
+ },
+
+ initDrag: function(event) {
+ if(!Object.isUndefined(Draggable._dragging[this.element]) &&
+ Draggable._dragging[this.element]) return;
+ if(Event.isLeftClick(event)) {
+ // abort on form elements, fixes a Firefox issue
+ var src = Event.element(event);
+ if((tag_name = src.tagName.toUpperCase()) && (
+ tag_name=='INPUT' ||
+ tag_name=='SELECT' ||
+ tag_name=='OPTION' ||
+ tag_name=='BUTTON' ||
+ tag_name=='TEXTAREA')) return;
+
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ var pos = this.element.cumulativeOffset();
+ this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
+
+ Draggables.activate(this);
+ Event.stop(event);
+ }
+ },
+
+ startDrag: function(event) {
+ this.dragging = true;
+ if(!this.delta)
+ this.delta = this.currentDelta();
+
+ if(this.options.zindex) {
+ this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
+ this.element.style.zIndex = this.options.zindex;
+ }
+
+ if(this.options.ghosting) {
+ this._clone = this.element.cloneNode(true);
+ this._originallyAbsolute = (this.element.getStyle('position') == 'absolu…
+ if (!this._originallyAbsolute)
+ Position.absolutize(this.element);
+ this.element.parentNode.insertBefore(this._clone, this.element);
+ }
+
+ if(this.options.scroll) {
+ if (this.options.scroll == window) {
+ var where = this._getWindowScroll(this.options.scroll);
+ this.originalScrollLeft = where.left;
+ this.originalScrollTop = where.top;
+ } else {
+ this.originalScrollLeft = this.options.scroll.scrollLeft;
+ this.originalScrollTop = this.options.scroll.scrollTop;
+ }
+ }
+
+ Draggables.notify('onStart', this, event);
+
+ if(this.options.starteffect) this.options.starteffect(this.element);
+ },
+
+ updateDrag: function(event, pointer) {
+ if(!this.dragging) this.startDrag(event);
+
+ if(!this.options.quiet){
+ Position.prepare();
+ Droppables.show(pointer, this.element);
+ }
+
+ Draggables.notify('onDrag', this, event);
+
+ this.draw(pointer);
+ if(this.options.change) this.options.change(this);
+
+ if(this.options.scroll) {
+ this.stopScrolling();
+
+ var p;
+ if (this.options.scroll == window) {
+ with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, le…
+ } else {
+ p = Position.page(this.options.scroll);
+ p[0] += this.options.scroll.scrollLeft + Position.deltaX;
+ p[1] += this.options.scroll.scrollTop + Position.deltaY;
+ p.push(p[0]+this.options.scroll.offsetWidth);
+ p.push(p[1]+this.options.scroll.offsetHeight);
+ }
+ var speed = [0,0];
+ if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointe…
+ if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointe…
+ if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointe…
+ if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointe…
+ this.startScrolling(speed);
+ }
+
+ // fix AppleWebKit rendering
+ if(Prototype.Browser.WebKit) window.scrollBy(0,0);
+
+ Event.stop(event);
+ },
+
+ finishDrag: function(event, success) {
+ this.dragging = false;
+
+ if(this.options.quiet){
+ Position.prepare();
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ Droppables.show(pointer, this.element);
+ }
+
+ if(this.options.ghosting) {
+ if (!this._originallyAbsolute)
+ Position.relativize(this.element);
+ delete this._originallyAbsolute;
+ Element.remove(this._clone);
+ this._clone = null;
+ }
+
+ var dropped = false;
+ if(success) {
+ dropped = Droppables.fire(event, this.element);
+ if (!dropped) dropped = false;
+ }
+ if(dropped && this.options.onDropped) this.options.onDropped(this.element);
+ Draggables.notify('onEnd', this, event);
+
+ var revert = this.options.revert;
+ if(revert && Object.isFunction(revert)) revert = revert(this.element);
+
+ var d = this.currentDelta();
+ if(revert && this.options.reverteffect) {
+ if (dropped == 0 || revert != 'failure')
+ this.options.reverteffect(this.element,
+ d[1]-this.delta[1], d[0]-this.delta[0]);
+ } else {
+ this.delta = d;
+ }
+
+ if(this.options.zindex)
+ this.element.style.zIndex = this.originalZ;
+
+ if(this.options.endeffect)
+ this.options.endeffect(this.element);
+
+ Draggables.deactivate(this);
+ Droppables.reset();
+ },
+
+ keyPress: function(event) {
+ if(event.keyCode!=Event.KEY_ESC) return;
+ this.finishDrag(event, false);
+ Event.stop(event);
+ },
+
+ endDrag: function(event) {
+ if(!this.dragging) return;
+ this.stopScrolling();
+ this.finishDrag(event, true);
+ Event.stop(event);
+ },
+
+ draw: function(point) {
+ var pos = this.element.cumulativeOffset();
+ if(this.options.ghosting) {
+ var r = Position.realOffset(this.element);
+ pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
+ }
+
+ var d = this.currentDelta();
+ pos[0] -= d[0]; pos[1] -= d[1];
+
+ if(this.options.scroll && (this.options.scroll != window && this._isScroll…
+ pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
+ pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
+ }
+
+ var p = [0,1].map(function(i){
+ return (point[i]-pos[i]-this.offset[i])
+ }.bind(this));
+
+ if(this.options.snap) {
+ if(Object.isFunction(this.options.snap)) {
+ p = this.options.snap(p[0],p[1],this);
+ } else {
+ if(Object.isArray(this.options.snap)) {
+ p = p.map( function(v, i) {
+ return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(…
+ } else {
+ p = p.map( function(v) {
+ return (v/this.options.snap).round()*this.options.snap }.bind(this));
+ }
+ }}
+
+ var style = this.element.style;
+ if((!this.options.constraint) || (this.options.constraint=='horizontal'))
+ style.left = p[0] + "px";
+ if((!this.options.constraint) || (this.options.constraint=='vertical'))
+ style.top = p[1] + "px";
+
+ if(style.visibility=="hidden") style.visibility = ""; // fix gecko renderi…
+ },
+
+ stopScrolling: function() {
+ if(this.scrollInterval) {
+ clearInterval(this.scrollInterval);
+ this.scrollInterval = null;
+ Draggables._lastScrollPointer = null;
+ }
+ },
+
+ startScrolling: function(speed) {
+ if(!(speed[0] || speed[1])) return;
+ this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.option…
+ this.lastScrolled = new Date();
+ this.scrollInterval = setInterval(this.scroll.bind(this), 10);
+ },
+
+ scroll: function() {
+ var current = new Date();
+ var delta = current - this.lastScrolled;
+ this.lastScrolled = current;
+ if(this.options.scroll == window) {
+ with (this._getWindowScroll(this.options.scroll)) {
+ if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
+ var d = delta / 1000;
+ this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*…
+ }
+ }
+ } else {
+ this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
+ this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
+ }
+
+ Position.prepare();
+ Droppables.show(Draggables._lastPointer, this.element);
+ Draggables.notify('onDrag', this);
+ if (this._isScrollChild) {
+ Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Drag…
+ Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
+ Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
+ if (Draggables._lastScrollPointer[0] < 0)
+ Draggables._lastScrollPointer[0] = 0;
+ if (Draggables._lastScrollPointer[1] < 0)
+ Draggables._lastScrollPointer[1] = 0;
+ this.draw(Draggables._lastScrollPointer);
+ }
+
+ if(this.options.change) this.options.change(this);
+ },
+
+ _getWindowScroll: function(w) {
+ var T, L, W, H;
+ with (w.document) {
+ if (w.document.documentElement && documentElement.scrollTop) {
+ T = documentElement.scrollTop;
+ L = documentElement.scrollLeft;
+ } else if (w.document.body) {
+ T = body.scrollTop;
+ L = body.scrollLeft;
+ }
+ if (w.innerWidth) {
+ W = w.innerWidth;
+ H = w.innerHeight;
+ } else if (w.document.documentElement && documentElement.clientWidth) {
+ W = documentElement.clientWidth;
+ H = documentElement.clientHeight;
+ } else {
+ W = body.offsetWidth;
+ H = body.offsetHeight;
+ }
+ }
+ return { top: T, left: L, width: W, height: H };
+ }
+});
+
+Draggable._dragging = { };
+
+/*--------------------------------------------------------------------------*/
+
+var SortableObserver = Class.create({
+ initialize: function(element, observer) {
+ this.element = $(element);
+ this.observer = observer;
+ this.lastValue = Sortable.serialize(this.element);
+ },
+
+ onStart: function() {
+ this.lastValue = Sortable.serialize(this.element);
+ },
+
+ onEnd: function() {
+ Sortable.unmark();
+ if(this.lastValue != Sortable.serialize(this.element))
+ this.observer(this.element)
+ }
+});
+
+var Sortable = {
+ SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
+
+ sortables: { },
+
+ _findRootElement: function(element) {
+ while (element.tagName.toUpperCase() != "BODY") {
+ if(element.id && Sortable.sortables[element.id]) return element;
+ element = element.parentNode;
+ }
+ },
+
+ options: function(element) {
+ element = Sortable._findRootElement($(element));
+ if(!element) return;
+ return Sortable.sortables[element.id];
+ },
+
+ destroy: function(element){
+ element = $(element);
+ var s = Sortable.sortables[element.id];
+
+ if(s) {
+ Draggables.removeObserver(s.element);
+ s.droppables.each(function(d){ Droppables.remove(d) });
+ s.draggables.invoke('destroy');
+
+ delete Sortable.sortables[s.element.id];
+ }
+ },
+
+ create: function(element) {
+ element = $(element);
+ var options = Object.extend({
+ element: element,
+ tag: 'li', // assumes li children, override with tag: 'tag…
+ dropOnEmpty: false,
+ tree: false,
+ treeTag: 'ul',
+ overlap: 'vertical', // one of 'vertical', 'horizontal'
+ constraint: 'vertical', // one of 'vertical', 'horizontal', false
+ containment: element, // also takes array of elements (or id's); or f…
+ handle: false, // or a CSS class
+ only: false,
+ delay: 0,
+ hoverclass: null,
+ ghosting: false,
+ quiet: false,
+ scroll: false,
+ scrollSensitivity: 20,
+ scrollSpeed: 15,
+ format: this.SERIALIZE_RULE,
+
+ // these take arrays of elements or ids and can be
+ // used for better initialization performance
+ elements: false,
+ handles: false,
+
+ onChange: Prototype.emptyFunction,
+ onUpdate: Prototype.emptyFunction
+ }, arguments[1] || { });
+
+ // clear any old sortable with same element
+ this.destroy(element);
+
+ // build options for the draggables
+ var options_for_draggable = {
+ revert: true,
+ quiet: options.quiet,
+ scroll: options.scroll,
+ scrollSpeed: options.scrollSpeed,
+ scrollSensitivity: options.scrollSensitivity,
+ delay: options.delay,
+ ghosting: options.ghosting,
+ constraint: options.constraint,
+ handle: options.handle };
+
+ if(options.starteffect)
+ options_for_draggable.starteffect = options.starteffect;
+
+ if(options.reverteffect)
+ options_for_draggable.reverteffect = options.reverteffect;
+ else
+ if(options.ghosting) options_for_draggable.reverteffect = function(eleme…
+ element.style.top = 0;
+ element.style.left = 0;
+ };
+
+ if(options.endeffect)
+ options_for_draggable.endeffect = options.endeffect;
+
+ if(options.zindex)
+ options_for_draggable.zindex = options.zindex;
+
+ // build options for the droppables
+ var options_for_droppable = {
+ overlap: options.overlap,
+ containment: options.containment,
+ tree: options.tree,
+ hoverclass: options.hoverclass,
+ onHover: Sortable.onHover
+ };
+
+ var options_for_tree = {
+ onHover: Sortable.onEmptyHover,
+ overlap: options.overlap,
+ containment: options.containment,
+ hoverclass: options.hoverclass
+ };
+
+ // fix for gecko engine
+ Element.cleanWhitespace(element);
+
+ options.draggables = [];
+ options.droppables = [];
+
+ // drop on empty handling
+ if(options.dropOnEmpty || options.tree) {
+ Droppables.add(element, options_for_tree);
+ options.droppables.push(element);
+ }
+
+ (options.elements || this.findElements(element, options) || []).each( func…
+ var handle = options.handles ? $(options.handles[i]) :
+ (options.handle ? $(e).select('.' + options.handle)[0] : e);
+ options.draggables.push(
+ new Draggable(e, Object.extend(options_for_draggable, { handle: handle…
+ Droppables.add(e, options_for_droppable);
+ if(options.tree) e.treeNode = element;
+ options.droppables.push(e);
+ });
+
+ if(options.tree) {
+ (Sortable.findTreeElements(element, options) || []).each( function(e) {
+ Droppables.add(e, options_for_tree);
+ e.treeNode = element;
+ options.droppables.push(e);
+ });
+ }
+
+ // keep reference
+ this.sortables[element.identify()] = options;
+
+ // for onupdate
+ Draggables.addObserver(new SortableObserver(element, options.onUpdate));
+
+ },
+
+ // return all suitable-for-sortable elements in a guaranteed order
+ findElements: function(element, options) {
+ return Element.findChildren(
+ element, options.only, options.tree ? true : false, options.tag);
+ },
+
+ findTreeElements: function(element, options) {
+ return Element.findChildren(
+ element, options.only, options.tree ? true : false, options.treeTag);
+ },
+
+ onHover: function(element, dropon, overlap) {
+ if(Element.isParent(dropon, element)) return;
+
+ if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
+ return;
+ } else if(overlap>0.5) {
+ Sortable.mark(dropon, 'before');
+ if(dropon.previousSibling != element) {
+ var oldParentNode = element.parentNode;
+ element.style.visibility = "hidden"; // fix gecko rendering
+ dropon.parentNode.insertBefore(element, dropon);
+ if(dropon.parentNode!=oldParentNode)
+ Sortable.options(oldParentNode).onChange(element);
+ Sortable.options(dropon.parentNode).onChange(element);
+ }
+ } else {
+ Sortable.mark(dropon, 'after');
+ var nextElement = dropon.nextSibling || null;
+ if(nextElement != element) {
+ var oldParentNode = element.parentNode;
+ element.style.visibility = "hidden"; // fix gecko rendering
+ dropon.parentNode.insertBefore(element, nextElement);
+ if(dropon.parentNode!=oldParentNode)
+ Sortable.options(oldParentNode).onChange(element);
+ Sortable.options(dropon.parentNode).onChange(element);
+ }
+ }
+ },
+
+ onEmptyHover: function(element, dropon, overlap) {
+ var oldParentNode = element.parentNode;
+ var droponOptions = Sortable.options(dropon);
+
+ if(!Element.isParent(dropon, element)) {
+ var index;
+
+ var children = Sortable.findElements(dropon, {tag: droponOptions.tag, on…
+ var child = null;
+
+ if(children) {
+ var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 …
+
+ for (index = 0; index < children.length; index += 1) {
+ if (offset - Element.offsetSize (children[index], droponOptions.over…
+ offset -= Element.offsetSize (children[index], droponOptions.overl…
+ } else if (offset - (Element.offsetSize (children[index], droponOpti…
+ child = index + 1 < children.length ? children[index + 1] : null;
+ break;
+ } else {
+ child = children[index];
+ break;
+ }
+ }
+ }
+
+ dropon.insertBefore(element, child);
+
+ Sortable.options(oldParentNode).onChange(element);
+ droponOptions.onChange(element);
+ }
+ },
+
+ unmark: function() {
+ if(Sortable._marker) Sortable._marker.hide();
+ },
+
+ mark: function(dropon, position) {
+ // mark on ghosting only
+ var sortable = Sortable.options(dropon.parentNode);
+ if(sortable && !sortable.ghosting) return;
+
+ if(!Sortable._marker) {
+ Sortable._marker =
+ ($('dropmarker') || Element.extend(document.createElement('DIV'))).
+ hide().addClassName('dropmarker').setStyle({position:'absolute'});
+ document.getElementsByTagName("body").item(0).appendChild(Sortable._mark…
+ }
+ var offsets = dropon.cumulativeOffset();
+ Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
+
+ if(position=='after')
+ if(sortable.overlap == 'horizontal')
+ Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px…
+ else
+ Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px…
+
+ Sortable._marker.show();
+ },
+
+ _tree: function(element, options, parent) {
+ var children = Sortable.findElements(element, options) || [];
+
+ for (var i = 0; i < children.length; ++i) {
+ var match = children[i].id.match(options.format);
+
+ if (!match) continue;
+
+ var child = {
+ id: encodeURIComponent(match ? match[1] : null),
+ element: element,
+ parent: parent,
+ children: [],
+ position: parent.children.length,
+ container: $(children[i]).down(options.treeTag)
+ };
+
+ /* Get the element containing the children and recurse over it */
+ if (child.container)
+ this._tree(child.container, options, child);
+
+ parent.children.push (child);
+ }
+
+ return parent;
+ },
+
+ tree: function(element) {
+ element = $(element);
+ var sortableOptions = this.options(element);
+ var options = Object.extend({
+ tag: sortableOptions.tag,
+ treeTag: sortableOptions.treeTag,
+ only: sortableOptions.only,
+ name: element.id,
+ format: sortableOptions.format
+ }, arguments[1] || { });
+
+ var root = {
+ id: null,
+ parent: null,
+ children: [],
+ container: element,
+ position: 0
+ };
+
+ return Sortable._tree(element, options, root);
+ },
+
+ /* Construct a [i] index for a particular node */
+ _constructIndex: function(node) {
+ var index = '';
+ do {
+ if (node.id) index = '[' + node.position + ']' + index;
+ } while ((node = node.parent) != null);
+ return index;
+ },
+
+ sequence: function(element) {
+ element = $(element);
+ var options = Object.extend(this.options(element), arguments[1] || { });
+
+ return $(this.findElements(element, options) || []).map( function(item) {
+ return item.id.match(options.format) ? item.id.match(options.format)[1] …
+ });
+ },
+
+ setSequence: function(element, new_sequence) {
+ element = $(element);
+ var options = Object.extend(this.options(element), arguments[2] || { });
+
+ var nodeMap = { };
+ this.findElements(element, options).each( function(n) {
+ if (n.id.match(options.format))
+ nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
+ n.parentNode.removeChild(n);
+ });
+
+ new_sequence.each(function(ident) {
+ var n = nodeMap[ident];
+ if (n) {
+ n[1].appendChild(n[0]);
+ delete nodeMap[ident];
+ }
+ });
+ },
+
+ serialize: function(element) {
+ element = $(element);
+ var options = Object.extend(Sortable.options(element), arguments[1] || { }…
+ var name = encodeURIComponent(
+ (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
+
+ if (options.tree) {
+ return Sortable.tree(element, arguments[1]).children.map( function (item…
+ return [name + Sortable._constructIndex(item) + "[id]=" +
+ encodeURIComponent(item.id)].concat(item.children.map(argument…
+ }).flatten().join('&');
+ } else {
+ return Sortable.sequence(element, arguments[1]).map( function(item) {
+ return name + "[]=" + encodeURIComponent(item);
+ }).join('&');
+ }
+ }
+};
+
+// Returns true if child is contained within element
+Element.isParent = function(child, element) {
+ if (!child.parentNode || child == element) return false;
+ if (child.parentNode == element) return true;
+ return Element.isParent(child.parentNode, element);
+};
+
+Element.findChildren = function(element, only, recursive, tagName) {
+ if(!element.hasChildNodes()) return null;
+ tagName = tagName.toUpperCase();
+ if(only) only = [only].flatten();
+ var elements = [];
+ $A(element.childNodes).each( function(e) {
+ if(e.tagName && e.tagName.toUpperCase()==tagName &&
+ (!only || (Element.classNames(e).detect(function(v) { return only.includ…
+ elements.push(e);
+ if(recursive) {
+ var grandchildren = Element.findChildren(e, only, recursive, tagName);
+ if(grandchildren) elements.push(grandchildren);
+ }
+ });
+
+ return (elements.length>0 ? elements.flatten() : []);
+};
+
+Element.offsetSize = function (element, type) {
+ return element['offset' + ((type=='vertical' || type=='height') ? 'Height' :…
+};
+\ No newline at end of file
diff --git a/web/public/javascripts/effects.js b/web/public/javascripts/effects…
@@ -0,0 +1,1123 @@
+// script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
+
+// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.ac…
+// Contributors:
+// Justin Palmer (http://encytemedia.com/)
+// Mark Pilgrim (http://diveintomark.org/)
+// Martin Bialasinki
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style lic…
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// converts rgb() and #xxx to #xxxxxx format,
+// returns self (or first argument) if not convertable
+String.prototype.parseColor = function() {
+ var color = '#';
+ if (this.slice(0,4) == 'rgb(') {
+ var cols = this.slice(4,this.length-1).split(',');
+ var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
+ } else {
+ if (this.slice(0,1) == '#') {
+ if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this…
+ if (this.length==7) color = this.toLowerCase();
+ }
+ }
+ return (color.length==7 ? color : (arguments[0] || this));
+};
+
+/*--------------------------------------------------------------------------*/
+
+Element.collectTextNodes = function(element) {
+ return $A($(element).childNodes).collect( function(node) {
+ return (node.nodeType==3 ? node.nodeValue :
+ (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
+ }).flatten().join('');
+};
+
+Element.collectTextNodesIgnoreClass = function(element, className) {
+ return $A($(element).childNodes).collect( function(node) {
+ return (node.nodeType==3 ? node.nodeValue :
+ ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
+ Element.collectTextNodesIgnoreClass(node, className) : ''));
+ }).flatten().join('');
+};
+
+Element.setContentZoom = function(element, percent) {
+ element = $(element);
+ element.setStyle({fontSize: (percent/100) + 'em'});
+ if (Prototype.Browser.WebKit) window.scrollBy(0,0);
+ return element;
+};
+
+Element.getInlineOpacity = function(element){
+ return $(element).style.opacity || '';
+};
+
+Element.forceRerendering = function(element) {
+ try {
+ element = $(element);
+ var n = document.createTextNode(' ');
+ element.appendChild(n);
+ element.removeChild(n);
+ } catch(e) { }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Effect = {
+ _elementDoesNotExistError: {
+ name: 'ElementDoesNotExistError',
+ message: 'The specified DOM element does not exist, but is required for th…
+ },
+ Transitions: {
+ linear: Prototype.K,
+ sinoidal: function(pos) {
+ return (-Math.cos(pos*Math.PI)/2) + .5;
+ },
+ reverse: function(pos) {
+ return 1-pos;
+ },
+ flicker: function(pos) {
+ var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
+ return pos > 1 ? 1 : pos;
+ },
+ wobble: function(pos) {
+ return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
+ },
+ pulse: function(pos, pulses) {
+ return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
+ },
+ spring: function(pos) {
+ return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
+ },
+ none: function(pos) {
+ return 0;
+ },
+ full: function(pos) {
+ return 1;
+ }
+ },
+ DefaultOptions: {
+ duration: 1.0, // seconds
+ fps: 100, // 100= assume 66fps max.
+ sync: false, // true for combining
+ from: 0.0,
+ to: 1.0,
+ delay: 0.0,
+ queue: 'parallel'
+ },
+ tagifyText: function(element) {
+ var tagifyStyle = 'position:relative';
+ if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
+
+ element = $(element);
+ $A(element.childNodes).each( function(child) {
+ if (child.nodeType==3) {
+ child.nodeValue.toArray().each( function(character) {
+ element.insertBefore(
+ new Element('span', {style: tagifyStyle}).update(
+ character == ' ' ? String.fromCharCode(160) : character),
+ child);
+ });
+ Element.remove(child);
+ }
+ });
+ },
+ multiple: function(element, effect) {
+ var elements;
+ if (((typeof element == 'object') ||
+ Object.isFunction(element)) &&
+ (element.length))
+ elements = element;
+ else
+ elements = $(element).childNodes;
+
+ var options = Object.extend({
+ speed: 0.1,
+ delay: 0.0
+ }, arguments[2] || { });
+ var masterDelay = options.delay;
+
+ $A(elements).each( function(element, index) {
+ new effect(element, Object.extend(options, { delay: index * options.spee…
+ });
+ },
+ PAIRS: {
+ 'slide': ['SlideDown','SlideUp'],
+ 'blind': ['BlindDown','BlindUp'],
+ 'appear': ['Appear','Fade']
+ },
+ toggle: function(element, effect, options) {
+ element = $(element);
+ effect = (effect || 'appear').toLowerCase();
+
+ return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](eleme…
+ queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
+ }, options || {}));
+ }
+};
+
+Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
+
+/* ------------- core effects ------------- */
+
+Effect.ScopedQueue = Class.create(Enumerable, {
+ initialize: function() {
+ this.effects = [];
+ this.interval = null;
+ },
+ _each: function(iterator) {
+ this.effects._each(iterator);
+ },
+ add: function(effect) {
+ var timestamp = new Date().getTime();
+
+ var position = Object.isString(effect.options.queue) ?
+ effect.options.queue : effect.options.queue.position;
+
+ switch(position) {
+ case 'front':
+ // move unstarted effects after this effect
+ this.effects.findAll(function(e){ return e.state=='idle' }).each( func…
+ e.startOn += effect.finishOn;
+ e.finishOn += effect.finishOn;
+ });
+ break;
+ case 'with-last':
+ timestamp = this.effects.pluck('startOn').max() || timestamp;
+ break;
+ case 'end':
+ // start effect after last queued effect has finished
+ timestamp = this.effects.pluck('finishOn').max() || timestamp;
+ break;
+ }
+
+ effect.startOn += timestamp;
+ effect.finishOn += timestamp;
+
+ if (!effect.options.queue.limit || (this.effects.length < effect.options.q…
+ this.effects.push(effect);
+
+ if (!this.interval)
+ this.interval = setInterval(this.loop.bind(this), 15);
+ },
+ remove: function(effect) {
+ this.effects = this.effects.reject(function(e) { return e==effect });
+ if (this.effects.length == 0) {
+ clearInterval(this.interval);
+ this.interval = null;
+ }
+ },
+ loop: function() {
+ var timePos = new Date().getTime();
+ for(var i=0, len=this.effects.length;i<len;i++)
+ this.effects[i] && this.effects[i].loop(timePos);
+ }
+});
+
+Effect.Queues = {
+ instances: $H(),
+ get: function(queueName) {
+ if (!Object.isString(queueName)) return queueName;
+
+ return this.instances.get(queueName) ||
+ this.instances.set(queueName, new Effect.ScopedQueue());
+ }
+};
+Effect.Queue = Effect.Queues.get('global');
+
+Effect.Base = Class.create({
+ position: null,
+ start: function(options) {
+ if (options && options.transition === false) options.transition = Effect.T…
+ this.options = Object.extend(Object.extend({ },Effect.DefaultOptions)…
+ this.currentFrame = 0;
+ this.state = 'idle';
+ this.startOn = this.options.delay*1000;
+ this.finishOn = this.startOn+(this.options.duration*1000);
+ this.fromToDelta = this.options.to-this.options.from;
+ this.totalTime = this.finishOn-this.startOn;
+ this.totalFrames = this.options.fps*this.options.duration;
+
+ this.render = (function() {
+ function dispatch(effect, eventName) {
+ if (effect.options[eventName + 'Internal'])
+ effect.options[eventName + 'Internal'](effect);
+ if (effect.options[eventName])
+ effect.options[eventName](effect);
+ }
+
+ return function(pos) {
+ if (this.state === "idle") {
+ this.state = "running";
+ dispatch(this, 'beforeSetup');
+ if (this.setup) this.setup();
+ dispatch(this, 'afterSetup');
+ }
+ if (this.state === "running") {
+ pos = (this.options.transition(pos) * this.fromToDelta) + this.optio…
+ this.position = pos;
+ dispatch(this, 'beforeUpdate');
+ if (this.update) this.update(pos);
+ dispatch(this, 'afterUpdate');
+ }
+ };
+ })();
+
+ this.event('beforeStart');
+ if (!this.options.sync)
+ Effect.Queues.get(Object.isString(this.options.queue) ?
+ 'global' : this.options.queue.scope).add(this);
+ },
+ loop: function(timePos) {
+ if (timePos >= this.startOn) {
+ if (timePos >= this.finishOn) {
+ this.render(1.0);
+ this.cancel();
+ this.event('beforeFinish');
+ if (this.finish) this.finish();
+ this.event('afterFinish');
+ return;
+ }
+ var pos = (timePos - this.startOn) / this.totalTime,
+ frame = (pos * this.totalFrames).round();
+ if (frame > this.currentFrame) {
+ this.render(pos);
+ this.currentFrame = frame;
+ }
+ }
+ },
+ cancel: function() {
+ if (!this.options.sync)
+ Effect.Queues.get(Object.isString(this.options.queue) ?
+ 'global' : this.options.queue.scope).remove(this);
+ this.state = 'finished';
+ },
+ event: function(eventName) {
+ if (this.options[eventName + 'Internal']) this.options[eventName + 'Intern…
+ if (this.options[eventName]) this.options[eventName](this);
+ },
+ inspect: function() {
+ var data = $H();
+ for(property in this)
+ if (!Object.isFunction(this[property])) data.set(property, this[property…
+ return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspe…
+ }
+});
+
+Effect.Parallel = Class.create(Effect.Base, {
+ initialize: function(effects) {
+ this.effects = effects || [];
+ this.start(arguments[1]);
+ },
+ update: function(position) {
+ this.effects.invoke('render', position);
+ },
+ finish: function(position) {
+ this.effects.each( function(effect) {
+ effect.render(1.0);
+ effect.cancel();
+ effect.event('beforeFinish');
+ if (effect.finish) effect.finish(position);
+ effect.event('afterFinish');
+ });
+ }
+});
+
+Effect.Tween = Class.create(Effect.Base, {
+ initialize: function(object, from, to) {
+ object = Object.isString(object) ? $(object) : object;
+ var args = $A(arguments), method = args.last(),
+ options = args.length == 5 ? args[3] : null;
+ this.method = Object.isFunction(method) ? method.bind(object) :
+ Object.isFunction(object[method]) ? object[method].bind(object) :
+ function(value) { object[method] = value };
+ this.start(Object.extend({ from: from, to: to }, options || { }));
+ },
+ update: function(position) {
+ this.method(position);
+ }
+});
+
+Effect.Event = Class.create(Effect.Base, {
+ initialize: function() {
+ this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
+ },
+ update: Prototype.emptyFunction
+});
+
+Effect.Opacity = Class.create(Effect.Base, {
+ initialize: function(element) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ // make this work on IE on elements without 'layout'
+ if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+ this.element.setStyle({zoom: 1});
+ var options = Object.extend({
+ from: this.element.getOpacity() || 0.0,
+ to: 1.0
+ }, arguments[1] || { });
+ this.start(options);
+ },
+ update: function(position) {
+ this.element.setOpacity(position);
+ }
+});
+
+Effect.Move = Class.create(Effect.Base, {
+ initialize: function(element) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ var options = Object.extend({
+ x: 0,
+ y: 0,
+ mode: 'relative'
+ }, arguments[1] || { });
+ this.start(options);
+ },
+ setup: function() {
+ this.element.makePositioned();
+ this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
+ this.originalTop = parseFloat(this.element.getStyle('top') || '0');
+ if (this.options.mode == 'absolute') {
+ this.options.x = this.options.x - this.originalLeft;
+ this.options.y = this.options.y - this.originalTop;
+ }
+ },
+ update: function(position) {
+ this.element.setStyle({
+ left: (this.options.x * position + this.originalLeft).round() + 'px',
+ top: (this.options.y * position + this.originalTop).round() + 'px'
+ });
+ }
+});
+
+// for backwards compatibility
+Effect.MoveBy = function(element, toTop, toLeft) {
+ return new Effect.Move(element,
+ Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
+};
+
+Effect.Scale = Class.create(Effect.Base, {
+ initialize: function(element, percent) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ var options = Object.extend({
+ scaleX: true,
+ scaleY: true,
+ scaleContent: true,
+ scaleFromCenter: false,
+ scaleMode: 'box', // 'box' or 'contents' or { } with provided val…
+ scaleFrom: 100.0,
+ scaleTo: percent
+ }, arguments[2] || { });
+ this.start(options);
+ },
+ setup: function() {
+ this.restoreAfterFinish = this.options.restoreAfterFinish || false;
+ this.elementPositioning = this.element.getStyle('position');
+
+ this.originalStyle = { };
+ ['top','left','width','height','fontSize'].each( function(k) {
+ this.originalStyle[k] = this.element.style[k];
+ }.bind(this));
+
+ this.originalTop = this.element.offsetTop;
+ this.originalLeft = this.element.offsetLeft;
+
+ var fontSize = this.element.getStyle('font-size') || '100%';
+ ['em','px','%','pt'].each( function(fontSizeType) {
+ if (fontSize.indexOf(fontSizeType)>0) {
+ this.fontSize = parseFloat(fontSize);
+ this.fontSizeType = fontSizeType;
+ }
+ }.bind(this));
+
+ this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
+
+ this.dims = null;
+ if (this.options.scaleMode=='box')
+ this.dims = [this.element.offsetHeight, this.element.offsetWidth];
+ if (/^content/.test(this.options.scaleMode))
+ this.dims = [this.element.scrollHeight, this.element.scrollWidth];
+ if (!this.dims)
+ this.dims = [this.options.scaleMode.originalHeight,
+ this.options.scaleMode.originalWidth];
+ },
+ update: function(position) {
+ var currentScale = (this.options.scaleFrom/100.0) + (this.factor * positio…
+ if (this.options.scaleContent && this.fontSize)
+ this.element.setStyle({fontSize: this.fontSize * currentScale + this.fon…
+ this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScal…
+ },
+ finish: function(position) {
+ if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
+ },
+ setDimensions: function(height, width) {
+ var d = { };
+ if (this.options.scaleX) d.width = width.round() + 'px';
+ if (this.options.scaleY) d.height = height.round() + 'px';
+ if (this.options.scaleFromCenter) {
+ var topd = (height - this.dims[0])/2;
+ var leftd = (width - this.dims[1])/2;
+ if (this.elementPositioning == 'absolute') {
+ if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
+ if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
+ } else {
+ if (this.options.scaleY) d.top = -topd + 'px';
+ if (this.options.scaleX) d.left = -leftd + 'px';
+ }
+ }
+ this.element.setStyle(d);
+ }
+});
+
+Effect.Highlight = Class.create(Effect.Base, {
+ initialize: function(element) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }…
+ this.start(options);
+ },
+ setup: function() {
+ // Prevent executing on elements not in the layout flow
+ if (this.element.getStyle('display')=='none') { this.cancel(); return; }
+ // Disable background image during the effect
+ this.oldStyle = { };
+ if (!this.options.keepBackgroundImage) {
+ this.oldStyle.backgroundImage = this.element.getStyle('background-image'…
+ this.element.setStyle({backgroundImage: 'none'});
+ }
+ if (!this.options.endcolor)
+ this.options.endcolor = this.element.getStyle('background-color').parseC…
+ if (!this.options.restorecolor)
+ this.options.restorecolor = this.element.getStyle('background-color');
+ // init color calculations
+ this._base = $R(0,2).map(function(i){ return parseInt(this.options.startc…
+ this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcol…
+ },
+ update: function(position) {
+ this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
+ return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart(…
+ },
+ finish: function() {
+ this.element.setStyle(Object.extend(this.oldStyle, {
+ backgroundColor: this.options.restorecolor
+ }));
+ }
+});
+
+Effect.ScrollTo = function(element) {
+ var options = arguments[1] || { },
+ scrollOffsets = document.viewport.getScrollOffsets(),
+ elementOffsets = $(element).cumulativeOffset();
+
+ if (options.offset) elementOffsets[1] += options.offset;
+
+ return new Effect.Tween(null,
+ scrollOffsets.top,
+ elementOffsets[1],
+ options,
+ function(p){ scrollTo(scrollOffsets.left, p.round()); }
+ );
+};
+
+/* ------------- combination effects ------------- */
+
+Effect.Fade = function(element) {
+ element = $(element);
+ var oldOpacity = element.getInlineOpacity();
+ var options = Object.extend({
+ from: element.getOpacity() || 1.0,
+ to: 0.0,
+ afterFinishInternal: function(effect) {
+ if (effect.options.to!=0) return;
+ effect.element.hide().setStyle({opacity: oldOpacity});
+ }
+ }, arguments[1] || { });
+ return new Effect.Opacity(element,options);
+};
+
+Effect.Appear = function(element) {
+ element = $(element);
+ var options = Object.extend({
+ from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() ||…
+ to: 1.0,
+ // force Safari to render floated elements properly
+ afterFinishInternal: function(effect) {
+ effect.element.forceRerendering();
+ },
+ beforeSetup: function(effect) {
+ effect.element.setOpacity(effect.options.from).show();
+ }}, arguments[1] || { });
+ return new Effect.Opacity(element,options);
+};
+
+Effect.Puff = function(element) {
+ element = $(element);
+ var oldStyle = {
+ opacity: element.getInlineOpacity(),
+ position: element.getStyle('position'),
+ top: element.style.top,
+ left: element.style.left,
+ width: element.style.width,
+ height: element.style.height
+ };
+ return new Effect.Parallel(
+ [ new Effect.Scale(element, 200,
+ { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFin…
+ new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
+ Object.extend({ duration: 1.0,
+ beforeSetupInternal: function(effect) {
+ Position.absolutize(effect.effects[0].element);
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.hide().setStyle(oldStyle); }
+ }, arguments[1] || { })
+ );
+};
+
+Effect.BlindUp = function(element) {
+ element = $(element);
+ element.makeClipping();
+ return new Effect.Scale(element, 0,
+ Object.extend({ scaleContent: false,
+ scaleX: false,
+ restoreAfterFinish: true,
+ afterFinishInternal: function(effect) {
+ effect.element.hide().undoClipping();
+ }
+ }, arguments[1] || { })
+ );
+};
+
+Effect.BlindDown = function(element) {
+ element = $(element);
+ var elementDimensions = element.getDimensions();
+ return new Effect.Scale(element, 100, Object.extend({
+ scaleContent: false,
+ scaleX: false,
+ scaleFrom: 0,
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: eleme…
+ restoreAfterFinish: true,
+ afterSetup: function(effect) {
+ effect.element.makeClipping().setStyle({height: '0px'}).show();
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.undoClipping();
+ }
+ }, arguments[1] || { }));
+};
+
+Effect.SwitchOff = function(element) {
+ element = $(element);
+ var oldOpacity = element.getInlineOpacity();
+ return new Effect.Appear(element, Object.extend({
+ duration: 0.4,
+ from: 0,
+ transition: Effect.Transitions.flicker,
+ afterFinishInternal: function(effect) {
+ new Effect.Scale(effect.element, 1, {
+ duration: 0.3, scaleFromCenter: true,
+ scaleX: false, scaleContent: false, restoreAfterFinish: true,
+ beforeSetup: function(effect) {
+ effect.element.makePositioned().makeClipping();
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.hide().undoClipping().undoPositioned().setStyle({opac…
+ }
+ });
+ }
+ }, arguments[1] || { }));
+};
+
+Effect.DropOut = function(element) {
+ element = $(element);
+ var oldStyle = {
+ top: element.getStyle('top'),
+ left: element.getStyle('left'),
+ opacity: element.getInlineOpacity() };
+ return new Effect.Parallel(
+ [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
+ new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
+ Object.extend(
+ { duration: 0.5,
+ beforeSetup: function(effect) {
+ effect.effects[0].element.makePositioned();
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
+ }
+ }, arguments[1] || { }));
+};
+
+Effect.Shake = function(element) {
+ element = $(element);
+ var options = Object.extend({
+ distance: 20,
+ duration: 0.5
+ }, arguments[1] || {});
+ var distance = parseFloat(options.distance);
+ var split = parseFloat(options.duration) / 10.0;
+ var oldStyle = {
+ top: element.getStyle('top'),
+ left: element.getStyle('left') };
+ return new Effect.Move(element,
+ { x: distance, y: 0, duration: split, afterFinishInternal: function(eff…
+ new Effect.Move(effect.element,
+ { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: functio…
+ new Effect.Move(effect.element,
+ { x: distance*2, y: 0, duration: split*2, afterFinishInternal: functio…
+ new Effect.Move(effect.element,
+ { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: functio…
+ new Effect.Move(effect.element,
+ { x: distance*2, y: 0, duration: split*2, afterFinishInternal: functio…
+ new Effect.Move(effect.element,
+ { x: -distance, y: 0, duration: split, afterFinishInternal: function(eff…
+ effect.element.undoPositioned().setStyle(oldStyle);
+ }}); }}); }}); }}); }}); }});
+};
+
+Effect.SlideDown = function(element) {
+ element = $(element).cleanWhitespace();
+ // SlideDown need to have the content of the element wrapped in a container …
+ var oldInnerBottom = element.down().getStyle('bottom');
+ var elementDimensions = element.getDimensions();
+ return new Effect.Scale(element, 100, Object.extend({
+ scaleContent: false,
+ scaleX: false,
+ scaleFrom: window.opera ? 0 : 1,
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: eleme…
+ restoreAfterFinish: true,
+ afterSetup: function(effect) {
+ effect.element.makePositioned();
+ effect.element.down().makePositioned();
+ if (window.opera) effect.element.setStyle({top: ''});
+ effect.element.makeClipping().setStyle({height: '0px'}).show();
+ },
+ afterUpdateInternal: function(effect) {
+ effect.element.down().setStyle({bottom:
+ (effect.dims[0] - effect.element.clientHeight) + 'px' });
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.undoClipping().undoPositioned();
+ effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}…
+ }, arguments[1] || { })
+ );
+};
+
+Effect.SlideUp = function(element) {
+ element = $(element).cleanWhitespace();
+ var oldInnerBottom = element.down().getStyle('bottom');
+ var elementDimensions = element.getDimensions();
+ return new Effect.Scale(element, window.opera ? 0 : 1,
+ Object.extend({ scaleContent: false,
+ scaleX: false,
+ scaleMode: 'box',
+ scaleFrom: 100,
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: eleme…
+ restoreAfterFinish: true,
+ afterSetup: function(effect) {
+ effect.element.makePositioned();
+ effect.element.down().makePositioned();
+ if (window.opera) effect.element.setStyle({top: ''});
+ effect.element.makeClipping().show();
+ },
+ afterUpdateInternal: function(effect) {
+ effect.element.down().setStyle({bottom:
+ (effect.dims[0] - effect.element.clientHeight) + 'px' });
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.hide().undoClipping().undoPositioned();
+ effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}…
+ }
+ }, arguments[1] || { })
+ );
+};
+
+// Bug in opera makes the TD containing this element expand for a instance aft…
+Effect.Squish = function(element) {
+ return new Effect.Scale(element, window.opera ? 1 : 0, {
+ restoreAfterFinish: true,
+ beforeSetup: function(effect) {
+ effect.element.makeClipping();
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.hide().undoClipping();
+ }
+ });
+};
+
+Effect.Grow = function(element) {
+ element = $(element);
+ var options = Object.extend({
+ direction: 'center',
+ moveTransition: Effect.Transitions.sinoidal,
+ scaleTransition: Effect.Transitions.sinoidal,
+ opacityTransition: Effect.Transitions.full
+ }, arguments[1] || { });
+ var oldStyle = {
+ top: element.style.top,
+ left: element.style.left,
+ height: element.style.height,
+ width: element.style.width,
+ opacity: element.getInlineOpacity() };
+
+ var dims = element.getDimensions();
+ var initialMoveX, initialMoveY;
+ var moveX, moveY;
+
+ switch (options.direction) {
+ case 'top-left':
+ initialMoveX = initialMoveY = moveX = moveY = 0;
+ break;
+ case 'top-right':
+ initialMoveX = dims.width;
+ initialMoveY = moveY = 0;
+ moveX = -dims.width;
+ break;
+ case 'bottom-left':
+ initialMoveX = moveX = 0;
+ initialMoveY = dims.height;
+ moveY = -dims.height;
+ break;
+ case 'bottom-right':
+ initialMoveX = dims.width;
+ initialMoveY = dims.height;
+ moveX = -dims.width;
+ moveY = -dims.height;
+ break;
+ case 'center':
+ initialMoveX = dims.width / 2;
+ initialMoveY = dims.height / 2;
+ moveX = -dims.width / 2;
+ moveY = -dims.height / 2;
+ break;
+ }
+
+ return new Effect.Move(element, {
+ x: initialMoveX,
+ y: initialMoveY,
+ duration: 0.01,
+ beforeSetup: function(effect) {
+ effect.element.hide().makeClipping().makePositioned();
+ },
+ afterFinishInternal: function(effect) {
+ new Effect.Parallel(
+ [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0,…
+ new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, tr…
+ new Effect.Scale(effect.element, 100, {
+ scaleMode: { originalHeight: dims.height, originalWidth: dims.widt…
+ sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.s…
+ ], Object.extend({
+ beforeSetup: function(effect) {
+ effect.effects[0].element.setStyle({height: '0px'}).show();
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.undoClipping().undoPositioned().setSt…
+ }
+ }, options)
+ );
+ }
+ });
+};
+
+Effect.Shrink = function(element) {
+ element = $(element);
+ var options = Object.extend({
+ direction: 'center',
+ moveTransition: Effect.Transitions.sinoidal,
+ scaleTransition: Effect.Transitions.sinoidal,
+ opacityTransition: Effect.Transitions.none
+ }, arguments[1] || { });
+ var oldStyle = {
+ top: element.style.top,
+ left: element.style.left,
+ height: element.style.height,
+ width: element.style.width,
+ opacity: element.getInlineOpacity() };
+
+ var dims = element.getDimensions();
+ var moveX, moveY;
+
+ switch (options.direction) {
+ case 'top-left':
+ moveX = moveY = 0;
+ break;
+ case 'top-right':
+ moveX = dims.width;
+ moveY = 0;
+ break;
+ case 'bottom-left':
+ moveX = 0;
+ moveY = dims.height;
+ break;
+ case 'bottom-right':
+ moveX = dims.width;
+ moveY = dims.height;
+ break;
+ case 'center':
+ moveX = dims.width / 2;
+ moveY = dims.height / 2;
+ break;
+ }
+
+ return new Effect.Parallel(
+ [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition…
+ new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition…
+ new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: o…
+ ], Object.extend({
+ beforeStartInternal: function(effect) {
+ effect.effects[0].element.makePositioned().makeClipping();
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.hide().undoClipping().undoPositioned().se…
+ }, options)
+ );
+};
+
+Effect.Pulsate = function(element) {
+ element = $(element);
+ var options = arguments[1] || { },
+ oldOpacity = element.getInlineOpacity(),
+ transition = options.transition || Effect.Transitions.linear,
+ reverser = function(pos){
+ return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2)…
+ };
+
+ return new Effect.Opacity(element,
+ Object.extend(Object.extend({ duration: 2.0, from: 0,
+ afterFinishInternal: function(effect) { effect.element.setStyle({opacity…
+ }, options), {transition: reverser}));
+};
+
+Effect.Fold = function(element) {
+ element = $(element);
+ var oldStyle = {
+ top: element.style.top,
+ left: element.style.left,
+ width: element.style.width,
+ height: element.style.height };
+ element.makeClipping();
+ return new Effect.Scale(element, 5, Object.extend({
+ scaleContent: false,
+ scaleX: false,
+ afterFinishInternal: function(effect) {
+ new Effect.Scale(element, 1, {
+ scaleContent: false,
+ scaleY: false,
+ afterFinishInternal: function(effect) {
+ effect.element.hide().undoClipping().setStyle(oldStyle);
+ } });
+ }}, arguments[1] || { }));
+};
+
+Effect.Morph = Class.create(Effect.Base, {
+ initialize: function(element) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ var options = Object.extend({
+ style: { }
+ }, arguments[1] || { });
+
+ if (!Object.isString(options.style)) this.style = $H(options.style);
+ else {
+ if (options.style.include(':'))
+ this.style = options.style.parseStyle();
+ else {
+ this.element.addClassName(options.style);
+ this.style = $H(this.element.getStyles());
+ this.element.removeClassName(options.style);
+ var css = this.element.getStyles();
+ this.style = this.style.reject(function(style) {
+ return style.value == css[style.key];
+ });
+ options.afterFinishInternal = function(effect) {
+ effect.element.addClassName(effect.options.style);
+ effect.transforms.each(function(transform) {
+ effect.element.style[transform.style] = '';
+ });
+ };
+ }
+ }
+ this.start(options);
+ },
+
+ setup: function(){
+ function parseColor(color){
+ if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color =…
+ color = color.parseColor();
+ return $R(0,2).map(function(i){
+ return parseInt( color.slice(i*2+1,i*2+3), 16 );
+ });
+ }
+ this.transforms = this.style.map(function(pair){
+ var property = pair[0], value = pair[1], unit = null;
+
+ if (value.parseColor('#zzzzzz') != '#zzzzzz') {
+ value = value.parseColor();
+ unit = 'color';
+ } else if (property == 'opacity') {
+ value = parseFloat(value);
+ if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+ this.element.setStyle({zoom: 1});
+ } else if (Element.CSS_LENGTH.test(value)) {
+ var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
+ value = parseFloat(components[1]);
+ unit = (components.length == 3) ? components[2] : null;
+ }
+
+ var originalValue = this.element.getStyle(property);
+ return {
+ style: property.camelize(),
+ originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(…
+ targetValue: unit=='color' ? parseColor(value) : value,
+ unit: unit
+ };
+ }.bind(this)).reject(function(transform){
+ return (
+ (transform.originalValue == transform.targetValue) ||
+ (
+ transform.unit != 'color' &&
+ (isNaN(transform.originalValue) || isNaN(transform.targetValue))
+ )
+ );
+ });
+ },
+ update: function(position) {
+ var style = { }, transform, i = this.transforms.length;
+ while(i--)
+ style[(transform = this.transforms[i]).style] =
+ transform.unit=='color' ? '#'+
+ (Math.round(transform.originalValue[0]+
+ (transform.targetValue[0]-transform.originalValue[0])*position)).t…
+ (Math.round(transform.originalValue[1]+
+ (transform.targetValue[1]-transform.originalValue[1])*position)).t…
+ (Math.round(transform.originalValue[2]+
+ (transform.targetValue[2]-transform.originalValue[2])*position)).t…
+ (transform.originalValue +
+ (transform.targetValue - transform.originalValue) * position).toFixe…
+ (transform.unit === null ? '' : transform.unit);
+ this.element.setStyle(style, true);
+ }
+});
+
+Effect.Transform = Class.create({
+ initialize: function(tracks){
+ this.tracks = [];
+ this.options = arguments[1] || { };
+ this.addTracks(tracks);
+ },
+ addTracks: function(tracks){
+ tracks.each(function(track){
+ track = $H(track);
+ var data = track.values().first();
+ this.tracks.push($H({
+ ids: track.keys().first(),
+ effect: Effect.Morph,
+ options: { style: data }
+ }));
+ }.bind(this));
+ return this;
+ },
+ play: function(){
+ return new Effect.Parallel(
+ this.tracks.map(function(track){
+ var ids = track.get('ids'), effect = track.get('effect'), options = tr…
+ var elements = [$(ids) || $$(ids)].flatten();
+ return elements.map(function(e){ return new effect(e, Object.extend({ …
+ }).flatten(),
+ this.options
+ );
+ }
+});
+
+Element.CSS_PROPERTIES = $w(
+ 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
+ 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
+ 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
+ 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
+ 'fontSize fontWeight height left letterSpacing lineHeight ' +
+ 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
+ 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
+ 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
+ 'right textIndent top width wordSpacing zIndex');
+
+Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
+
+String.__parseStyleElement = document.createElement('div');
+String.prototype.parseStyle = function(){
+ var style, styleRules = $H();
+ if (Prototype.Browser.WebKit)
+ style = new Element('div',{style:this}).style;
+ else {
+ String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
+ style = String.__parseStyleElement.childNodes[0].style;
+ }
+
+ Element.CSS_PROPERTIES.each(function(property){
+ if (style[property]) styleRules.set(property, style[property]);
+ });
+
+ if (Prototype.Browser.IE && this.include('opacity'))
+ styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]…
+
+ return styleRules;
+};
+
+if (document.defaultView && document.defaultView.getComputedStyle) {
+ Element.getStyles = function(element) {
+ var css = document.defaultView.getComputedStyle($(element), null);
+ return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
+ styles[property] = css[property];
+ return styles;
+ });
+ };
+} else {
+ Element.getStyles = function(element) {
+ element = $(element);
+ var css = element.currentStyle, styles;
+ styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
+ results[property] = css[property];
+ return results;
+ });
+ if (!styles.opacity) styles.opacity = element.getOpacity();
+ return styles;
+ };
+}
+
+Effect.Methods = {
+ morph: function(element, style) {
+ element = $(element);
+ new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || …
+ return element;
+ },
+ visualEffect: function(element, effect, options) {
+ element = $(element);
+ var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() +…
+ new Effect[klass](element, options);
+ return element;
+ },
+ highlight: function(element, options) {
+ element = $(element);
+ new Effect.Highlight(element, options);
+ return element;
+ }
+};
+
+$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
+ 'pulsate shake puff squish switchOff dropOut').each(
+ function(effect) {
+ Effect.Methods[effect] = function(element, options){
+ element = $(element);
+ Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, op…
+ return element;
+ };
+ }
+);
+
+$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectT…
+ function(f) { Effect.Methods[f] = Element[f]; }
+);
+
+Element.addMethods(Effect.Methods);
+\ No newline at end of file
diff --git a/web/public/javascripts/lightbox.js b/web/public/javascripts/lightb…
@@ -0,0 +1,426 @@
+/*
+ Lightbox JS: Fullsize Image Overlays
+ by Lokesh Dhakar - http://www.huddletogether.com
+
+ For more information on this script, visit:
+ http://huddletogether.com/projects/lightbox/
+
+ Script featured on Dynamic Drive code library Jan 24th, 06':
+ http://www.dynamicdrive.com
+
+ Licensed under the Creative Commons Attribution 2.5 License - http://c…
+ (basically, do anything you want, just leave my name and link)
+
+ Table of Contents
+ -----------------
+ Configuration
+
+ Functions
+ - getPageScroll()
+ - getPageSize()
+ - pause()
+ - getKey()
+ - listenKey()
+ - showLightbox()
+ - hideLightbox()
+ - initLightbox()
+ - addLoadEvent()
+
+ Function Calls
+ - addLoadEvent(initLightbox)
+
+*/
+
+
+
+//
+// Configuration
+//
+
+// If you would like to use a custom loading image or close button reference t…
+var loadingImage = '/images/loading.gif';
+var closeButton = '/images/close.gif';
+
+
+
+
+
+//
+// getPageScroll()
+// Returns array with x,y page scroll values.
+// Core code from - quirksmode.org
+//
+function getPageScroll(){
+
+ var yScroll;
+
+ if (self.pageYOffset) {
+ yScroll = self.pageYOffset;
+ } else if (document.documentElement && document.documentElement.scroll…
+ yScroll = document.documentElement.scrollTop;
+ } else if (document.body) {// all other Explorers
+ yScroll = document.body.scrollTop;
+ }
+
+ arrayPageScroll = new Array('',yScroll)
+ return arrayPageScroll;
+}
+
+
+
+//
+// getPageSize()
+// Returns array with page width, height and window width, height
+// Core code from - quirksmode.org
+// Edit for Firefox by pHaez
+//
+function getPageSize(){
+
+ var xScroll, yScroll;
+
+ if (window.innerHeight && window.scrollMaxY) {
+ xScroll = document.body.scrollWidth;
+ yScroll = window.innerHeight + window.scrollMaxY;
+ } else if (document.body.scrollHeight > document.body.offsetHeight){ /…
+ xScroll = document.body.scrollWidth;
+ yScroll = document.body.scrollHeight;
+ } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozil…
+ xScroll = document.body.offsetWidth;
+ yScroll = document.body.offsetHeight;
+ }
+
+ var windowWidth, windowHeight;
+ if (self.innerHeight) { // all except Explorer
+ windowWidth = self.innerWidth;
+ windowHeight = self.innerHeight;
+ } else if (document.documentElement && document.documentElement.client…
+ windowWidth = document.documentElement.clientWidth;
+ windowHeight = document.documentElement.clientHeight;
+ } else if (document.body) { // other Explorers
+ windowWidth = document.body.clientWidth;
+ windowHeight = document.body.clientHeight;
+ }
+
+ // for small pages with total height less then height of the viewport
+ if(yScroll < windowHeight){
+ pageHeight = windowHeight;
+ } else {
+ pageHeight = yScroll;
+ }
+
+ // for small pages with total width less then width of the viewport
+ if(xScroll < windowWidth){
+ pageWidth = windowWidth;
+ } else {
+ pageWidth = xScroll;
+ }
+
+
+ arrayPageSize = new Array(pageWidth,pageHeight,windowWidth,windowHeigh…
+ return arrayPageSize;
+}
+
+
+//
+// pause(numberMillis)
+// Pauses code execution for specified time. Uses busy code, not good.
+// Code from http://www.faqts.com/knowledge_base/view.phtml/aid/1602
+//
+function pause(numberMillis) {
+ var now = new Date();
+ var exitTime = now.getTime() + numberMillis;
+ while (true) {
+ now = new Date();
+ if (now.getTime() > exitTime)
+ return;
+ }
+}
+
+//
+// getKey(key)
+// Gets keycode. If 'x' is pressed then it hides the lightbox.
+//
+
+function getKey(e){
+ if (e == null) { // ie
+ keycode = event.keyCode;
+ } else { // mozilla
+ keycode = e.which;
+ }
+ key = String.fromCharCode(keycode).toLowerCase();
+
+ if(key == 'x'){ hideLightbox(); }
+}
+
+
+//
+// listenKey()
+//
+function listenKey () { document.onkeypress = getKey; }
+
+
+//
+// showLightbox()
+// Preloads images. Pleaces new image in lightbox then centers and displays.
+//
+function showLightbox(objLink)
+{
+ // prep objects
+ var objOverlay = document.getElementById('overlay');
+ var objLightbox = document.getElementById('lightbox');
+ var objCaption = document.getElementById('lightboxCaption');
+ var objImage = document.getElementById('lightboxImage');
+ var objLoadingImage = document.getElementById('loadingImage');
+ var objLightboxDetails = document.getElementById('lightboxDetails');
+
+
+ var arrayPageSize = getPageSize();
+ var arrayPageScroll = getPageScroll();
+
+ // center loadingImage if it exists
+ if (objLoadingImage) {
+ objLoadingImage.style.top = (arrayPageScroll[1] + ((arrayPageS…
+ objLoadingImage.style.left = (((arrayPageSize[0] - 20 - objLoa…
+ objLoadingImage.style.display = 'block';
+ }
+
+ // set height of Overlay to take up whole page and show
+ objOverlay.style.height = (arrayPageSize[1] + 'px');
+ objOverlay.style.display = 'block';
+
+ // preload image
+ imgPreload = new Image();
+
+ imgPreload.onload=function(){
+ objImage.src = objLink.href;
+
+ // center lightbox and make sure that the top and left values …
+ // and the image placed outside the viewport
+ var lightboxTop = arrayPageScroll[1] + ((arrayPageSize[3] - 35…
+ var lightboxLeft = ((arrayPageSize[0] - 20 - imgPreload.width)…
+
+ objLightbox.style.top = (lightboxTop < 0) ? "0px" : lightboxTo…
+ objLightbox.style.left = (lightboxLeft < 0) ? "0px" : lightbox…
+
+
+ objLightboxDetails.style.width = imgPreload.width + 'px';
+
+ if(objLink.getAttribute('title')){
+ objCaption.style.display = 'block';
+ //objCaption.style.width = imgPreload.width + 'px';
+ objCaption.innerHTML = objLink.getAttribute('title');
+ } else {
+ objCaption.style.display = 'none';
+ }
+
+ // A small pause between the image loading and displaying is r…
+ // this prevents the previous image displaying for a short bur…
+ if (navigator.appVersion.indexOf("MSIE")!=-1){
+ pause(250);
+ }
+
+ if (objLoadingImage) { objLoadingImage.style.display = …
+ objLightbox.style.display = 'block';
+
+ // After image is loaded, update the overlay height as the new…
+ // increased the overall page height.
+ arrayPageSize = getPageSize();
+ objOverlay.style.height = (arrayPageSize[1] + 'px');
+
+ // Check for 'x' keypress
+ listenKey();
+
+ return false;
+ }
+
+ imgPreload.src = objLink.href;
+
+}
+
+
+
+
+
+//
+// hideLightbox()
+//
+function hideLightbox()
+{
+ // get objects
+ objOverlay = document.getElementById('overlay');
+ objLightbox = document.getElementById('lightbox');
+
+ // hide lightbox and overlay
+ objOverlay.style.display = 'none';
+ objLightbox.style.display = 'none';
+
+ // disable keypress listener
+ document.onkeypress = '';
+}
+
+
+
+
+//
+// initLightbox()
+// Function runs on window load, going through link tags looking for rel="ligh…
+// These links receive onclick events that enable the lightbox display for the…
+// The function also inserts html markup at the top of the page which will be …
+// container for the overlay pattern and the inline image.
+//
+function initLightbox()
+{
+
+ if (!document.getElementsByTagName){ return; }
+ var anchors = document.getElementsByTagName("a");
+
+ // loop through all anchor tags
+ for (var i=0; i<anchors.length; i++){
+ var anchor = anchors[i];
+
+ if (anchor.getAttribute("href") && (anchor.getAttribute("rel")…
+ anchor.onclick = function () {showLightbox(this); retu…
+ }
+ }
+
+ // the rest of this code inserts html at the top of the page that look…
+ //
+ // <div id="overlay">
+ // <a href="#" onclick="hideLightbox(); return false;">…
+ // </div>
+ // <div id="lightbox">
+ // <a href="#" onclick="hideLightbox(); return false;" …
+ // <img id="closeButton" />
+ // <img id="lightboxImage" />
+ // </a>
+ // <div id="lightboxDetails">
+ // <div id="lightboxCaption"></div>
+ // <div id="keyboardMsg"></div>
+ // </div>
+ // </div>
+
+ var objBody = document.getElementsByTagName("body").item(0);
+
+ // create overlay div and hardcode some functional styles (aesthetic s…
+ var objOverlay = document.createElement("div");
+ objOverlay.setAttribute('id','overlay');
+ objOverlay.onclick = function () {hideLightbox(); return false;}
+ objOverlay.style.display = 'none';
+ objOverlay.style.position = 'absolute';
+ objOverlay.style.top = '0';
+ objOverlay.style.left = '0';
+ objOverlay.style.zIndex = '90';
+ objOverlay.style.width = '100%';
+ objBody.insertBefore(objOverlay, objBody.firstChild);
+
+ var arrayPageSize = getPageSize();
+ var arrayPageScroll = getPageScroll();
+
+ // preload and create loader image
+ var imgPreloader = new Image();
+
+ // if loader image found, create link to hide lightbox and create load…
+ imgPreloader.onload=function(){
+
+ var objLoadingImageLink = document.createElement("a");
+ objLoadingImageLink.setAttribute('href','#');
+ objLoadingImageLink.onclick = function () {hideLightbox(); ret…
+ objOverlay.appendChild(objLoadingImageLink);
+
+ var objLoadingImage = document.createElement("img");
+ objLoadingImage.src = loadingImage;
+ objLoadingImage.setAttribute('id','loadingImage');
+ objLoadingImage.style.position = 'absolute';
+ objLoadingImage.style.zIndex = '150';
+ objLoadingImageLink.appendChild(objLoadingImage);
+
+ imgPreloader.onload=function(){}; // clear onLoa…
+
+ return false;
+ }
+
+ imgPreloader.src = loadingImage;
+
+ // create lightbox div, same note about styles as above
+ var objLightbox = document.createElement("div");
+ objLightbox.setAttribute('id','lightbox');
+ objLightbox.style.display = 'none';
+ objLightbox.style.position = 'absolute';
+ objLightbox.style.zIndex = '100';
+ objBody.insertBefore(objLightbox, objOverlay.nextSibling);
+
+ // create link
+ var objLink = document.createElement("a");
+ objLink.setAttribute('href','#');
+ objLink.setAttribute('title','Click to close');
+ objLink.onclick = function () {hideLightbox(); return false;}
+ objLightbox.appendChild(objLink);
+
+ // preload and create close button image
+ var imgPreloadCloseButton = new Image();
+
+ // if close button image found,
+ imgPreloadCloseButton.onload=function(){
+
+ var objCloseButton = document.createElement("img");
+ objCloseButton.src = closeButton;
+ objCloseButton.setAttribute('id','closeButton');
+ objCloseButton.style.position = 'absolute';
+ objCloseButton.style.zIndex = '200';
+ objLink.appendChild(objCloseButton);
+
+ return false;
+ }
+
+ imgPreloadCloseButton.src = closeButton;
+
+ // create image
+ var objImage = document.createElement("img");
+ objImage.setAttribute('id','lightboxImage');
+ objLink.appendChild(objImage);
+
+ // create details div, a container for the caption and keyboard message
+ var objLightboxDetails = document.createElement("div");
+ objLightboxDetails.setAttribute('id','lightboxDetails');
+ objLightbox.appendChild(objLightboxDetails);
+
+ // create caption
+ var objCaption = document.createElement("div");
+ objCaption.setAttribute('id','lightboxCaption');
+ objCaption.style.display = 'none';
+ objLightboxDetails.appendChild(objCaption);
+
+ // create keyboard message
+ var objKeyboardMsg = document.createElement("div");
+ objKeyboardMsg.setAttribute('id','keyboardMsg');
+ objKeyboardMsg.innerHTML = 'press <kbd>x</kbd> to close';
+ objLightboxDetails.appendChild(objKeyboardMsg);
+
+
+}
+
+
+
+
+//
+// addLoadEvent()
+// Adds event to window.onload without overwriting currently assigned onload f…
+// Function found at Simon Willison's weblog - http://simon.incutio.com/
+//
+function addLoadEvent(func)
+{
+ var oldonload = window.onload;
+ if (typeof window.onload != 'function'){
+ window.onload = func;
+ } else {
+ window.onload = function(){
+ oldonload();
+ func();
+ }
+ }
+
+}
+
+
+
+addLoadEvent(initLightbox); // run initLightbox onLoad
diff --git a/web/public/javascripts/prototype.js b/web/public/javascripts/proto…
@@ -0,0 +1,6001 @@
+/* Prototype JavaScript framework, version 1.7_rc2
+ * (c) 2005-2010 Sam Stephenson
+ *
+ * Prototype is freely distributable under the terms of an MIT-style license.
+ * For details, see the Prototype web site: http://www.prototypejs.org/
+ *
+ *--------------------------------------------------------------------------*/
+
+var Prototype = {
+
+ Version: '1.7_rc2',
+
+ Browser: (function(){
+ var ua = navigator.userAgent;
+ var isOpera = Object.prototype.toString.call(window.opera) == '[object Ope…
+ return {
+ IE: !!window.attachEvent && !isOpera,
+ Opera: isOpera,
+ WebKit: ua.indexOf('AppleWebKit/') > -1,
+ Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
+ MobileSafari: /Apple.*Mobile/.test(ua)
+ }
+ })(),
+
+ BrowserFeatures: {
+ XPath: !!document.evaluate,
+
+ SelectorsAPI: !!document.querySelector,
+
+ ElementExtensions: (function() {
+ var constructor = window.Element || window.HTMLElement;
+ return !!(constructor && constructor.prototype);
+ })(),
+ SpecificElementExtensions: (function() {
+ if (typeof window.HTMLDivElement !== 'undefined')
+ return true;
+
+ var div = document.createElement('div'),
+ form = document.createElement('form'),
+ isSupported = false;
+
+ if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
+ isSupported = true;
+ }
+
+ div = form = null;
+
+ return isSupported;
+ })()
+ },
+
+ ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+ JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+ emptyFunction: function() { },
+
+ K: function(x) { return x }
+};
+
+if (Prototype.Browser.MobileSafari)
+ Prototype.BrowserFeatures.SpecificElementExtensions = false;
+
+
+var Abstract = { };
+
+
+var Try = {
+ these: function() {
+ var returnValue;
+
+ for (var i = 0, length = arguments.length; i < length; i++) {
+ var lambda = arguments[i];
+ try {
+ returnValue = lambda();
+ break;
+ } catch (e) { }
+ }
+
+ return returnValue;
+ }
+};
+
+/* Based on Alex Arnell's inheritance implementation. */
+
+var Class = (function() {
+
+ var IS_DONTENUM_BUGGY = (function(){
+ for (var p in { toString: 1 }) {
+ if (p === 'toString') return false;
+ }
+ return true;
+ })();
+
+ function subclass() {};
+ function create() {
+ var parent = null, properties = $A(arguments);
+ if (Object.isFunction(properties[0]))
+ parent = properties.shift();
+
+ function klass() {
+ this.initialize.apply(this, arguments);
+ }
+
+ Object.extend(klass, Class.Methods);
+ klass.superclass = parent;
+ klass.subclasses = [];
+
+ if (parent) {
+ subclass.prototype = parent.prototype;
+ klass.prototype = new subclass;
+ parent.subclasses.push(klass);
+ }
+
+ for (var i = 0, length = properties.length; i < length; i++)
+ klass.addMethods(properties[i]);
+
+ if (!klass.prototype.initialize)
+ klass.prototype.initialize = Prototype.emptyFunction;
+
+ klass.prototype.constructor = klass;
+ return klass;
+ }
+
+ function addMethods(source) {
+ var ancestor = this.superclass && this.superclass.prototype,
+ properties = Object.keys(source);
+
+ if (IS_DONTENUM_BUGGY) {
+ if (source.toString != Object.prototype.toString)
+ properties.push("toString");
+ if (source.valueOf != Object.prototype.valueOf)
+ properties.push("valueOf");
+ }
+
+ for (var i = 0, length = properties.length; i < length; i++) {
+ var property = properties[i], value = source[property];
+ if (ancestor && Object.isFunction(value) &&
+ value.argumentNames()[0] == "$super") {
+ var method = value;
+ value = (function(m) {
+ return function() { return ancestor[m].apply(this, arguments); };
+ })(property).wrap(method);
+
+ value.valueOf = method.valueOf.bind(method);
+ value.toString = method.toString.bind(method);
+ }
+ this.prototype[property] = value;
+ }
+
+ return this;
+ }
+
+ return {
+ create: create,
+ Methods: {
+ addMethods: addMethods
+ }
+ };
+})();
+(function() {
+
+ var _toString = Object.prototype.toString,
+ NULL_TYPE = 'Null',
+ UNDEFINED_TYPE = 'Undefined',
+ BOOLEAN_TYPE = 'Boolean',
+ NUMBER_TYPE = 'Number',
+ STRING_TYPE = 'String',
+ OBJECT_TYPE = 'Object',
+ BOOLEAN_CLASS = '[object Boolean]',
+ NUMBER_CLASS = '[object Number]',
+ STRING_CLASS = '[object String]',
+ ARRAY_CLASS = '[object Array]',
+ NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON &&
+ typeof JSON.stringify === 'function' &&
+ JSON.stringify(0) === '0' &&
+ typeof JSON.stringify(Prototype.K) === 'undefined';
+
+ function Type(o) {
+ switch(o) {
+ case null: return NULL_TYPE;
+ case (void 0): return UNDEFINED_TYPE;
+ }
+ var type = typeof o;
+ switch(type) {
+ case 'boolean': return BOOLEAN_TYPE;
+ case 'number': return NUMBER_TYPE;
+ case 'string': return STRING_TYPE;
+ }
+ return OBJECT_TYPE;
+ }
+
+ function extend(destination, source) {
+ for (var property in source)
+ destination[property] = source[property];
+ return destination;
+ }
+
+ function inspect(object) {
+ try {
+ if (isUndefined(object)) return 'undefined';
+ if (object === null) return 'null';
+ return object.inspect ? object.inspect() : String(object);
+ } catch (e) {
+ if (e instanceof RangeError) return '...';
+ throw e;
+ }
+ }
+
+ function toJSON(value) {
+ return Str('', { '': value }, []);
+ }
+
+ function Str(key, holder, stack) {
+ var value = holder[key],
+ type = typeof value;
+
+ if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+ var _class = _toString.call(value);
+
+ switch (_class) {
+ case NUMBER_CLASS:
+ case BOOLEAN_CLASS:
+ case STRING_CLASS:
+ value = value.valueOf();
+ }
+
+ switch (value) {
+ case null: return 'null';
+ case true: return 'true';
+ case false: return 'false';
+ }
+
+ type = typeof value;
+ switch (type) {
+ case 'string':
+ return value.inspect(true);
+ case 'number':
+ return isFinite(value) ? String(value) : 'null';
+ case 'object':
+
+ for (var i = 0, length = stack.length; i < length; i++) {
+ if (stack[i] === value) { throw new TypeError(); }
+ }
+ stack.push(value);
+
+ var partial = [];
+ if (_class === ARRAY_CLASS) {
+ for (var i = 0, length = value.length; i < length; i++) {
+ var str = Str(i, value, stack);
+ partial.push(typeof str === 'undefined' ? 'null' : str);
+ }
+ partial = '[' + partial.join(',') + ']';
+ } else {
+ var keys = Object.keys(value);
+ for (var i = 0, length = keys.length; i < length; i++) {
+ var key = keys[i], str = Str(key, value, stack);
+ if (typeof str !== "undefined") {
+ partial.push(key.inspect(true)+ ':' + str);
+ }
+ }
+ partial = '{' + partial.join(',') + '}';
+ }
+ stack.pop();
+ return partial;
+ }
+ }
+
+ function stringify(object) {
+ return JSON.stringify(object);
+ }
+
+ function toQueryString(object) {
+ return $H(object).toQueryString();
+ }
+
+ function toHTML(object) {
+ return object && object.toHTML ? object.toHTML() : String.interpret(object…
+ }
+
+ function keys(object) {
+ if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); }
+ var results = [];
+ for (var property in object) {
+ if (object.hasOwnProperty(property)) {
+ results.push(property);
+ }
+ }
+ return results;
+ }
+
+ function values(object) {
+ var results = [];
+ for (var property in object)
+ results.push(object[property]);
+ return results;
+ }
+
+ function clone(object) {
+ return extend({ }, object);
+ }
+
+ function isElement(object) {
+ return !!(object && object.nodeType == 1);
+ }
+
+ function isArray(object) {
+ return _toString.call(object) === ARRAY_CLASS;
+ }
+
+ var hasNativeIsArray = (typeof Array.isArray == 'function')
+ && Array.isArray([]) && !Array.isArray({});
+
+ if (hasNativeIsArray) {
+ isArray = Array.isArray;
+ }
+
+ function isHash(object) {
+ return object instanceof Hash;
+ }
+
+ function isFunction(object) {
+ return typeof object === "function";
+ }
+
+ function isString(object) {
+ return _toString.call(object) === STRING_CLASS;
+ }
+
+ function isNumber(object) {
+ return _toString.call(object) === NUMBER_CLASS;
+ }
+
+ function isUndefined(object) {
+ return typeof object === "undefined";
+ }
+
+ extend(Object, {
+ extend: extend,
+ inspect: inspect,
+ toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON,
+ toQueryString: toQueryString,
+ toHTML: toHTML,
+ keys: Object.keys || keys,
+ values: values,
+ clone: clone,
+ isElement: isElement,
+ isArray: isArray,
+ isHash: isHash,
+ isFunction: isFunction,
+ isString: isString,
+ isNumber: isNumber,
+ isUndefined: isUndefined
+ });
+})();
+Object.extend(Function.prototype, (function() {
+ var slice = Array.prototype.slice;
+
+ function update(array, args) {
+ var arrayLength = array.length, length = args.length;
+ while (length--) array[arrayLength + length] = args[length];
+ return array;
+ }
+
+ function merge(array, args) {
+ array = slice.call(array, 0);
+ return update(array, args);
+ }
+
+ function argumentNames() {
+ var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
+ .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
+ .replace(/\s+/g, '').split(',');
+ return names.length == 1 && !names[0] ? [] : names;
+ }
+
+ function bind(context) {
+ if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
+ var __method = this, args = slice.call(arguments, 1);
+ return function() {
+ var a = merge(args, arguments);
+ return __method.apply(context, a);
+ }
+ }
+
+ function bindAsEventListener(context) {
+ var __method = this, args = slice.call(arguments, 1);
+ return function(event) {
+ var a = update([event || window.event], args);
+ return __method.apply(context, a);
+ }
+ }
+
+ function curry() {
+ if (!arguments.length) return this;
+ var __method = this, args = slice.call(arguments, 0);
+ return function() {
+ var a = merge(args, arguments);
+ return __method.apply(this, a);
+ }
+ }
+
+ function delay(timeout) {
+ var __method = this, args = slice.call(arguments, 1);
+ timeout = timeout * 1000;
+ return window.setTimeout(function() {
+ return __method.apply(__method, args);
+ }, timeout);
+ }
+
+ function defer() {
+ var args = update([0.01], arguments);
+ return this.delay.apply(this, args);
+ }
+
+ function wrap(wrapper) {
+ var __method = this;
+ return function() {
+ var a = update([__method.bind(this)], arguments);
+ return wrapper.apply(this, a);
+ }
+ }
+
+ function methodize() {
+ if (this._methodized) return this._methodized;
+ var __method = this;
+ return this._methodized = function() {
+ var a = update([this], arguments);
+ return __method.apply(null, a);
+ };
+ }
+
+ return {
+ argumentNames: argumentNames,
+ bind: bind,
+ bindAsEventListener: bindAsEventListener,
+ curry: curry,
+ delay: delay,
+ defer: defer,
+ wrap: wrap,
+ methodize: methodize
+ }
+})());
+
+
+
+(function(proto) {
+
+
+ function toISOString() {
+ return this.getUTCFullYear() + '-' +
+ (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+ this.getUTCDate().toPaddedString(2) + 'T' +
+ this.getUTCHours().toPaddedString(2) + ':' +
+ this.getUTCMinutes().toPaddedString(2) + ':' +
+ this.getUTCSeconds().toPaddedString(2) + 'Z';
+ }
+
+
+ function toJSON() {
+ return this.toISOString();
+ }
+
+ if (!proto.toISOString) proto.toISOString = toISOString;
+ if (!proto.toJSON) proto.toJSON = toJSON;
+
+})(Date.prototype);
+
+
+RegExp.prototype.match = RegExp.prototype.test;
+
+RegExp.escape = function(str) {
+ return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+};
+var PeriodicalExecuter = Class.create({
+ initialize: function(callback, frequency) {
+ this.callback = callback;
+ this.frequency = frequency;
+ this.currentlyExecuting = false;
+
+ this.registerCallback();
+ },
+
+ registerCallback: function() {
+ this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 10…
+ },
+
+ execute: function() {
+ this.callback(this);
+ },
+
+ stop: function() {
+ if (!this.timer) return;
+ clearInterval(this.timer);
+ this.timer = null;
+ },
+
+ onTimerEvent: function() {
+ if (!this.currentlyExecuting) {
+ try {
+ this.currentlyExecuting = true;
+ this.execute();
+ this.currentlyExecuting = false;
+ } catch(e) {
+ this.currentlyExecuting = false;
+ throw e;
+ }
+ }
+ }
+});
+Object.extend(String, {
+ interpret: function(value) {
+ return value == null ? '' : String(value);
+ },
+ specialChar: {
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '\\': '\\\\'
+ }
+});
+
+Object.extend(String.prototype, (function() {
+ var NATIVE_JSON_PARSE_SUPPORT = window.JSON &&
+ typeof JSON.parse === 'function' &&
+ JSON.parse('{"test": true}').test;
+
+ function prepareReplacement(replacement) {
+ if (Object.isFunction(replacement)) return replacement;
+ var template = new Template(replacement);
+ return function(match) { return template.evaluate(match) };
+ }
+
+ function gsub(pattern, replacement) {
+ var result = '', source = this, match;
+ replacement = prepareReplacement(replacement);
+
+ if (Object.isString(pattern))
+ pattern = RegExp.escape(pattern);
+
+ if (!(pattern.length || pattern.source)) {
+ replacement = replacement('');
+ return replacement + source.split('').join(replacement) + replacement;
+ }
+
+ while (source.length > 0) {
+ if (match = source.match(pattern)) {
+ result += source.slice(0, match.index);
+ result += String.interpret(replacement(match));
+ source = source.slice(match.index + match[0].length);
+ } else {
+ result += source, source = '';
+ }
+ }
+ return result;
+ }
+
+ function sub(pattern, replacement, count) {
+ replacement = prepareReplacement(replacement);
+ count = Object.isUndefined(count) ? 1 : count;
+
+ return this.gsub(pattern, function(match) {
+ if (--count < 0) return match[0];
+ return replacement(match);
+ });
+ }
+
+ function scan(pattern, iterator) {
+ this.gsub(pattern, iterator);
+ return String(this);
+ }
+
+ function truncate(length, truncation) {
+ length = length || 30;
+ truncation = Object.isUndefined(truncation) ? '...' : truncation;
+ return this.length > length ?
+ this.slice(0, length - truncation.length) + truncation : String(this);
+ }
+
+ function strip() {
+ return this.replace(/^\s+/, '').replace(/\s+$/, '');
+ }
+
+ function stripTags() {
+ return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, '');
+ }
+
+ function stripScripts() {
+ return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+ }
+
+ function extractScripts() {
+ var matchAll = new RegExp(Prototype.ScriptFragment, 'img'),
+ matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+ return (this.match(matchAll) || []).map(function(scriptTag) {
+ return (scriptTag.match(matchOne) || ['', ''])[1];
+ });
+ }
+
+ function evalScripts() {
+ return this.extractScripts().map(function(script) { return eval(script) });
+ }
+
+ function escapeHTML() {
+ return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'…
+ }
+
+ function unescapeHTML() {
+ return this.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(…
+ }
+
+
+ function toQueryParams(separator) {
+ var match = this.strip().match(/([^?#]*)(#.*)?$/);
+ if (!match) return { };
+
+ return match[1].split(separator || '&').inject({ }, function(hash, pair) {
+ if ((pair = pair.split('='))[0]) {
+ var key = decodeURIComponent(pair.shift()),
+ value = pair.length > 1 ? pair.join('=') : pair[0];
+
+ if (value != undefined) value = decodeURIComponent(value);
+
+ if (key in hash) {
+ if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
+ hash[key].push(value);
+ }
+ else hash[key] = value;
+ }
+ return hash;
+ });
+ }
+
+ function toArray() {
+ return this.split('');
+ }
+
+ function succ() {
+ return this.slice(0, this.length - 1) +
+ String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+ }
+
+ function times(count) {
+ return count < 1 ? '' : new Array(count + 1).join(this);
+ }
+
+ function camelize() {
+ return this.replace(/-+(.)?/g, function(match, chr) {
+ return chr ? chr.toUpperCase() : '';
+ });
+ }
+
+ function capitalize() {
+ return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+ }
+
+ function underscore() {
+ return this.replace(/::/g, '/')
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
+ .replace(/([a-z\d])([A-Z])/g, '$1_$2')
+ .replace(/-/g, '_')
+ .toLowerCase();
+ }
+
+ function dasherize() {
+ return this.replace(/_/g, '-');
+ }
+
+ function inspect(useDoubleQuotes) {
+ var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) {
+ if (character in String.specialChar) {
+ return String.specialChar[character];
+ }
+ return '\\u00' + character.charCodeAt().toPaddedString(2, 16);
+ });
+ if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+ return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+ }
+
+ function unfilterJSON(filter) {
+ return this.replace(filter || Prototype.JSONFilter, '$1');
+ }
+
+ function isJSON() {
+ var str = this;
+ if (str.blank()) return false;
+ str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@');
+ str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\…
+ str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+ return (/^[\],:{}\s]*$/).test(str);
+ }
+
+ function evalJSON(sanitize) {
+ var json = this.unfilterJSON(),
+ cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-…
+ if (cx.test(json)) {
+ json = json.replace(cx, function (a) {
+ return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+ try {
+ if (!sanitize || json.isJSON()) return eval('(' + json + ')');
+ } catch (e) { }
+ throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+ }
+
+ function parseJSON() {
+ var json = this.unfilterJSON();
+ return JSON.parse(json);
+ }
+
+ function include(pattern) {
+ return this.indexOf(pattern) > -1;
+ }
+
+ function startsWith(pattern) {
+ return this.lastIndexOf(pattern, 0) === 0;
+ }
+
+ function endsWith(pattern) {
+ var d = this.length - pattern.length;
+ return d >= 0 && this.indexOf(pattern, d) === d;
+ }
+
+ function empty() {
+ return this == '';
+ }
+
+ function blank() {
+ return /^\s*$/.test(this);
+ }
+
+ function interpolate(object, pattern) {
+ return new Template(this, pattern).evaluate(object);
+ }
+
+ return {
+ gsub: gsub,
+ sub: sub,
+ scan: scan,
+ truncate: truncate,
+ strip: String.prototype.trim || strip,
+ stripTags: stripTags,
+ stripScripts: stripScripts,
+ extractScripts: extractScripts,
+ evalScripts: evalScripts,
+ escapeHTML: escapeHTML,
+ unescapeHTML: unescapeHTML,
+ toQueryParams: toQueryParams,
+ parseQuery: toQueryParams,
+ toArray: toArray,
+ succ: succ,
+ times: times,
+ camelize: camelize,
+ capitalize: capitalize,
+ underscore: underscore,
+ dasherize: dasherize,
+ inspect: inspect,
+ unfilterJSON: unfilterJSON,
+ isJSON: isJSON,
+ evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON,
+ include: include,
+ startsWith: startsWith,
+ endsWith: endsWith,
+ empty: empty,
+ blank: blank,
+ interpolate: interpolate
+ };
+})());
+
+var Template = Class.create({
+ initialize: function(template, pattern) {
+ this.template = template.toString();
+ this.pattern = pattern || Template.Pattern;
+ },
+
+ evaluate: function(object) {
+ if (object && Object.isFunction(object.toTemplateReplacements))
+ object = object.toTemplateReplacements();
+
+ return this.template.gsub(this.pattern, function(match) {
+ if (object == null) return (match[1] + '');
+
+ var before = match[1] || '';
+ if (before == '\\') return match[2];
+
+ var ctx = object, expr = match[3],
+ pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+
+ match = pattern.exec(expr);
+ if (match == null) return before;
+
+ while (match != null) {
+ var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') …
+ ctx = ctx[comp];
+ if (null == ctx || '' == match[3]) break;
+ expr = expr.substring('[' == match[3] ? match[1].length : match[0].len…
+ match = pattern.exec(expr);
+ }
+
+ return before + String.interpret(ctx);
+ });
+ }
+});
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+
+var $break = { };
+
+var Enumerable = (function() {
+ function each(iterator, context) {
+ var index = 0;
+ try {
+ this._each(function(value) {
+ iterator.call(context, value, index++);
+ });
+ } catch (e) {
+ if (e != $break) throw e;
+ }
+ return this;
+ }
+
+ function eachSlice(number, iterator, context) {
+ var index = -number, slices = [], array = this.toArray();
+ if (number < 1) return array;
+ while ((index += number) < array.length)
+ slices.push(array.slice(index, index+number));
+ return slices.collect(iterator, context);
+ }
+
+ function all(iterator, context) {
+ iterator = iterator || Prototype.K;
+ var result = true;
+ this.each(function(value, index) {
+ result = result && !!iterator.call(context, value, index);
+ if (!result) throw $break;
+ });
+ return result;
+ }
+
+ function any(iterator, context) {
+ iterator = iterator || Prototype.K;
+ var result = false;
+ this.each(function(value, index) {
+ if (result = !!iterator.call(context, value, index))
+ throw $break;
+ });
+ return result;
+ }
+
+ function collect(iterator, context) {
+ iterator = iterator || Prototype.K;
+ var results = [];
+ this.each(function(value, index) {
+ results.push(iterator.call(context, value, index));
+ });
+ return results;
+ }
+
+ function detect(iterator, context) {
+ var result;
+ this.each(function(value, index) {
+ if (iterator.call(context, value, index)) {
+ result = value;
+ throw $break;
+ }
+ });
+ return result;
+ }
+
+ function findAll(iterator, context) {
+ var results = [];
+ this.each(function(value, index) {
+ if (iterator.call(context, value, index))
+ results.push(value);
+ });
+ return results;
+ }
+
+ function grep(filter, iterator, context) {
+ iterator = iterator || Prototype.K;
+ var results = [];
+
+ if (Object.isString(filter))
+ filter = new RegExp(RegExp.escape(filter));
+
+ this.each(function(value, index) {
+ if (filter.match(value))
+ results.push(iterator.call(context, value, index));
+ });
+ return results;
+ }
+
+ function include(object) {
+ if (Object.isFunction(this.indexOf))
+ if (this.indexOf(object) != -1) return true;
+
+ var found = false;
+ this.each(function(value) {
+ if (value == object) {
+ found = true;
+ throw $break;
+ }
+ });
+ return found;
+ }
+
+ function inGroupsOf(number, fillWith) {
+ fillWith = Object.isUndefined(fillWith) ? null : fillWith;
+ return this.eachSlice(number, function(slice) {
+ while(slice.length < number) slice.push(fillWith);
+ return slice;
+ });
+ }
+
+ function inject(memo, iterator, context) {
+ this.each(function(value, index) {
+ memo = iterator.call(context, memo, value, index);
+ });
+ return memo;
+ }
+
+ function invoke(method) {
+ var args = $A(arguments).slice(1);
+ return this.map(function(value) {
+ return value[method].apply(value, args);
+ });
+ }
+
+ function max(iterator, context) {
+ iterator = iterator || Prototype.K;
+ var result;
+ this.each(function(value, index) {
+ value = iterator.call(context, value, index);
+ if (result == null || value >= result)
+ result = value;
+ });
+ return result;
+ }
+
+ function min(iterator, context) {
+ iterator = iterator || Prototype.K;
+ var result;
+ this.each(function(value, index) {
+ value = iterator.call(context, value, index);
+ if (result == null || value < result)
+ result = value;
+ });
+ return result;
+ }
+
+ function partition(iterator, context) {
+ iterator = iterator || Prototype.K;
+ var trues = [], falses = [];
+ this.each(function(value, index) {
+ (iterator.call(context, value, index) ?
+ trues : falses).push(value);
+ });
+ return [trues, falses];
+ }
+
+ function pluck(property) {
+ var results = [];
+ this.each(function(value) {
+ results.push(value[property]);
+ });
+ return results;
+ }
+
+ function reject(iterator, context) {
+ var results = [];
+ this.each(function(value, index) {
+ if (!iterator.call(context, value, index))
+ results.push(value);
+ });
+ return results;
+ }
+
+ function sortBy(iterator, context) {
+ return this.map(function(value, index) {
+ return {
+ value: value,
+ criteria: iterator.call(context, value, index)
+ };
+ }).sort(function(left, right) {
+ var a = left.criteria, b = right.criteria;
+ return a < b ? -1 : a > b ? 1 : 0;
+ }).pluck('value');
+ }
+
+ function toArray() {
+ return this.map();
+ }
+
+ function zip() {
+ var iterator = Prototype.K, args = $A(arguments);
+ if (Object.isFunction(args.last()))
+ iterator = args.pop();
+
+ var collections = [this].concat(args).map($A);
+ return this.map(function(value, index) {
+ return iterator(collections.pluck(index));
+ });
+ }
+
+ function size() {
+ return this.toArray().length;
+ }
+
+ function inspect() {
+ return '#<Enumerable:' + this.toArray().inspect() + '>';
+ }
+
+
+
+
+
+
+
+
+
+ return {
+ each: each,
+ eachSlice: eachSlice,
+ all: all,
+ every: all,
+ any: any,
+ some: any,
+ collect: collect,
+ map: collect,
+ detect: detect,
+ findAll: findAll,
+ select: findAll,
+ filter: findAll,
+ grep: grep,
+ include: include,
+ member: include,
+ inGroupsOf: inGroupsOf,
+ inject: inject,
+ invoke: invoke,
+ max: max,
+ min: min,
+ partition: partition,
+ pluck: pluck,
+ reject: reject,
+ sortBy: sortBy,
+ toArray: toArray,
+ entries: toArray,
+ zip: zip,
+ size: size,
+ inspect: inspect,
+ find: detect
+ };
+})();
+
+function $A(iterable) {
+ if (!iterable) return [];
+ if ('toArray' in Object(iterable)) return iterable.toArray();
+ var length = iterable.length || 0, results = new Array(length);
+ while (length--) results[length] = iterable[length];
+ return results;
+}
+
+
+function $w(string) {
+ if (!Object.isString(string)) return [];
+ string = string.strip();
+ return string ? string.split(/\s+/) : [];
+}
+
+Array.from = $A;
+
+
+(function() {
+ var arrayProto = Array.prototype,
+ slice = arrayProto.slice,
+ _each = arrayProto.forEach; // use native browser JS 1.6 implementation …
+
+ function each(iterator) {
+ for (var i = 0, length = this.length; i < length; i++)
+ iterator(this[i]);
+ }
+ if (!_each) _each = each;
+
+ function clear() {
+ this.length = 0;
+ return this;
+ }
+
+ function first() {
+ return this[0];
+ }
+
+ function last() {
+ return this[this.length - 1];
+ }
+
+ function compact() {
+ return this.select(function(value) {
+ return value != null;
+ });
+ }
+
+ function flatten() {
+ return this.inject([], function(array, value) {
+ if (Object.isArray(value))
+ return array.concat(value.flatten());
+ array.push(value);
+ return array;
+ });
+ }
+
+ function without() {
+ var values = slice.call(arguments, 0);
+ return this.select(function(value) {
+ return !values.include(value);
+ });
+ }
+
+ function reverse(inline) {
+ return (inline === false ? this.toArray() : this)._reverse();
+ }
+
+ function uniq(sorted) {
+ return this.inject([], function(array, value, index) {
+ if (0 == index || (sorted ? array.last() != value : !array.include(value…
+ array.push(value);
+ return array;
+ });
+ }
+
+ function intersect(array) {
+ return this.uniq().findAll(function(item) {
+ return array.detect(function(value) { return item === value });
+ });
+ }
+
+
+ function clone() {
+ return slice.call(this, 0);
+ }
+
+ function size() {
+ return this.length;
+ }
+
+ function inspect() {
+ return '[' + this.map(Object.inspect).join(', ') + ']';
+ }
+
+ function indexOf(item, i) {
+ i || (i = 0);
+ var length = this.length;
+ if (i < 0) i = length + i;
+ for (; i < length; i++)
+ if (this[i] === item) return i;
+ return -1;
+ }
+
+ function lastIndexOf(item, i) {
+ i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
+ var n = this.slice(0, i).reverse().indexOf(item);
+ return (n < 0) ? n : i - n - 1;
+ }
+
+ function concat() {
+ var array = slice.call(this, 0), item;
+ for (var i = 0, length = arguments.length; i < length; i++) {
+ item = arguments[i];
+ if (Object.isArray(item) && !('callee' in item)) {
+ for (var j = 0, arrayLength = item.length; j < arrayLength; j++)
+ array.push(item[j]);
+ } else {
+ array.push(item);
+ }
+ }
+ return array;
+ }
+
+ Object.extend(arrayProto, Enumerable);
+
+ if (!arrayProto._reverse)
+ arrayProto._reverse = arrayProto.reverse;
+
+ Object.extend(arrayProto, {
+ _each: _each,
+ clear: clear,
+ first: first,
+ last: last,
+ compact: compact,
+ flatten: flatten,
+ without: without,
+ reverse: reverse,
+ uniq: uniq,
+ intersect: intersect,
+ clone: clone,
+ toArray: clone,
+ size: size,
+ inspect: inspect
+ });
+
+ var CONCAT_ARGUMENTS_BUGGY = (function() {
+ return [].concat(arguments)[0][0] !== 1;
+ })(1,2)
+
+ if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat;
+
+ if (!arrayProto.indexOf) arrayProto.indexOf = indexOf;
+ if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf;
+})();
+function $H(object) {
+ return new Hash(object);
+};
+
+var Hash = Class.create(Enumerable, (function() {
+ function initialize(object) {
+ this._object = Object.isHash(object) ? object.toObject() : Object.clone(ob…
+ }
+
+
+ function _each(iterator) {
+ for (var key in this._object) {
+ var value = this._object[key], pair = [key, value];
+ pair.key = key;
+ pair.value = value;
+ iterator(pair);
+ }
+ }
+
+ function set(key, value) {
+ return this._object[key] = value;
+ }
+
+ function get(key) {
+ if (this._object[key] !== Object.prototype[key])
+ return this._object[key];
+ }
+
+ function unset(key) {
+ var value = this._object[key];
+ delete this._object[key];
+ return value;
+ }
+
+ function toObject() {
+ return Object.clone(this._object);
+ }
+
+
+
+ function keys() {
+ return this.pluck('key');
+ }
+
+ function values() {
+ return this.pluck('value');
+ }
+
+ function index(value) {
+ var match = this.detect(function(pair) {
+ return pair.value === value;
+ });
+ return match && match.key;
+ }
+
+ function merge(object) {
+ return this.clone().update(object);
+ }
+
+ function update(object) {
+ return new Hash(object).inject(this, function(result, pair) {
+ result.set(pair.key, pair.value);
+ return result;
+ });
+ }
+
+ function toQueryPair(key, value) {
+ if (Object.isUndefined(value)) return key;
+ return key + '=' + encodeURIComponent(String.interpret(value));
+ }
+
+ function toQueryString() {
+ return this.inject([], function(results, pair) {
+ var key = encodeURIComponent(pair.key), values = pair.value;
+
+ if (values && typeof values == 'object') {
+ if (Object.isArray(values))
+ return results.concat(values.map(toQueryPair.curry(key)));
+ } else results.push(toQueryPair(key, values));
+ return results;
+ }).join('&');
+ }
+
+ function inspect() {
+ return '#<Hash:{' + this.map(function(pair) {
+ return pair.map(Object.inspect).join(': ');
+ }).join(', ') + '}>';
+ }
+
+ function clone() {
+ return new Hash(this);
+ }
+
+ return {
+ initialize: initialize,
+ _each: _each,
+ set: set,
+ get: get,
+ unset: unset,
+ toObject: toObject,
+ toTemplateReplacements: toObject,
+ keys: keys,
+ values: values,
+ index: index,
+ merge: merge,
+ update: update,
+ toQueryString: toQueryString,
+ inspect: inspect,
+ toJSON: toObject,
+ clone: clone
+ };
+})());
+
+Hash.from = $H;
+Object.extend(Number.prototype, (function() {
+ function toColorPart() {
+ return this.toPaddedString(2, 16);
+ }
+
+ function succ() {
+ return this + 1;
+ }
+
+ function times(iterator, context) {
+ $R(0, this, true).each(iterator, context);
+ return this;
+ }
+
+ function toPaddedString(length, radix) {
+ var string = this.toString(radix || 10);
+ return '0'.times(length - string.length) + string;
+ }
+
+ function abs() {
+ return Math.abs(this);
+ }
+
+ function round() {
+ return Math.round(this);
+ }
+
+ function ceil() {
+ return Math.ceil(this);
+ }
+
+ function floor() {
+ return Math.floor(this);
+ }
+
+ return {
+ toColorPart: toColorPart,
+ succ: succ,
+ times: times,
+ toPaddedString: toPaddedString,
+ abs: abs,
+ round: round,
+ ceil: ceil,
+ floor: floor
+ };
+})());
+
+function $R(start, end, exclusive) {
+ return new ObjectRange(start, end, exclusive);
+}
+
+var ObjectRange = Class.create(Enumerable, (function() {
+ function initialize(start, end, exclusive) {
+ this.start = start;
+ this.end = end;
+ this.exclusive = exclusive;
+ }
+
+ function _each(iterator) {
+ var value = this.start;
+ while (this.include(value)) {
+ iterator(value);
+ value = value.succ();
+ }
+ }
+
+ function include(value) {
+ if (value < this.start)
+ return false;
+ if (this.exclusive)
+ return value < this.end;
+ return value <= this.end;
+ }
+
+ return {
+ initialize: initialize,
+ _each: _each,
+ include: include
+ };
+})());
+
+
+
+var Ajax = {
+ getTransport: function() {
+ return Try.these(
+ function() {return new XMLHttpRequest()},
+ function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+ function() {return new ActiveXObject('Microsoft.XMLHTTP')}
+ ) || false;
+ },
+
+ activeRequestCount: 0
+};
+
+Ajax.Responders = {
+ responders: [],
+
+ _each: function(iterator) {
+ this.responders._each(iterator);
+ },
+
+ register: function(responder) {
+ if (!this.include(responder))
+ this.responders.push(responder);
+ },
+
+ unregister: function(responder) {
+ this.responders = this.responders.without(responder);
+ },
+
+ dispatch: function(callback, request, transport, json) {
+ this.each(function(responder) {
+ if (Object.isFunction(responder[callback])) {
+ try {
+ responder[callback].apply(responder, [request, transport, json]);
+ } catch (e) { }
+ }
+ });
+ }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+ onCreate: function() { Ajax.activeRequestCount++ },
+ onComplete: function() { Ajax.activeRequestCount-- }
+});
+Ajax.Base = Class.create({
+ initialize: function(options) {
+ this.options = {
+ method: 'post',
+ asynchronous: true,
+ contentType: 'application/x-www-form-urlencoded',
+ encoding: 'UTF-8',
+ parameters: '',
+ evalJSON: true,
+ evalJS: true
+ };
+ Object.extend(this.options, options || { });
+
+ this.options.method = this.options.method.toLowerCase();
+
+ if (Object.isString(this.options.parameters))
+ this.options.parameters = this.options.parameters.toQueryParams();
+ else if (Object.isHash(this.options.parameters))
+ this.options.parameters = this.options.parameters.toObject();
+ }
+});
+Ajax.Request = Class.create(Ajax.Base, {
+ _complete: false,
+
+ initialize: function($super, url, options) {
+ $super(options);
+ this.transport = Ajax.getTransport();
+ this.request(url);
+ },
+
+ request: function(url) {
+ this.url = url;
+ this.method = this.options.method;
+ var params = Object.clone(this.options.parameters);
+
+ if (!['get', 'post'].include(this.method)) {
+ params['_method'] = this.method;
+ this.method = 'post';
+ }
+
+ this.parameters = params;
+
+ if (params = Object.toQueryString(params)) {
+ if (this.method == 'get')
+ this.url += (this.url.include('?') ? '&' : '?') + params;
+ else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
+ params += '&_=';
+ }
+
+ try {
+ var response = new Ajax.Response(this);
+ if (this.options.onCreate) this.options.onCreate(response);
+ Ajax.Responders.dispatch('onCreate', this, response);
+
+ this.transport.open(this.method.toUpperCase(), this.url,
+ this.options.asynchronous);
+
+ if (this.options.asynchronous) this.respondToReadyState.bind(this).defer…
+
+ this.transport.onreadystatechange = this.onStateChange.bind(this);
+ this.setRequestHeaders();
+
+ this.body = this.method == 'post' ? (this.options.postBody || params) : …
+ this.transport.send(this.body);
+
+ /* Force Firefox to handle ready state 4 for synchronous requests */
+ if (!this.options.asynchronous && this.transport.overrideMimeType)
+ this.onStateChange();
+
+ }
+ catch (e) {
+ this.dispatchException(e);
+ }
+ },
+
+ onStateChange: function() {
+ var readyState = this.transport.readyState;
+ if (readyState > 1 && !((readyState == 4) && this._complete))
+ this.respondToReadyState(this.transport.readyState);
+ },
+
+ setRequestHeaders: function() {
+ var headers = {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'X-Prototype-Version': Prototype.Version,
+ 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+ };
+
+ if (this.method == 'post') {
+ headers['Content-type'] = this.options.contentType +
+ (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+ /* Force "Connection: close" for older Mozilla browsers to work
+ * around a bug where XMLHttpRequest sends an incorrect
+ * Content-length header. See Mozilla Bugzilla #246651.
+ */
+ if (this.transport.overrideMimeType &&
+ (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+ headers['Connection'] = 'close';
+ }
+
+ if (typeof this.options.requestHeaders == 'object') {
+ var extras = this.options.requestHeaders;
+
+ if (Object.isFunction(extras.push))
+ for (var i = 0, length = extras.length; i < length; i += 2)
+ headers[extras[i]] = extras[i+1];
+ else
+ $H(extras).each(function(pair) { headers[pair.key] = pair.value });
+ }
+
+ for (var name in headers)
+ this.transport.setRequestHeader(name, headers[name]);
+ },
+
+ success: function() {
+ var status = this.getStatus();
+ return !status || (status >= 200 && status < 300);
+ },
+
+ getStatus: function() {
+ try {
+ return this.transport.status || 0;
+ } catch (e) { return 0 }
+ },
+
+ respondToReadyState: function(readyState) {
+ var state = Ajax.Request.Events[readyState], response = new Ajax.Response(…
+
+ if (state == 'Complete') {
+ try {
+ this._complete = true;
+ (this.options['on' + response.status]
+ || this.options['on' + (this.success() ? 'Success' : 'Failure')]
+ || Prototype.emptyFunction)(response, response.headerJSON);
+ } catch (e) {
+ this.dispatchException(e);
+ }
+
+ var contentType = response.getHeader('Content-type');
+ if (this.options.evalJS == 'force'
+ || (this.options.evalJS && this.isSameOrigin() && contentType
+ && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script…
+ this.evalResponse();
+ }
+
+ try {
+ (this.options['on' + state] || Prototype.emptyFunction)(response, respon…
+ Ajax.Responders.dispatch('on' + state, this, response, response.headerJS…
+ } catch (e) {
+ this.dispatchException(e);
+ }
+
+ if (state == 'Complete') {
+ this.transport.onreadystatechange = Prototype.emptyFunction;
+ }
+ },
+
+ isSameOrigin: function() {
+ var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
+ return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
+ protocol: location.protocol,
+ domain: document.domain,
+ port: location.port ? ':' + location.port : ''
+ }));
+ },
+
+ getHeader: function(name) {
+ try {
+ return this.transport.getResponseHeader(name) || null;
+ } catch (e) { return null; }
+ },
+
+ evalResponse: function() {
+ try {
+ return eval((this.transport.responseText || '').unfilterJSON());
+ } catch (e) {
+ this.dispatchException(e);
+ }
+ },
+
+ dispatchException: function(exception) {
+ (this.options.onException || Prototype.emptyFunction)(this, exception);
+ Ajax.Responders.dispatch('onException', this, exception);
+ }
+});
+
+Ajax.Request.Events =
+ ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+
+
+
+
+
+
+
+Ajax.Response = Class.create({
+ initialize: function(request){
+ this.request = request;
+ var transport = this.transport = request.transport,
+ readyState = this.readyState = transport.readyState;
+
+ if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+ this.status = this.getStatus();
+ this.statusText = this.getStatusText();
+ this.responseText = String.interpret(transport.responseText);
+ this.headerJSON = this._getHeaderJSON();
+ }
+
+ if (readyState == 4) {
+ var xml = transport.responseXML;
+ this.responseXML = Object.isUndefined(xml) ? null : xml;
+ this.responseJSON = this._getResponseJSON();
+ }
+ },
+
+ status: 0,
+
+ statusText: '',
+
+ getStatus: Ajax.Request.prototype.getStatus,
+
+ getStatusText: function() {
+ try {
+ return this.transport.statusText || '';
+ } catch (e) { return '' }
+ },
+
+ getHeader: Ajax.Request.prototype.getHeader,
+
+ getAllHeaders: function() {
+ try {
+ return this.getAllResponseHeaders();
+ } catch (e) { return null }
+ },
+
+ getResponseHeader: function(name) {
+ return this.transport.getResponseHeader(name);
+ },
+
+ getAllResponseHeaders: function() {
+ return this.transport.getAllResponseHeaders();
+ },
+
+ _getHeaderJSON: function() {
+ var json = this.getHeader('X-JSON');
+ if (!json) return null;
+ json = decodeURIComponent(escape(json));
+ try {
+ return json.evalJSON(this.request.options.sanitizeJSON ||
+ !this.request.isSameOrigin());
+ } catch (e) {
+ this.request.dispatchException(e);
+ }
+ },
+
+ _getResponseJSON: function() {
+ var options = this.request.options;
+ if (!options.evalJSON || (options.evalJSON != 'force' &&
+ !(this.getHeader('Content-type') || '').include('application/json')) ||
+ this.responseText.blank())
+ return null;
+ try {
+ return this.responseText.evalJSON(options.sanitizeJSON ||
+ !this.request.isSameOrigin());
+ } catch (e) {
+ this.request.dispatchException(e);
+ }
+ }
+});
+
+Ajax.Updater = Class.create(Ajax.Request, {
+ initialize: function($super, container, url, options) {
+ this.container = {
+ success: (container.success || container),
+ failure: (container.failure || (container.success ? null : container))
+ };
+
+ options = Object.clone(options);
+ var onComplete = options.onComplete;
+ options.onComplete = (function(response, json) {
+ this.updateContent(response.responseText);
+ if (Object.isFunction(onComplete)) onComplete(response, json);
+ }).bind(this);
+
+ $super(url, options);
+ },
+
+ updateContent: function(responseText) {
+ var receiver = this.container[this.success() ? 'success' : 'failure'],
+ options = this.options;
+
+ if (!options.evalScripts) responseText = responseText.stripScripts();
+
+ if (receiver = $(receiver)) {
+ if (options.insertion) {
+ if (Object.isString(options.insertion)) {
+ var insertion = { }; insertion[options.insertion] = responseText;
+ receiver.insert(insertion);
+ }
+ else options.insertion(receiver, responseText);
+ }
+ else receiver.update(responseText);
+ }
+ }
+});
+
+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
+ initialize: function($super, container, url, options) {
+ $super(options);
+ this.onComplete = this.options.onComplete;
+
+ this.frequency = (this.options.frequency || 2);
+ this.decay = (this.options.decay || 1);
+
+ this.updater = { };
+ this.container = container;
+ this.url = url;
+
+ this.start();
+ },
+
+ start: function() {
+ this.options.onComplete = this.updateComplete.bind(this);
+ this.onTimerEvent();
+ },
+
+ stop: function() {
+ this.updater.options.onComplete = undefined;
+ clearTimeout(this.timer);
+ (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+ },
+
+ updateComplete: function(response) {
+ if (this.options.decay) {
+ this.decay = (response.responseText == this.lastText ?
+ this.decay * this.options.decay : 1);
+
+ this.lastText = response.responseText;
+ }
+ this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequenc…
+ },
+
+ onTimerEvent: function() {
+ this.updater = new Ajax.Updater(this.container, this.url, this.options);
+ }
+});
+
+
+function $(element) {
+ if (arguments.length > 1) {
+ for (var i = 0, elements = [], length = arguments.length; i < length; i++)
+ elements.push($(arguments[i]));
+ return elements;
+ }
+ if (Object.isString(element))
+ element = document.getElementById(element);
+ return Element.extend(element);
+}
+
+if (Prototype.BrowserFeatures.XPath) {
+ document._getElementsByXPath = function(expression, parentElement) {
+ var results = [];
+ var query = document.evaluate(expression, $(parentElement) || document,
+ null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+ for (var i = 0, length = query.snapshotLength; i < length; i++)
+ results.push(Element.extend(query.snapshotItem(i)));
+ return results;
+ };
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!Node) var Node = { };
+
+if (!Node.ELEMENT_NODE) {
+ Object.extend(Node, {
+ ELEMENT_NODE: 1,
+ ATTRIBUTE_NODE: 2,
+ TEXT_NODE: 3,
+ CDATA_SECTION_NODE: 4,
+ ENTITY_REFERENCE_NODE: 5,
+ ENTITY_NODE: 6,
+ PROCESSING_INSTRUCTION_NODE: 7,
+ COMMENT_NODE: 8,
+ DOCUMENT_NODE: 9,
+ DOCUMENT_TYPE_NODE: 10,
+ DOCUMENT_FRAGMENT_NODE: 11,
+ NOTATION_NODE: 12
+ });
+}
+
+
+
+(function(global) {
+
+ var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){
+ try {
+ var el = document.createElement('<input name="x">');
+ return el.tagName.toLowerCase() === 'input' && el.name === 'x';
+ }
+ catch(err) {
+ return false;
+ }
+ })();
+
+ var element = global.Element;
+
+ global.Element = function(tagName, attributes) {
+ attributes = attributes || { };
+ tagName = tagName.toLowerCase();
+ var cache = Element.cache;
+ if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) {
+ tagName = '<' + tagName + ' name="' + attributes.name + '">';
+ delete attributes.name;
+ return Element.writeAttribute(document.createElement(tagName), attribute…
+ }
+ if (!cache[tagName]) cache[tagName] = Element.extend(document.createElemen…
+ return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
+ };
+
+ Object.extend(global.Element, element || { });
+ if (element) global.Element.prototype = element.prototype;
+
+})(this);
+
+Element.idCounter = 1;
+Element.cache = { };
+
+function purgeElement(element) {
+ var uid = element._prototypeUID;
+ if (uid) {
+ Element.stopObserving(element);
+ element._prototypeUID = void 0;
+ delete Element.Storage[uid];
+ }
+}
+
+Element.Methods = {
+ visible: function(element) {
+ return $(element).style.display != 'none';
+ },
+
+ toggle: function(element) {
+ element = $(element);
+ Element[Element.visible(element) ? 'hide' : 'show'](element);
+ return element;
+ },
+
+ hide: function(element) {
+ element = $(element);
+ element.style.display = 'none';
+ return element;
+ },
+
+ show: function(element) {
+ element = $(element);
+ element.style.display = '';
+ return element;
+ },
+
+ remove: function(element) {
+ element = $(element);
+ element.parentNode.removeChild(element);
+ return element;
+ },
+
+ update: (function(){
+
+ var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){
+ var el = document.createElement("select"),
+ isBuggy = true;
+ el.innerHTML = "<option value=\"test\">test</option>";
+ if (el.options && el.options[0]) {
+ isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION";
+ }
+ el = null;
+ return isBuggy;
+ })();
+
+ var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){
+ try {
+ var el = document.createElement("table");
+ if (el && el.tBodies) {
+ el.innerHTML = "<tbody><tr><td>test</td></tr></tbody>";
+ var isBuggy = typeof el.tBodies[0] == "undefined";
+ el = null;
+ return isBuggy;
+ }
+ } catch (e) {
+ return true;
+ }
+ })();
+
+ var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () {
+ var s = document.createElement("script"),
+ isBuggy = false;
+ try {
+ s.appendChild(document.createTextNode(""));
+ isBuggy = !s.firstChild ||
+ s.firstChild && s.firstChild.nodeType !== 3;
+ } catch (e) {
+ isBuggy = true;
+ }
+ s = null;
+ return isBuggy;
+ })();
+
+ function update(element, content) {
+ element = $(element);
+
+ var descendants = element.getElementsByTagName('*'),
+ i = descendants.length;
+ while (i--) purgeElement(descendants[i]);
+
+ if (content && content.toElement)
+ content = content.toElement();
+
+ if (Object.isElement(content))
+ return element.update().insert(content);
+
+ content = Object.toHTML(content);
+
+ var tagName = element.tagName.toUpperCase();
+
+ if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) {
+ element.text = content;
+ return element;
+ }
+
+ if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) {
+ if (tagName in Element._insertionTranslations.tags) {
+ while (element.firstChild) {
+ element.removeChild(element.firstChild);
+ }
+ Element._getContentFromAnonymousElement(tagName, content.stripScript…
+ .each(function(node) {
+ element.appendChild(node)
+ });
+ }
+ else {
+ element.innerHTML = content.stripScripts();
+ }
+ }
+ else {
+ element.innerHTML = content.stripScripts();
+ }
+
+ content.evalScripts.bind(content).defer();
+ return element;
+ }
+
+ return update;
+ })(),
+
+ replace: function(element, content) {
+ element = $(element);
+ if (content && content.toElement) content = content.toElement();
+ else if (!Object.isElement(content)) {
+ content = Object.toHTML(content);
+ var range = element.ownerDocument.createRange();
+ range.selectNode(element);
+ content.evalScripts.bind(content).defer();
+ content = range.createContextualFragment(content.stripScripts());
+ }
+ element.parentNode.replaceChild(content, element);
+ return element;
+ },
+
+ insert: function(element, insertions) {
+ element = $(element);
+
+ if (Object.isString(insertions) || Object.isNumber(insertions) ||
+ Object.isElement(insertions) || (insertions && (insertions.toElement |…
+ insertions = {bottom:insertions};
+
+ var content, insert, tagName, childNodes;
+
+ for (var position in insertions) {
+ content = insertions[position];
+ position = position.toLowerCase();
+ insert = Element._insertionTranslations[position];
+
+ if (content && content.toElement) content = content.toElement();
+ if (Object.isElement(content)) {
+ insert(element, content);
+ continue;
+ }
+
+ content = Object.toHTML(content);
+
+ tagName = ((position == 'before' || position == 'after')
+ ? element.parentNode : element).tagName.toUpperCase();
+
+ childNodes = Element._getContentFromAnonymousElement(tagName, content.st…
+
+ if (position == 'top' || position == 'after') childNodes.reverse();
+ childNodes.each(insert.curry(element));
+
+ content.evalScripts.bind(content).defer();
+ }
+
+ return element;
+ },
+
+ wrap: function(element, wrapper, attributes) {
+ element = $(element);
+ if (Object.isElement(wrapper))
+ $(wrapper).writeAttribute(attributes || { });
+ else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attribut…
+ else wrapper = new Element('div', wrapper);
+ if (element.parentNode)
+ element.parentNode.replaceChild(wrapper, element);
+ wrapper.appendChild(element);
+ return wrapper;
+ },
+
+ inspect: function(element) {
+ element = $(element);
+ var result = '<' + element.tagName.toLowerCase();
+ $H({'id': 'id', 'className': 'class'}).each(function(pair) {
+ var property = pair.first(),
+ attribute = pair.last(),
+ value = (element[property] || '').toString();
+ if (value) result += ' ' + attribute + '=' + value.inspect(true);
+ });
+ return result + '>';
+ },
+
+ recursivelyCollect: function(element, property, maximumLength) {
+ element = $(element);
+ maximumLength = maximumLength || -1;
+ var elements = [];
+
+ while (element = element[property]) {
+ if (element.nodeType == 1)
+ elements.push(Element.extend(element));
+ if (elements.length == maximumLength)
+ break;
+ }
+
+ return elements;
+ },
+
+ ancestors: function(element) {
+ return Element.recursivelyCollect(element, 'parentNode');
+ },
+
+ descendants: function(element) {
+ return Element.select(element, "*");
+ },
+
+ firstDescendant: function(element) {
+ element = $(element).firstChild;
+ while (element && element.nodeType != 1) element = element.nextSibling;
+ return $(element);
+ },
+
+ immediateDescendants: function(element) {
+ var results = [], child = $(element).firstChild;
+ while (child) {
+ if (child.nodeType === 1) {
+ results.push(Element.extend(child));
+ }
+ child = child.nextSibling;
+ }
+ return results;
+ },
+
+ previousSiblings: function(element, maximumLength) {
+ return Element.recursivelyCollect(element, 'previousSibling');
+ },
+
+ nextSiblings: function(element) {
+ return Element.recursivelyCollect(element, 'nextSibling');
+ },
+
+ siblings: function(element) {
+ element = $(element);
+ return Element.previousSiblings(element).reverse()
+ .concat(Element.nextSiblings(element));
+ },
+
+ match: function(element, selector) {
+ element = $(element);
+ if (Object.isString(selector))
+ return Prototype.Selector.match(element, selector);
+ return selector.match(element);
+ },
+
+ up: function(element, expression, index) {
+ element = $(element);
+ if (arguments.length == 1) return $(element.parentNode);
+ var ancestors = Element.ancestors(element);
+ return Object.isNumber(expression) ? ancestors[expression] :
+ Prototype.Selector.find(ancestors, expression, index);
+ },
+
+ down: function(element, expression, index) {
+ element = $(element);
+ if (arguments.length == 1) return Element.firstDescendant(element);
+ return Object.isNumber(expression) ? Element.descendants(element)[expressi…
+ Element.select(element, expression)[index || 0];
+ },
+
+ previous: function(element, expression, index) {
+ element = $(element);
+ if (Object.isNumber(expression)) index = expression, expression = false;
+ if (!Object.isNumber(index)) index = 0;
+
+ if (expression) {
+ return Prototype.Selector.find(element.previousSiblings(), expression, i…
+ } else {
+ return element.recursivelyCollect("previousSibling", index + 1)[index];
+ }
+ },
+
+ next: function(element, expression, index) {
+ element = $(element);
+ if (Object.isNumber(expression)) index = expression, expression = false;
+ if (!Object.isNumber(index)) index = 0;
+
+ if (expression) {
+ return Prototype.Selector.find(element.nextSiblings(), expression, index…
+ } else {
+ var maximumLength = Object.isNumber(index) ? index + 1 : 1;
+ return element.recursivelyCollect("nextSibling", index + 1)[index];
+ }
+ },
+
+
+ select: function(element) {
+ element = $(element);
+ var expressions = Array.prototype.slice.call(arguments, 1).join(', ');
+ return Prototype.Selector.select(expressions, element);
+ },
+
+ adjacent: function(element) {
+ element = $(element);
+ var expressions = Array.prototype.slice.call(arguments, 1).join(', ');
+ return Prototype.Selector.select(expressions, element.parentNode).without(…
+ },
+
+ identify: function(element) {
+ element = $(element);
+ var id = Element.readAttribute(element, 'id');
+ if (id) return id;
+ do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id));
+ Element.writeAttribute(element, 'id', id);
+ return id;
+ },
+
+ readAttribute: function(element, name) {
+ element = $(element);
+ if (Prototype.Browser.IE) {
+ var t = Element._attributeTranslations.read;
+ if (t.values[name]) return t.values[name](element, name);
+ if (t.names[name]) name = t.names[name];
+ if (name.include(':')) {
+ return (!element.attributes || !element.attributes[name]) ? null :
+ element.attributes[name].value;
+ }
+ }
+ return element.getAttribute(name);
+ },
+
+ writeAttribute: function(element, name, value) {
+ element = $(element);
+ var attributes = { }, t = Element._attributeTranslations.write;
+
+ if (typeof name == 'object') attributes = name;
+ else attributes[name] = Object.isUndefined(value) ? true : value;
+
+ for (var attr in attributes) {
+ name = t.names[attr] || attr;
+ value = attributes[attr];
+ if (t.values[attr]) name = t.values[attr](element, value);
+ if (value === false || value === null)
+ element.removeAttribute(name);
+ else if (value === true)
+ element.setAttribute(name, name);
+ else element.setAttribute(name, value);
+ }
+ return element;
+ },
+
+ getHeight: function(element) {
+ return Element.getDimensions(element).height;
+ },
+
+ getWidth: function(element) {
+ return Element.getDimensions(element).width;
+ },
+
+ classNames: function(element) {
+ return new Element.ClassNames(element);
+ },
+
+ hasClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ var elementClassName = element.className;
+ return (elementClassName.length > 0 && (elementClassName == className ||
+ new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
+ },
+
+ addClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ if (!Element.hasClassName(element, className))
+ element.className += (element.className ? ' ' : '') + className;
+ return element;
+ },
+
+ removeClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ element.className = element.className.replace(
+ new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
+ return element;
+ },
+
+ toggleClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ return Element[Element.hasClassName(element, className) ?
+ 'removeClassName' : 'addClassName'](element, className);
+ },
+
+ cleanWhitespace: function(element) {
+ element = $(element);
+ var node = element.firstChild;
+ while (node) {
+ var nextNode = node.nextSibling;
+ if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+ element.removeChild(node);
+ node = nextNode;
+ }
+ return element;
+ },
+
+ empty: function(element) {
+ return $(element).innerHTML.blank();
+ },
+
+ descendantOf: function(element, ancestor) {
+ element = $(element), ancestor = $(ancestor);
+
+ if (element.compareDocumentPosition)
+ return (element.compareDocumentPosition(ancestor) & 8) === 8;
+
+ if (ancestor.contains)
+ return ancestor.contains(element) && ancestor !== element;
+
+ while (element = element.parentNode)
+ if (element == ancestor) return true;
+
+ return false;
+ },
+
+ scrollTo: function(element) {
+ element = $(element);
+ var pos = Element.cumulativeOffset(element);
+ window.scrollTo(pos[0], pos[1]);
+ return element;
+ },
+
+ getStyle: function(element, style) {
+ element = $(element);
+ style = style == 'float' ? 'cssFloat' : style.camelize();
+ var value = element.style[style];
+ if (!value || value == 'auto') {
+ var css = document.defaultView.getComputedStyle(element, null);
+ value = css ? css[style] : null;
+ }
+ if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+ return value == 'auto' ? null : value;
+ },
+
+ getOpacity: function(element) {
+ return $(element).getStyle('opacity');
+ },
+
+ setStyle: function(element, styles) {
+ element = $(element);
+ var elementStyle = element.style, match;
+ if (Object.isString(styles)) {
+ element.style.cssText += ';' + styles;
+ return styles.include('opacity') ?
+ element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : elemen…
+ }
+ for (var property in styles)
+ if (property == 'opacity') element.setOpacity(styles[property]);
+ else
+ elementStyle[(property == 'float' || property == 'cssFloat') ?
+ (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFl…
+ property] = styles[property];
+
+ return element;
+ },
+
+ setOpacity: function(element, value) {
+ element = $(element);
+ element.style.opacity = (value == 1 || value === '') ? '' :
+ (value < 0.00001) ? 0 : value;
+ return element;
+ },
+
+ makePositioned: function(element) {
+ element = $(element);
+ var pos = Element.getStyle(element, 'position');
+ if (pos == 'static' || !pos) {
+ element._madePositioned = true;
+ element.style.position = 'relative';
+ if (Prototype.Browser.Opera) {
+ element.style.top = 0;
+ element.style.left = 0;
+ }
+ }
+ return element;
+ },
+
+ undoPositioned: function(element) {
+ element = $(element);
+ if (element._madePositioned) {
+ element._madePositioned = undefined;
+ element.style.position =
+ element.style.top =
+ element.style.left =
+ element.style.bottom =
+ element.style.right = '';
+ }
+ return element;
+ },
+
+ makeClipping: function(element) {
+ element = $(element);
+ if (element._overflow) return element;
+ element._overflow = Element.getStyle(element, 'overflow') || 'auto';
+ if (element._overflow !== 'hidden')
+ element.style.overflow = 'hidden';
+ return element;
+ },
+
+ undoClipping: function(element) {
+ element = $(element);
+ if (!element._overflow) return element;
+ element.style.overflow = element._overflow == 'auto' ? '' : element._overf…
+ element._overflow = null;
+ return element;
+ },
+
+ cumulativeOffset: function(element) {
+ var valueT = 0, valueL = 0;
+ if (element.parentNode) {
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ } while (element);
+ }
+ return Element._returnOffset(valueL, valueT);
+ },
+
+ positionedOffset: function(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ if (element) {
+ if (element.tagName.toUpperCase() == 'BODY') break;
+ var p = Element.getStyle(element, 'position');
+ if (p !== 'static') break;
+ }
+ } while (element);
+ return Element._returnOffset(valueL, valueT);
+ },
+
+ absolutize: function(element) {
+ element = $(element);
+ if (Element.getStyle(element, 'position') == 'absolute') return element;
+
+ var offsets = Element.positionedOffset(element),
+ top = offsets[1],
+ left = offsets[0],
+ width = element.clientWidth,
+ height = element.clientHeight;
+
+ element._originalLeft = left - parseFloat(element.style.left || 0);
+ element._originalTop = top - parseFloat(element.style.top || 0);
+ element._originalWidth = element.style.width;
+ element._originalHeight = element.style.height;
+
+ element.style.position = 'absolute';
+ element.style.top = top + 'px';
+ element.style.left = left + 'px';
+ element.style.width = width + 'px';
+ element.style.height = height + 'px';
+ return element;
+ },
+
+ relativize: function(element) {
+ element = $(element);
+ if (Element.getStyle(element, 'position') == 'relative') return element;
+
+ element.style.position = 'relative';
+ var top = parseFloat(element.style.top || 0) - (element._originalTop || …
+ left = parseFloat(element.style.left || 0) - (element._originalLeft ||…
+
+ element.style.top = top + 'px';
+ element.style.left = left + 'px';
+ element.style.height = element._originalHeight;
+ element.style.width = element._originalWidth;
+ return element;
+ },
+
+ cumulativeScrollOffset: function(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.scrollTop || 0;
+ valueL += element.scrollLeft || 0;
+ element = element.parentNode;
+ } while (element);
+ return Element._returnOffset(valueL, valueT);
+ },
+
+ getOffsetParent: function(element) {
+ if (element.offsetParent) return $(element.offsetParent);
+ if (element == document.body) return $(element);
+
+ while ((element = element.parentNode) && element != document.body)
+ if (Element.getStyle(element, 'position') != 'static')
+ return $(element);
+
+ return $(document.body);
+ },
+
+ viewportOffset: function(forElement) {
+ var valueT = 0,
+ valueL = 0,
+ element = forElement;
+
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+
+ if (element.offsetParent == document.body &&
+ Element.getStyle(element, 'position') == 'absolute') break;
+
+ } while (element = element.offsetParent);
+
+ element = forElement;
+ do {
+ if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toU…
+ valueT -= element.scrollTop || 0;
+ valueL -= element.scrollLeft || 0;
+ }
+ } while (element = element.parentNode);
+
+ return Element._returnOffset(valueL, valueT);
+ },
+
+ clonePosition: function(element, source) {
+ var options = Object.extend({
+ setLeft: true,
+ setTop: true,
+ setWidth: true,
+ setHeight: true,
+ offsetTop: 0,
+ offsetLeft: 0
+ }, arguments[2] || { });
+
+ source = $(source);
+ var p = Element.viewportOffset(source), delta = [0, 0], parent = null;
+
+ element = $(element);
+
+ if (Element.getStyle(element, 'position') == 'absolute') {
+ parent = Element.getOffsetParent(element);
+ delta = Element.viewportOffset(parent);
+ }
+
+ if (parent == document.body) {
+ delta[0] -= document.body.offsetLeft;
+ delta[1] -= document.body.offsetTop;
+ }
+
+ if (options.setLeft) element.style.left = (p[0] - delta[0] + options.of…
+ if (options.setTop) element.style.top = (p[1] - delta[1] + options.of…
+ if (options.setWidth) element.style.width = source.offsetWidth + 'px';
+ if (options.setHeight) element.style.height = source.offsetHeight + 'px';
+ return element;
+ }
+};
+
+Object.extend(Element.Methods, {
+ getElementsBySelector: Element.Methods.select,
+
+ childElements: Element.Methods.immediateDescendants
+});
+
+Element._attributeTranslations = {
+ write: {
+ names: {
+ className: 'class',
+ htmlFor: 'for'
+ },
+ values: { }
+ }
+};
+
+if (Prototype.Browser.Opera) {
+ Element.Methods.getStyle = Element.Methods.getStyle.wrap(
+ function(proceed, element, style) {
+ switch (style) {
+ case 'left': case 'top': case 'right': case 'bottom':
+ if (proceed(element, 'position') === 'static') return null;
+ case 'height': case 'width':
+ if (!Element.visible(element)) return null;
+
+ var dim = parseInt(proceed(element, style), 10);
+
+ if (dim !== element['offset' + style.capitalize()])
+ return dim + 'px';
+
+ var properties;
+ if (style === 'height') {
+ properties = ['border-top-width', 'padding-top',
+ 'padding-bottom', 'border-bottom-width'];
+ }
+ else {
+ properties = ['border-left-width', 'padding-left',
+ 'padding-right', 'border-right-width'];
+ }
+ return properties.inject(dim, function(memo, property) {
+ var val = proceed(element, property);
+ return val === null ? memo : memo - parseInt(val, 10);
+ }) + 'px';
+ default: return proceed(element, style);
+ }
+ }
+ );
+
+ Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
+ function(proceed, element, attribute) {
+ if (attribute === 'title') return element.title;
+ return proceed(element, attribute);
+ }
+ );
+}
+
+else if (Prototype.Browser.IE) {
+ Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
+ function(proceed, element) {
+ element = $(element);
+ if (!element.parentNode) return $(document.body);
+ var position = element.getStyle('position');
+ if (position !== 'static') return proceed(element);
+ element.setStyle({ position: 'relative' });
+ var value = proceed(element);
+ element.setStyle({ position: position });
+ return value;
+ }
+ );
+
+ $w('positionedOffset viewportOffset').each(function(method) {
+ Element.Methods[method] = Element.Methods[method].wrap(
+ function(proceed, element) {
+ element = $(element);
+ if (!element.parentNode) return Element._returnOffset(0, 0);
+ var position = element.getStyle('position');
+ if (position !== 'static') return proceed(element);
+ var offsetParent = element.getOffsetParent();
+ if (offsetParent && offsetParent.getStyle('position') === 'fixed')
+ offsetParent.setStyle({ zoom: 1 });
+ element.setStyle({ position: 'relative' });
+ var value = proceed(element);
+ element.setStyle({ position: position });
+ return value;
+ }
+ );
+ });
+
+ Element.Methods.getStyle = function(element, style) {
+ element = $(element);
+ style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.c…
+ var value = element.style[style];
+ if (!value && element.currentStyle) value = element.currentStyle[style];
+
+ if (style == 'opacity') {
+ if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*…
+ if (value[1]) return parseFloat(value[1]) / 100;
+ return 1.0;
+ }
+
+ if (value == 'auto') {
+ if ((style == 'width' || style == 'height') && (element.getStyle('displa…
+ return element['offset' + style.capitalize()] + 'px';
+ return null;
+ }
+ return value;
+ };
+
+ Element.Methods.setOpacity = function(element, value) {
+ function stripAlpha(filter){
+ return filter.replace(/alpha\([^\)]*\)/gi,'');
+ }
+ element = $(element);
+ var currentStyle = element.currentStyle;
+ if ((currentStyle && !currentStyle.hasLayout) ||
+ (!currentStyle && element.style.zoom == 'normal'))
+ element.style.zoom = 1;
+
+ var filter = element.getStyle('filter'), style = element.style;
+ if (value == 1 || value === '') {
+ (filter = stripAlpha(filter)) ?
+ style.filter = filter : style.removeAttribute('filter');
+ return element;
+ } else if (value < 0.00001) value = 0;
+ style.filter = stripAlpha(filter) +
+ 'alpha(opacity=' + (value * 100) + ')';
+ return element;
+ };
+
+ Element._attributeTranslations = (function(){
+
+ var classProp = 'className',
+ forProp = 'for',
+ el = document.createElement('div');
+
+ el.setAttribute(classProp, 'x');
+
+ if (el.className !== 'x') {
+ el.setAttribute('class', 'x');
+ if (el.className === 'x') {
+ classProp = 'class';
+ }
+ }
+ el = null;
+
+ el = document.createElement('label');
+ el.setAttribute(forProp, 'x');
+ if (el.htmlFor !== 'x') {
+ el.setAttribute('htmlFor', 'x');
+ if (el.htmlFor === 'x') {
+ forProp = 'htmlFor';
+ }
+ }
+ el = null;
+
+ return {
+ read: {
+ names: {
+ 'class': classProp,
+ 'className': classProp,
+ 'for': forProp,
+ 'htmlFor': forProp
+ },
+ values: {
+ _getAttr: function(element, attribute) {
+ return element.getAttribute(attribute);
+ },
+ _getAttr2: function(element, attribute) {
+ return element.getAttribute(attribute, 2);
+ },
+ _getAttrNode: function(element, attribute) {
+ var node = element.getAttributeNode(attribute);
+ return node ? node.value : "";
+ },
+ _getEv: (function(){
+
+ var el = document.createElement('div'), f;
+ el.onclick = Prototype.emptyFunction;
+ var value = el.getAttribute('onclick');
+
+ if (String(value).indexOf('{') > -1) {
+ f = function(element, attribute) {
+ attribute = element.getAttribute(attribute);
+ if (!attribute) return null;
+ attribute = attribute.toString();
+ attribute = attribute.split('{')[1];
+ attribute = attribute.split('}')[0];
+ return attribute.strip();
+ };
+ }
+ else if (value === '') {
+ f = function(element, attribute) {
+ attribute = element.getAttribute(attribute);
+ if (!attribute) return null;
+ return attribute.strip();
+ };
+ }
+ el = null;
+ return f;
+ })(),
+ _flag: function(element, attribute) {
+ return $(element).hasAttribute(attribute) ? attribute : null;
+ },
+ style: function(element) {
+ return element.style.cssText.toLowerCase();
+ },
+ title: function(element) {
+ return element.title;
+ }
+ }
+ }
+ }
+ })();
+
+ Element._attributeTranslations.write = {
+ names: Object.extend({
+ cellpadding: 'cellPadding',
+ cellspacing: 'cellSpacing'
+ }, Element._attributeTranslations.read.names),
+ values: {
+ checked: function(element, value) {
+ element.checked = !!value;
+ },
+
+ style: function(element, value) {
+ element.style.cssText = value ? value : '';
+ }
+ }
+ };
+
+ Element._attributeTranslations.has = {};
+
+ $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
+ 'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
+ Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
+ Element._attributeTranslations.has[attr.toLowerCase()] = attr;
+ });
+
+ (function(v) {
+ Object.extend(v, {
+ href: v._getAttr2,
+ src: v._getAttr2,
+ type: v._getAttr,
+ action: v._getAttrNode,
+ disabled: v._flag,
+ checked: v._flag,
+ readonly: v._flag,
+ multiple: v._flag,
+ onload: v._getEv,
+ onunload: v._getEv,
+ onclick: v._getEv,
+ ondblclick: v._getEv,
+ onmousedown: v._getEv,
+ onmouseup: v._getEv,
+ onmouseover: v._getEv,
+ onmousemove: v._getEv,
+ onmouseout: v._getEv,
+ onfocus: v._getEv,
+ onblur: v._getEv,
+ onkeypress: v._getEv,
+ onkeydown: v._getEv,
+ onkeyup: v._getEv,
+ onsubmit: v._getEv,
+ onreset: v._getEv,
+ onselect: v._getEv,
+ onchange: v._getEv
+ });
+ })(Element._attributeTranslations.read.values);
+
+ if (Prototype.BrowserFeatures.ElementExtensions) {
+ (function() {
+ function _descendants(element) {
+ var nodes = element.getElementsByTagName('*'), results = [];
+ for (var i = 0, node; node = nodes[i]; i++)
+ if (node.tagName !== "!") // Filter out comment nodes.
+ results.push(node);
+ return results;
+ }
+
+ Element.Methods.down = function(element, expression, index) {
+ element = $(element);
+ if (arguments.length == 1) return element.firstDescendant();
+ return Object.isNumber(expression) ? _descendants(element)[expression]…
+ Element.select(element, expression)[index || 0];
+ }
+ })();
+ }
+
+}
+
+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
+ Element.Methods.setOpacity = function(element, value) {
+ element = $(element);
+ element.style.opacity = (value == 1) ? 0.999999 :
+ (value === '') ? '' : (value < 0.00001) ? 0 : value;
+ return element;
+ };
+}
+
+else if (Prototype.Browser.WebKit) {
+ Element.Methods.setOpacity = function(element, value) {
+ element = $(element);
+ element.style.opacity = (value == 1 || value === '') ? '' :
+ (value < 0.00001) ? 0 : value;
+
+ if (value == 1)
+ if (element.tagName.toUpperCase() == 'IMG' && element.width) {
+ element.width++; element.width--;
+ } else try {
+ var n = document.createTextNode(' ');
+ element.appendChild(n);
+ element.removeChild(n);
+ } catch (e) { }
+
+ return element;
+ };
+
+ Element.Methods.cumulativeOffset = function(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ if (element.offsetParent == document.body)
+ if (Element.getStyle(element, 'position') == 'absolute') break;
+
+ element = element.offsetParent;
+ } while (element);
+
+ return Element._returnOffset(valueL, valueT);
+ };
+}
+
+if ('outerHTML' in document.documentElement) {
+ Element.Methods.replace = function(element, content) {
+ element = $(element);
+
+ if (content && content.toElement) content = content.toElement();
+ if (Object.isElement(content)) {
+ element.parentNode.replaceChild(content, element);
+ return element;
+ }
+
+ content = Object.toHTML(content);
+ var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
+
+ if (Element._insertionTranslations.tags[tagName]) {
+ var nextSibling = element.next(),
+ fragments = Element._getContentFromAnonymousElement(tagName, content…
+ parent.removeChild(element);
+ if (nextSibling)
+ fragments.each(function(node) { parent.insertBefore(node, nextSibling)…
+ else
+ fragments.each(function(node) { parent.appendChild(node) });
+ }
+ else element.outerHTML = content.stripScripts();
+
+ content.evalScripts.bind(content).defer();
+ return element;
+ };
+}
+
+Element._returnOffset = function(l, t) {
+ var result = [l, t];
+ result.left = l;
+ result.top = t;
+ return result;
+};
+
+Element._getContentFromAnonymousElement = function(tagName, html) {
+ var div = new Element('div'),
+ t = Element._insertionTranslations.tags[tagName];
+ if (t) {
+ div.innerHTML = t[0] + html + t[1];
+ for (var i = t[2]; i--; ) {
+ div = div.firstChild;
+ }
+ }
+ else {
+ div.innerHTML = html;
+ }
+ return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+ before: function(element, node) {
+ element.parentNode.insertBefore(node, element);
+ },
+ top: function(element, node) {
+ element.insertBefore(node, element.firstChild);
+ },
+ bottom: function(element, node) {
+ element.appendChild(node);
+ },
+ after: function(element, node) {
+ element.parentNode.insertBefore(node, element.nextSibling);
+ },
+ tags: {
+ TABLE: ['<table>', '</table>', 1],
+ TBODY: ['<table><tbody>', '</tbody></table>', 2],
+ TR: ['<table><tbody><tr>', '</tr></tbody></table>', 3],
+ TD: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
+ SELECT: ['<select>', '</select>', 1]
+ }
+};
+
+(function() {
+ var tags = Element._insertionTranslations.tags;
+ Object.extend(tags, {
+ THEAD: tags.TBODY,
+ TFOOT: tags.TBODY,
+ TH: tags.TD
+ });
+})();
+
+Element.Methods.Simulated = {
+ hasAttribute: function(element, attribute) {
+ attribute = Element._attributeTranslations.has[attribute] || attribute;
+ var node = $(element).getAttributeNode(attribute);
+ return !!(node && node.specified);
+ }
+};
+
+Element.Methods.ByTag = { };
+
+Object.extend(Element, Element.Methods);
+
+(function(div) {
+
+ if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) {
+ window.HTMLElement = { };
+ window.HTMLElement.prototype = div['__proto__'];
+ Prototype.BrowserFeatures.ElementExtensions = true;
+ }
+
+ div = null;
+
+})(document.createElement('div'));
+
+Element.extend = (function() {
+
+ function checkDeficiency(tagName) {
+ if (typeof window.Element != 'undefined') {
+ var proto = window.Element.prototype;
+ if (proto) {
+ var id = '_' + (Math.random()+'').slice(2),
+ el = document.createElement(tagName);
+ proto[id] = 'x';
+ var isBuggy = (el[id] !== 'x');
+ delete proto[id];
+ el = null;
+ return isBuggy;
+ }
+ }
+ return false;
+ }
+
+ function extendElementWith(element, methods) {
+ for (var property in methods) {
+ var value = methods[property];
+ if (Object.isFunction(value) && !(property in element))
+ element[property] = value.methodize();
+ }
+ }
+
+ var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object');
+
+ if (Prototype.BrowserFeatures.SpecificElementExtensions) {
+ if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) {
+ return function(element) {
+ if (element && typeof element._extendedByPrototype == 'undefined') {
+ var t = element.tagName;
+ if (t && (/^(?:object|applet|embed)$/i.test(t))) {
+ extendElementWith(element, Element.Methods);
+ extendElementWith(element, Element.Methods.Simulated);
+ extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]);
+ }
+ }
+ return element;
+ }
+ }
+ return Prototype.K;
+ }
+
+ var Methods = { }, ByTag = Element.Methods.ByTag;
+
+ var extend = Object.extend(function(element) {
+ if (!element || typeof element._extendedByPrototype != 'undefined' ||
+ element.nodeType != 1 || element == window) return element;
+
+ var methods = Object.clone(Methods),
+ tagName = element.tagName.toUpperCase();
+
+ if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
+
+ extendElementWith(element, methods);
+
+ element._extendedByPrototype = Prototype.emptyFunction;
+ return element;
+
+ }, {
+ refresh: function() {
+ if (!Prototype.BrowserFeatures.ElementExtensions) {
+ Object.extend(Methods, Element.Methods);
+ Object.extend(Methods, Element.Methods.Simulated);
+ }
+ }
+ });
+
+ extend.refresh();
+ return extend;
+})();
+
+if (document.documentElement.hasAttribute) {
+ Element.hasAttribute = function(element, attribute) {
+ return element.hasAttribute(attribute);
+ };
+}
+else {
+ Element.hasAttribute = Element.Methods.Simulated.hasAttribute;
+}
+
+Element.addMethods = function(methods) {
+ var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+ if (!methods) {
+ Object.extend(Form, Form.Methods);
+ Object.extend(Form.Element, Form.Element.Methods);
+ Object.extend(Element.Methods.ByTag, {
+ "FORM": Object.clone(Form.Methods),
+ "INPUT": Object.clone(Form.Element.Methods),
+ "SELECT": Object.clone(Form.Element.Methods),
+ "TEXTAREA": Object.clone(Form.Element.Methods)
+ });
+ }
+
+ if (arguments.length == 2) {
+ var tagName = methods;
+ methods = arguments[1];
+ }
+
+ if (!tagName) Object.extend(Element.Methods, methods || { });
+ else {
+ if (Object.isArray(tagName)) tagName.each(extend);
+ else extend(tagName);
+ }
+
+ function extend(tagName) {
+ tagName = tagName.toUpperCase();
+ if (!Element.Methods.ByTag[tagName])
+ Element.Methods.ByTag[tagName] = { };
+ Object.extend(Element.Methods.ByTag[tagName], methods);
+ }
+
+ function copy(methods, destination, onlyIfAbsent) {
+ onlyIfAbsent = onlyIfAbsent || false;
+ for (var property in methods) {
+ var value = methods[property];
+ if (!Object.isFunction(value)) continue;
+ if (!onlyIfAbsent || !(property in destination))
+ destination[property] = value.methodize();
+ }
+ }
+
+ function findDOMClass(tagName) {
+ var klass;
+ var trans = {
+ "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
+ "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
+ "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
+ "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
+ "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
+ "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
+ "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
+ "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
+ "FrameSet", "IFRAME": "IFrame"
+ };
+ if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+ if (window[klass]) return window[klass];
+ klass = 'HTML' + tagName + 'Element';
+ if (window[klass]) return window[klass];
+ klass = 'HTML' + tagName.capitalize() + 'Element';
+ if (window[klass]) return window[klass];
+
+ var element = document.createElement(tagName),
+ proto = element['__proto__'] || element.constructor.prototype;
+
+ element = null;
+ return proto;
+ }
+
+ var elementPrototype = window.HTMLElement ? HTMLElement.prototype :
+ Element.prototype;
+
+ if (F.ElementExtensions) {
+ copy(Element.Methods, elementPrototype);
+ copy(Element.Methods.Simulated, elementPrototype, true);
+ }
+
+ if (F.SpecificElementExtensions) {
+ for (var tag in Element.Methods.ByTag) {
+ var klass = findDOMClass(tag);
+ if (Object.isUndefined(klass)) continue;
+ copy(T[tag], klass.prototype);
+ }
+ }
+
+ Object.extend(Element, Element.Methods);
+ delete Element.ByTag;
+
+ if (Element.extend.refresh) Element.extend.refresh();
+ Element.cache = { };
+};
+
+
+document.viewport = {
+
+ getDimensions: function() {
+ return { width: this.getWidth(), height: this.getHeight() };
+ },
+
+ getScrollOffsets: function() {
+ return Element._returnOffset(
+ window.pageXOffset || document.documentElement.scrollLeft || document.bo…
+ window.pageYOffset || document.documentElement.scrollTop || document.bo…
+ }
+};
+
+(function(viewport) {
+ var B = Prototype.Browser, doc = document, element, property = {};
+
+ function getRootElement() {
+ if (B.WebKit && !doc.evaluate)
+ return document;
+
+ if (B.Opera && window.parseFloat(window.opera.version()) < 9.5)
+ return document.body;
+
+ return document.documentElement;
+ }
+
+ function define(D) {
+ if (!element) element = getRootElement();
+
+ property[D] = 'client' + D;
+
+ viewport['get' + D] = function() { return element[property[D]] };
+ return viewport['get' + D]();
+ }
+
+ viewport.getWidth = define.curry('Width');
+
+ viewport.getHeight = define.curry('Height');
+})(document.viewport);
+
+
+Element.Storage = {
+ UID: 1
+};
+
+Element.addMethods({
+ getStorage: function(element) {
+ if (!(element = $(element))) return;
+
+ var uid;
+ if (element === window) {
+ uid = 0;
+ } else {
+ if (typeof element._prototypeUID === "undefined")
+ element._prototypeUID = Element.Storage.UID++;
+ uid = element._prototypeUID;
+ }
+
+ if (!Element.Storage[uid])
+ Element.Storage[uid] = $H();
+
+ return Element.Storage[uid];
+ },
+
+ store: function(element, key, value) {
+ if (!(element = $(element))) return;
+
+ if (arguments.length === 2) {
+ Element.getStorage(element).update(key);
+ } else {
+ Element.getStorage(element).set(key, value);
+ }
+
+ return element;
+ },
+
+ retrieve: function(element, key, defaultValue) {
+ if (!(element = $(element))) return;
+ var hash = Element.getStorage(element), value = hash.get(key);
+
+ if (Object.isUndefined(value)) {
+ hash.set(key, defaultValue);
+ value = defaultValue;
+ }
+
+ return value;
+ },
+
+ clone: function(element, deep) {
+ if (!(element = $(element))) return;
+ var clone = element.cloneNode(deep);
+ clone._prototypeUID = void 0;
+ if (deep) {
+ var descendants = Element.select(clone, '*'),
+ i = descendants.length;
+ while (i--) {
+ descendants[i]._prototypeUID = void 0;
+ }
+ }
+ return Element.extend(clone);
+ },
+
+ purge: function(element) {
+ if (!(element = $(element))) return;
+ purgeElement(element);
+
+ var descendants = element.getElementsByTagName('*'),
+ i = descendants.length;
+
+ while (i--) purgeElement(descendants[i]);
+
+ return null;
+ }
+});
+
+(function() {
+
+ function toDecimal(pctString) {
+ var match = pctString.match(/^(\d+)%?$/i);
+ if (!match) return null;
+ return (Number(match[1]) / 100);
+ }
+
+ function getPixelValue(value, property) {
+ if (Object.isElement(value)) {
+ element = value;
+ value = element.getStyle(property);
+ }
+ if (value === null) {
+ return null;
+ }
+
+ if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) {
+ return window.parseFloat(value);
+ }
+
+ if (/\d/.test(value) && element.runtimeStyle) {
+ var style = element.style.left, rStyle = element.runtimeStyle.left;
+ element.runtimeStyle.left = element.currentStyle.left;
+ element.style.left = value || 0;
+ value = element.style.pixelLeft;
+ element.style.left = style;
+ element.runtimeStyle.left = rStyle;
+
+ return value;
+ }
+
+ if (value.include('%')) {
+ var decimal = toDecimal(value);
+ var whole;
+ if (property.include('left') || property.include('right') ||
+ property.include('width')) {
+ whole = $(element.parentNode).measure('width');
+ } else if (property.include('top') || property.include('bottom') ||
+ property.include('height')) {
+ whole = $(element.parentNode).measure('height');
+ }
+
+ return whole * decimal;
+ }
+
+ return 0;
+ }
+
+ function toCSSPixels(number) {
+ if (Object.isString(number) && number.endsWith('px')) {
+ return number;
+ }
+ return number + 'px';
+ }
+
+ function isDisplayed(element) {
+ var originalElement = element;
+ while (element && element.parentNode) {
+ var display = element.getStyle('display');
+ if (display === 'none') {
+ return false;
+ }
+ element = $(element.parentNode);
+ }
+ return true;
+ }
+
+ var hasLayout = Prototype.K;
+ if ('currentStyle' in document.documentElement) {
+ hasLayout = function(element) {
+ if (!element.currentStyle.hasLayout) {
+ element.style.zoom = 1;
+ }
+ return element;
+ };
+ }
+
+ function cssNameFor(key) {
+ if (key.include('border')) key = key + '-width';
+ return key.camelize();
+ }
+
+ Element.Layout = Class.create(Hash, {
+ initialize: function($super, element, preCompute) {
+ $super();
+ this.element = $(element);
+
+ Element.Layout.PROPERTIES.each( function(property) {
+ this._set(property, null);
+ }, this);
+
+ if (preCompute) {
+ this._preComputing = true;
+ this._begin();
+ Element.Layout.PROPERTIES.each( this._compute, this );
+ this._end();
+ this._preComputing = false;
+ }
+ },
+
+ _set: function(property, value) {
+ return Hash.prototype.set.call(this, property, value);
+ },
+
+ set: function(property, value) {
+ throw "Properties of Element.Layout are read-only.";
+ },
+
+ get: function($super, property) {
+ var value = $super(property);
+ return value === null ? this._compute(property) : value;
+ },
+
+ _begin: function() {
+ if (this._prepared) return;
+
+ var element = this.element;
+ if (isDisplayed(element)) {
+ this._prepared = true;
+ return;
+ }
+
+ var originalStyles = {
+ position: element.style.position || '',
+ width: element.style.width || '',
+ visibility: element.style.visibility || '',
+ display: element.style.display || ''
+ };
+
+ element.store('prototype_original_styles', originalStyles);
+
+ var position = element.getStyle('position'),
+ width = element.getStyle('width');
+
+ element.setStyle({
+ position: 'absolute',
+ visibility: 'hidden',
+ display: 'block'
+ });
+
+ var positionedWidth = element.getStyle('width');
+
+ var newWidth;
+ if (width && (positionedWidth === width)) {
+ newWidth = getPixelValue(width);
+ } else if (width && (position === 'absolute' || position === 'fixed')) {
+ newWidth = getPixelValue(width);
+ } else {
+ var parent = element.parentNode, pLayout = $(parent).getLayout();
+
+ newWidth = pLayout.get('width') -
+ this.get('margin-left') -
+ this.get('border-left') -
+ this.get('padding-left') -
+ this.get('padding-right') -
+ this.get('border-right') -
+ this.get('margin-right');
+ }
+
+ element.setStyle({ width: newWidth + 'px' });
+
+ this._prepared = true;
+ },
+
+ _end: function() {
+ var element = this.element;
+ var originalStyles = element.retrieve('prototype_original_styles');
+ element.store('prototype_original_styles', null);
+ element.setStyle(originalStyles);
+ this._prepared = false;
+ },
+
+ _compute: function(property) {
+ var COMPUTATIONS = Element.Layout.COMPUTATIONS;
+ if (!(property in COMPUTATIONS)) {
+ throw "Property not found.";
+ }
+ return this._set(property, COMPUTATIONS[property].call(this, this.elemen…
+ },
+
+ toObject: function() {
+ var args = $A(arguments);
+ var keys = (args.length === 0) ? Element.Layout.PROPERTIES :
+ args.join(' ').split(' ');
+ var obj = {};
+ keys.each( function(key) {
+ if (!Element.Layout.PROPERTIES.include(key)) return;
+ var value = this.get(key);
+ if (value != null) obj[key] = value;
+ }, this);
+ return obj;
+ },
+
+ toHash: function() {
+ var obj = this.toObject.apply(this, arguments);
+ return new Hash(obj);
+ },
+
+ toCSS: function() {
+ var args = $A(arguments);
+ var keys = (args.length === 0) ? Element.Layout.PROPERTIES :
+ args.join(' ').split(' ');
+ var css = {};
+
+ keys.each( function(key) {
+ if (!Element.Layout.PROPERTIES.include(key)) return;
+ if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return;
+
+ var value = this.get(key);
+ if (value != null) css[cssNameFor(key)] = value + 'px';
+ }, this);
+ return css;
+ },
+
+ inspect: function() {
+ return "#<Element.Layout>";
+ }
+ });
+
+ Object.extend(Element.Layout, {
+ PROPERTIES: $w('height width top left right bottom border-left border-righ…
+
+ COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-…
+
+ COMPUTATIONS: {
+ 'height': function(element) {
+ if (!this._preComputing) this._begin();
+
+ var bHeight = this.get('border-box-height');
+ if (bHeight <= 0) return 0;
+
+ var bTop = this.get('border-top'),
+ bBottom = this.get('border-bottom');
+
+ var pTop = this.get('padding-top'),
+ pBottom = this.get('padding-bottom');
+
+ if (!this._preComputing) this._end();
+
+ return bHeight - bTop - bBottom - pTop - pBottom;
+ },
+
+ 'width': function(element) {
+ if (!this._preComputing) this._begin();
+
+ var bWidth = this.get('border-box-width');
+ if (bWidth <= 0) return 0;
+
+ var bLeft = this.get('border-left'),
+ bRight = this.get('border-right');
+
+ var pLeft = this.get('padding-left'),
+ pRight = this.get('padding-right');
+
+ if (!this._preComputing) this._end();
+
+ return bWidth - bLeft - bRight - pLeft - pRight;
+ },
+
+ 'padding-box-height': function(element) {
+ var height = this.get('height'),
+ pTop = this.get('padding-top'),
+ pBottom = this.get('padding-bottom');
+
+ return height + pTop + pBottom;
+ },
+
+ 'padding-box-width': function(element) {
+ var width = this.get('width'),
+ pLeft = this.get('padding-left'),
+ pRight = this.get('padding-right');
+
+ return width + pLeft + pRight;
+ },
+
+ 'border-box-height': function(element) {
+ return element.offsetHeight;
+ },
+
+ 'border-box-width': function(element) {
+ return element.offsetWidth;
+ },
+
+ 'margin-box-height': function(element) {
+ var bHeight = this.get('border-box-height'),
+ mTop = this.get('margin-top'),
+ mBottom = this.get('margin-bottom');
+
+ if (bHeight <= 0) return 0;
+
+ return bHeight + mTop + mBottom;
+ },
+
+ 'margin-box-width': function(element) {
+ var bWidth = this.get('border-box-width'),
+ mLeft = this.get('margin-left'),
+ mRight = this.get('margin-right');
+
+ if (bWidth <= 0) return 0;
+
+ return bWidth + mLeft + mRight;
+ },
+
+ 'top': function(element) {
+ var offset = element.positionedOffset();
+ return offset.top;
+ },
+
+ 'bottom': function(element) {
+ var offset = element.positionedOffset(),
+ parent = element.getOffsetParent(),
+ pHeight = parent.measure('height');
+
+ var mHeight = this.get('border-box-height');
+
+ return pHeight - mHeight - offset.top;
+ },
+
+ 'left': function(element) {
+ var offset = element.positionedOffset();
+ return offset.left;
+ },
+
+ 'right': function(element) {
+ var offset = element.positionedOffset(),
+ parent = element.getOffsetParent(),
+ pWidth = parent.measure('width');
+
+ var mWidth = this.get('border-box-width');
+
+ return pWidth - mWidth - offset.left;
+ },
+
+ 'padding-top': function(element) {
+ return getPixelValue(element, 'paddingTop');
+ },
+
+ 'padding-bottom': function(element) {
+ return getPixelValue(element, 'paddingBottom');
+ },
+
+ 'padding-left': function(element) {
+ return getPixelValue(element, 'paddingLeft');
+ },
+
+ 'padding-right': function(element) {
+ return getPixelValue(element, 'paddingRight');
+ },
+
+ 'border-top': function(element) {
+ return Object.isNumber(element.clientTop) ? element.clientTop :
+ getPixelValue(element, 'borderTopWidth');
+ },
+
+ 'border-bottom': function(element) {
+ return Object.isNumber(element.clientBottom) ? element.clientBottom :
+ getPixelValue(element, 'borderBottomWidth');
+ },
+
+ 'border-left': function(element) {
+ return Object.isNumber(element.clientLeft) ? element.clientLeft :
+ getPixelValue(element, 'borderLeftWidth');
+ },
+
+ 'border-right': function(element) {
+ return Object.isNumber(element.clientRight) ? element.clientRight :
+ getPixelValue(element, 'borderRightWidth');
+ },
+
+ 'margin-top': function(element) {
+ return getPixelValue(element, 'marginTop');
+ },
+
+ 'margin-bottom': function(element) {
+ return getPixelValue(element, 'marginBottom');
+ },
+
+ 'margin-left': function(element) {
+ return getPixelValue(element, 'marginLeft');
+ },
+
+ 'margin-right': function(element) {
+ return getPixelValue(element, 'marginRight');
+ }
+ }
+ });
+
+ if ('getBoundingClientRect' in document.documentElement) {
+ Object.extend(Element.Layout.COMPUTATIONS, {
+ 'right': function(element) {
+ var parent = hasLayout(element.getOffsetParent());
+ var rect = element.getBoundingClientRect(),
+ pRect = parent.getBoundingClientRect();
+
+ return (pRect.right - rect.right).round();
+ },
+
+ 'bottom': function(element) {
+ var parent = hasLayout(element.getOffsetParent());
+ var rect = element.getBoundingClientRect(),
+ pRect = parent.getBoundingClientRect();
+
+ return (pRect.bottom - rect.bottom).round();
+ }
+ });
+ }
+
+ Element.Offset = Class.create({
+ initialize: function(left, top) {
+ this.left = left.round();
+ this.top = top.round();
+
+ this[0] = this.left;
+ this[1] = this.top;
+ },
+
+ relativeTo: function(offset) {
+ return new Element.Offset(
+ this.left - offset.left,
+ this.top - offset.top
+ );
+ },
+
+ inspect: function() {
+ return "#<Element.Offset left: #{left} top: #{top}>".interpolate(this);
+ },
+
+ toString: function() {
+ return "[#{left}, #{top}]".interpolate(this);
+ },
+
+ toArray: function() {
+ return [this.left, this.top];
+ }
+ });
+
+ function getLayout(element, preCompute) {
+ return new Element.Layout(element, preCompute);
+ }
+
+ function measure(element, property) {
+ return $(element).getLayout().get(property);
+ }
+
+ function getDimensions(element) {
+ var layout = $(element).getLayout();
+ return {
+ width: layout.get('width'),
+ height: layout.get('height')
+ };
+ }
+
+ function getOffsetParent(element) {
+ if (isDetached(element)) return $(document.body);
+
+ var isInline = (Element.getStyle(element, 'display') === 'inline');
+ if (!isInline && element.offsetParent) return $(element.offsetParent);
+ if (element === document.body) return $(element);
+
+ while ((element = element.parentNode) && element !== document.body) {
+ if (Element.getStyle(element, 'position') !== 'static') {
+ return (element.nodeName === 'HTML') ? $(document.body) : $(element);
+ }
+ }
+
+ return $(document.body);
+ }
+
+
+ function cumulativeOffset(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ } while (element);
+ return new Element.Offset(valueL, valueT);
+ }
+
+ function positionedOffset(element) {
+ var layout = element.getLayout();
+
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ if (element) {
+ if (isBody(element)) break;
+ var p = Element.getStyle(element, 'position');
+ if (p !== 'static') break;
+ }
+ } while (element);
+
+ valueL -= layout.get('margin-top');
+ valueT -= layout.get('margin-left');
+
+ return new Element.Offset(valueL, valueT);
+ }
+
+ function cumulativeScrollOffset(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.scrollTop || 0;
+ valueL += element.scrollLeft || 0;
+ element = element.parentNode;
+ } while (element);
+ return new Element.Offset(valueL, valueT);
+ }
+
+ function viewportOffset(forElement) {
+ var valueT = 0, valueL = 0, docBody = document.body;
+
+ var element = forElement;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ if (element.offsetParent == docBody &&
+ Element.getStyle(element, 'position') == 'absolute') break;
+ } while (element = element.offsetParent);
+
+ element = forElement;
+ do {
+ if (element != docBody) {
+ valueT -= element.scrollTop || 0;
+ valueL -= element.scrollLeft || 0;
+ }
+ } while (element = element.parentNode);
+ return new Element.Offset(valueL, valueT);
+ }
+
+ function absolutize(element) {
+ element = $(element);
+
+ if (Element.getStyle(element, 'position') === 'absolute') {
+ return element;
+ }
+
+ var offsetParent = getOffsetParent(element);
+ var eOffset = element.viewportOffset(),
+ pOffset = offsetParent.viewportOffset();
+
+ var offset = eOffset.relativeTo(pOffset);
+ var layout = element.getLayout();
+
+ element.store('prototype_absolutize_original_styles', {
+ left: element.getStyle('left'),
+ top: element.getStyle('top'),
+ width: element.getStyle('width'),
+ height: element.getStyle('height')
+ });
+
+ element.setStyle({
+ position: 'absolute',
+ top: offset.top + 'px',
+ left: offset.left + 'px',
+ width: layout.get('width') + 'px',
+ height: layout.get('height') + 'px'
+ });
+
+ return element;
+ }
+
+ function relativize(element) {
+ element = $(element);
+ if (Element.getStyle(element, 'position') === 'relative') {
+ return element;
+ }
+
+ var originalStyles =
+ element.retrieve('prototype_absolutize_original_styles');
+
+ if (originalStyles) element.setStyle(originalStyles);
+ return element;
+ }
+
+ Element.addMethods({
+ getLayout: getLayout,
+ measure: measure,
+ getDimensions: getDimensions,
+ getOffsetParent: getOffsetParent,
+ cumulativeOffset: cumulativeOffset,
+ positionedOffset: positionedOffset,
+ cumulativeScrollOffset: cumulativeScrollOffset,
+ viewportOffset: viewportOffset,
+ absolutize: absolutize,
+ relativize: relativize
+ });
+
+ function isBody(element) {
+ return element.nodeName.toUpperCase() === 'BODY';
+ }
+
+ function isDetached(element) {
+ return element !== document.body &&
+ !Element.descendantOf(element, document.body);
+ }
+
+ if ('getBoundingClientRect' in document.documentElement) {
+ Element.addMethods({
+ viewportOffset: function(element) {
+ element = $(element);
+ if (isDetached(element)) return new Element.Offset(0, 0);
+
+ var rect = element.getBoundingClientRect(),
+ docEl = document.documentElement;
+ return new Element.Offset(rect.left - docEl.clientLeft,
+ rect.top - docEl.clientTop);
+ },
+
+ positionedOffset: function(element) {
+ element = $(element);
+ var parent = element.getOffsetParent();
+ if (isDetached(element)) return new Element.Offset(0, 0);
+
+ if (element.offsetParent &&
+ element.offsetParent.nodeName.toUpperCase() === 'HTML') {
+ return positionedOffset(element);
+ }
+
+ var eOffset = element.viewportOffset(),
+ pOffset = isBody(parent) ? viewportOffset(parent) :
+ parent.viewportOffset();
+ var retOffset = eOffset.relativeTo(pOffset);
+
+ var layout = element.getLayout();
+ var top = retOffset.top - layout.get('margin-top');
+ var left = retOffset.left - layout.get('margin-left');
+
+ return new Element.Offset(left, top);
+ }
+ });
+ }
+})();
+window.$$ = function() {
+ var expression = $A(arguments).join(', ');
+ return Prototype.Selector.select(expression, document);
+};
+
+Prototype.Selector = (function() {
+
+ function select() {
+ throw new Error('Method "Prototype.Selector.select" must be defined.');
+ }
+
+ function match() {
+ throw new Error('Method "Prototype.Selector.match" must be defined.');
+ }
+
+ function find(elements, expression, index) {
+ index = index || 0;
+ var match = Prototype.Selector.match, length = elements.length, matchIndex…
+
+ for (i = 0; i < length; i++) {
+ if (match(elements[i], expression) && index == matchIndex++) {
+ return Element.extend(elements[i]);
+ }
+ }
+ }
+
+ function extendElements(elements) {
+ for (var i = 0, length = elements.length; i < length; i++) {
+ Element.extend(elements[i]);
+ }
+ return elements;
+ }
+
+
+ var K = Prototype.K;
+
+ return {
+ select: select,
+ match: match,
+ find: find,
+ extendElements: (Element.extend === K) ? K : extendElements,
+ extendElement: Element.extend
+ };
+})();
+Prototype._original_property = window.Sizzle;
+/*!
+ * Sizzle CSS Selector Engine - v1.0
+ * Copyright 2009, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|…
+ done = 0,
+ toString = Object.prototype.toString,
+ hasDuplicate = false,
+ baseHasDuplicate = true;
+
+[0, 0].sort(function(){
+ baseHasDuplicate = false;
+ return 0;
+});
+
+var Sizzle = function(selector, context, results, seed) {
+ results = results || [];
+ var origContext = context = context || document;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var parts = [], m, set, checkSet, check, mode, extra, prune = true, co…
+ soFar = selector;
+
+ while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) {
+ soFar = m[3];
+
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = m[3];
+ break;
+ }
+ }
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context );
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] )
+ selector += parts.shift();
+
+ set = posProcess( selector, set );
+ }
+ }
+ } else {
+ if ( !seed && parts.length > 1 && context.nodeType === 9 && !c…
+ Expr.match.ID.test(parts[0]) && !Expr.match.ID…
+ var ret = Sizzle.find( parts.shift(), context, context…
+ context = ret.expr ? Sizzle.filter( ret.expr, ret.set …
+ }
+
+ if ( context ) {
+ var ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 &…
+ set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : …
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray(set);
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ var cur = parts.pop(), pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, contextXM…
+ }
+ } else {
+ checkSet = parts = [];
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ throw "Syntax error, unrecognized expression: " + (cur || sele…
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+ } else if ( context && context.nodeType === 1 ) {
+ for ( var i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || c…
+ results.push( set[i] );
+ }
+ }
+ } else {
+ for ( var i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1…
+ results.push( set[i] );
+ }
+ }
+ }
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, origContext, results, seed );
+ Sizzle.uniqueSort( results );
+ }
+
+ return results;
+};
+
+Sizzle.uniqueSort = function(results){
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort(sortOrder);
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[i-1] ) {
+ results.splice(i--, 1);
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function(expr, set){
+ return Sizzle(expr, null, null, set);
+};
+
+Sizzle.find = function(expr, context, isXML){
+ var set, match;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+ var type = Expr.order[i], match;
+
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+ var left = match[1];
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace(/\\/g, "");
+ set = Expr.find[ type ]( match, context, isXML…
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type …
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = context.getElementsByTagName("*");
+ }
+
+ return {set: set, expr: expr};
+};
+
+Sizzle.filter = function(expr, set, inplace, not){
+ var old = expr, result = [], curLoop = set, match, anyFound,
+ isXMLFilter = set && set[0] && isXML(set[0]);
+
+ while ( expr && set.length ) {
+ for ( var type in Expr.filter ) {
+ if ( (match = Expr.match[ type ].exec( expr )) != null…
+ var filter = Expr.filter[ type ], found, item;
+ anyFound = false;
+
+ if ( curLoop == result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match,…
+
+ if ( !match ) {
+ anyFound = found = true;
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( var i = 0; (item = curLoop[i]) !…
+ if ( item ) {
+ found = filter( item, …
+ var pass = not ^ !!fou…
+
+ if ( inplace && found …
+ if ( pass ) {
+ anyFou…
+ } else {
+ curLoo…
+ }
+ } else if ( pass ) {
+ result.push( i…
+ anyFound = tru…
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type …
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ if ( expr == old ) {
+ if ( anyFound == null ) {
+ throw "Syntax error, unrecognized expression: …
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*…
+ TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\)…
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]…
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]…
+ },
+ leftMatch: {},
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+ attrHandle: {
+ href: function(elem){
+ return elem.getAttribute("href");
+ }
+ },
+ relative: {
+ "+": function(checkSet, part, isXML){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !/\W/.test(part),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag && !isXML ) {
+ part = part.toUpperCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++…
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) …
+
+ checkSet[i] = isPartStrNotTag || elem …
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+ ">": function(checkSet, part, isXML){
+ var isPartStr = typeof part === "string";
+
+ if ( isPartStr && !/\W/.test(part) ) {
+ part = isXML ? part : part.toUpperCase();
+
+ for ( var i = 0, l = checkSet.length; i < l; i…
+ var elem = checkSet[i];
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName …
+ }
+ }
+ } else {
+ for ( var i = 0, l = checkSet.length; i < l; i…
+ var elem = checkSet[i];
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === pa…
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+ "": function(checkSet, part, isXML){
+ var doneName = done++, checkFn = dirCheck;
+
+ if ( !/\W/.test(part) ) {
+ var nodeCheck = part = isXML ? part : part.toU…
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn("parentNode", part, doneName, checkSet, nodeCh…
+ },
+ "~": function(checkSet, part, isXML){
+ var doneName = done++, checkFn = dirCheck;
+
+ if ( typeof part === "string" && !/\W/.test(part) ) {
+ var nodeCheck = part = isXML ? part : part.toU…
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn("previousSibling", part, doneName, checkSet, n…
+ }
+ },
+ find: {
+ ID: function(match, context, isXML){
+ if ( typeof context.getElementById !== "undefined" && …
+ var m = context.getElementById(match[1]);
+ return m ? [m] : [];
+ }
+ },
+ NAME: function(match, context, isXML){
+ if ( typeof context.getElementsByName !== "undefined" …
+ var ret = [], results = context.getElementsByN…
+
+ for ( var i = 0, l = results.length; i < l; i+…
+ if ( results[i].getAttribute("name") =…
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+ TAG: function(match, context){
+ return context.getElementsByTagName(match[1]);
+ }
+ },
+ preFilter: {
+ CLASS: function(match, curLoop, inplace, result, not, isXML){
+ match = " " + match[1].replace(/\\/g, "") + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i+…
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + e…
+ if ( !inplace )
+ result.push( elem );
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+ ID: function(match){
+ return match[1].replace(/\\/g, "");
+ },
+ TAG: function(match, curLoop){
+ for ( var i = 0; curLoop[i] === false; i++ ){}
+ return curLoop[i] && isXML(curLoop[i]) ? match[1] : ma…
+ },
+ CHILD: function(match){
+ if ( match[1] == "nth" ) {
+ var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+ match[2] == "even" && "2n" || match[2]…
+ !/\D/.test( match[2] ) && "0n+" + matc…
+
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+
+ match[0] = done++;
+
+ return match;
+ },
+ ATTR: function(match, curLoop, inplace, result, not, isXML){
+ var name = match[1].replace(/\\/g, "");
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+ PSEUDO: function(match, curLoop, inplace, result, not){
+ if ( match[1] === "not" ) {
+ if ( ( chunker.exec(match[3]) || "" ).length >…
+ match[3] = Sizzle(match[3], null, null…
+ } else {
+ var ret = Sizzle.filter(match[3], curL…
+ if ( !inplace ) {
+ result.push.apply( result, ret…
+ }
+ return false;
+ }
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.ma…
+ return true;
+ }
+
+ return match;
+ },
+ POS: function(match){
+ match.unshift( true );
+ return match;
+ }
+ },
+ filters: {
+ enabled: function(elem){
+ return elem.disabled === false && elem.type !== "hidde…
+ },
+ disabled: function(elem){
+ return elem.disabled === true;
+ },
+ checked: function(elem){
+ return elem.checked === true;
+ },
+ selected: function(elem){
+ elem.parentNode.selectedIndex;
+ return elem.selected === true;
+ },
+ parent: function(elem){
+ return !!elem.firstChild;
+ },
+ empty: function(elem){
+ return !elem.firstChild;
+ },
+ has: function(elem, i, match){
+ return !!Sizzle( match[3], elem ).length;
+ },
+ header: function(elem){
+ return /h\d/i.test( elem.nodeName );
+ },
+ text: function(elem){
+ return "text" === elem.type;
+ },
+ radio: function(elem){
+ return "radio" === elem.type;
+ },
+ checkbox: function(elem){
+ return "checkbox" === elem.type;
+ },
+ file: function(elem){
+ return "file" === elem.type;
+ },
+ password: function(elem){
+ return "password" === elem.type;
+ },
+ submit: function(elem){
+ return "submit" === elem.type;
+ },
+ image: function(elem){
+ return "image" === elem.type;
+ },
+ reset: function(elem){
+ return "reset" === elem.type;
+ },
+ button: function(elem){
+ return "button" === elem.type || elem.nodeName.toUpper…
+ },
+ input: function(elem){
+ return /input|select|textarea|button/i.test(elem.nodeN…
+ }
+ },
+ setFilters: {
+ first: function(elem, i){
+ return i === 0;
+ },
+ last: function(elem, i, match, array){
+ return i === array.length - 1;
+ },
+ even: function(elem, i){
+ return i % 2 === 0;
+ },
+ odd: function(elem, i){
+ return i % 2 === 1;
+ },
+ lt: function(elem, i, match){
+ return i < match[3] - 0;
+ },
+ gt: function(elem, i, match){
+ return i > match[3] - 0;
+ },
+ nth: function(elem, i, match){
+ return match[3] - 0 == i;
+ },
+ eq: function(elem, i, match){
+ return match[3] - 0 == i;
+ }
+ },
+ filter: {
+ PSEUDO: function(elem, match, i, array){
+ var name = match[1], filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || …
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var i = 0, l = not.length; i < l; i++ ) {
+ if ( not[i] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ },
+ CHILD: function(elem, match){
+ var type = match[1], node = elem;
+ switch (type) {
+ case 'only':
+ case 'first':
+ while ( (node = node.previousSibling) …
+ if ( node.nodeType === 1 ) ret…
+ }
+ if ( type == 'first') return true;
+ node = elem;
+ case 'last':
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) ret…
+ }
+ return true;
+ case 'nth':
+ var first = match[2], last = match[3];
+
+ if ( first == 1 && last == 0 ) {
+ return true;
+ }
+
+ var doneName = match[0],
+ parent = elem.parentNode;
+
+ if ( parent && (parent.sizcache !== do…
+ var count = 0;
+ for ( node = parent.firstChild…
+ if ( node.nodeType ===…
+ node.nodeIndex…
+ }
+ }
+ parent.sizcache = doneName;
+ }
+
+ var diff = elem.nodeIndex - last;
+ if ( first == 0 ) {
+ return diff == 0;
+ } else {
+ return ( diff % first == 0 && …
+ }
+ }
+ },
+ ID: function(elem, match){
+ return elem.nodeType === 1 && elem.getAttribute("id") …
+ },
+ TAG: function(elem, match){
+ return (match === "*" && elem.nodeType === 1) || elem.…
+ },
+ CLASS: function(elem, match){
+ return (" " + (elem.className || elem.getAttribute("cl…
+ .indexOf( match ) > -1;
+ },
+ ATTR: function(elem, match){
+ var name = match[1],
+ result = Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value != check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === …
+ type === "|=" ?
+ value === check || value.substr(0, check.lengt…
+ false;
+ },
+ POS: function(elem, match, i, array){
+ var name = match[2], filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS;
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]…
+ Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.…
+}
+
+var makeArray = function(array, results) {
+ array = Array.prototype.slice.call( array, 0 );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes, 0 );
+
+} catch(e){
+ makeArray = function(array, results) {
+ var ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var i = 0, l = array.length; i < l; i++ …
+ ret.push( array[i] );
+ }
+ } else {
+ for ( var i = 0; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( !a.compareDocumentPosition || !b.compareDocumentPosition …
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ }
+
+ var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 …
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+} else if ( "sourceIndex" in document.documentElement ) {
+ sortOrder = function( a, b ) {
+ if ( !a.sourceIndex || !b.sourceIndex ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ }
+
+ var ret = a.sourceIndex - b.sourceIndex;
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+} else if ( document.createRange ) {
+ sortOrder = function( a, b ) {
+ if ( !a.ownerDocument || !b.ownerDocument ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ }
+
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDo…
+ aRange.setStart(a, 0);
+ aRange.setEnd(a, 0);
+ bRange.setStart(b, 0);
+ bRange.setEnd(b, 0);
+ var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRa…
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+}
+
+(function(){
+ var form = document.createElement("div"),
+ id = "script" + (new Date).getTime();
+ form.innerHTML = "<a name='" + id + "'/>";
+
+ var root = document.documentElement;
+ root.insertBefore( form, root.firstChild );
+
+ if ( !!document.getElementById( id ) ) {
+ Expr.find.ID = function(match, context, isXML){
+ if ( typeof context.getElementById !== "undefined" && …
+ var m = context.getElementById(match[1]);
+ return m ? m.id === match[1] || typeof m.getAt…
+ }
+ };
+
+ Expr.filter.ID = function(elem, match){
+ var node = typeof elem.getAttributeNode !== "undefined…
+ return elem.nodeType === 1 && node && node.nodeValue =…
+ };
+ }
+
+ root.removeChild( form );
+ root = form = null; // release memory in IE
+})();
+
+(function(){
+
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function(match, context){
+ var results = context.getElementsByTagName(match[1]);
+
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ div.innerHTML = "<a href='#'></a>";
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefin…
+ div.firstChild.getAttribute("href") !== "#" ) {
+ Expr.attrHandle.href = function(elem){
+ return elem.getAttribute("href", 2);
+ };
+ }
+
+ div = null; // release memory in IE
+})();
+
+if ( document.querySelectorAll ) (function(){
+ var oldSizzle = Sizzle, div = document.createElement("div");
+ div.innerHTML = "<p class='TEST'></p>";
+
+ if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === …
+ return;
+ }
+
+ Sizzle = function(query, context, extra, seed){
+ context = context || document;
+
+ if ( !seed && context.nodeType === 9 && !isXML(context) ) {
+ try {
+ return makeArray( context.querySelectorAll(que…
+ } catch(e){}
+ }
+
+ return oldSizzle(query, context, extra, seed);
+ };
+
+ for ( var prop in oldSizzle ) {
+ Sizzle[ prop ] = oldSizzle[ prop ];
+ }
+
+ div = null; // release memory in IE
+})();
+
+if ( document.getElementsByClassName && document.documentElement.getElementsBy…
+ var div = document.createElement("div");
+ div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+ if ( div.getElementsByClassName("e").length === 0 )
+ return;
+
+ div.lastChild.className = "e";
+
+ if ( div.getElementsByClassName("e").length === 1 )
+ return;
+
+ Expr.order.splice(1, 0, "CLASS");
+ Expr.find.CLASS = function(match, context, isXML) {
+ if ( typeof context.getElementsByClassName !== "undefined" && …
+ return context.getElementsByClassName(match[1]);
+ }
+ };
+
+ div = null; // release memory in IE
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ var sibDir = dir == "previousSibling" && !isXML;
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ if ( sibDir && elem.nodeType === 1 ){
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ elem = elem[dir];
+ var match = false;
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 && !isXML ){
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+
+ if ( elem.nodeName === cur ) {
+ match = elem;
+ break;
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ var sibDir = dir == "previousSibling" && !isXML;
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ if ( sibDir && elem.nodeType === 1 ) {
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ elem = elem[dir];
+ var match = false;
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 ) {
+ if ( !isXML ) {
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ if ( typeof cur !== "string" ) {
+ if ( elem === cur ) {
+ match = true;
+ break;
+ }
+
+ } else if ( Sizzle.filter( cur, [elem]…
+ match = elem;
+ break;
+ }
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+var contains = document.compareDocumentPosition ? function(a, b){
+ return a.compareDocumentPosition(b) & 16;
+} : function(a, b){
+ return a !== b && (a.contains ? a.contains(b) : true);
+};
+
+var isXML = function(elem){
+ return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML"…
+ !!elem.ownerDocument && elem.ownerDocument.documentElement.nod…
+};
+
+var posProcess = function(selector, context){
+ var tmpSet = [], later = "", match,
+ root = context.nodeType ? [context] : context;
+
+ while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+ later += match[0];
+ selector = selector.replace( Expr.match.PSEUDO, "" );
+ }
+
+ selector = Expr.relative[selector] ? selector + "*" : selector;
+
+ for ( var i = 0, l = root.length; i < l; i++ ) {
+ Sizzle( selector, root[i], tmpSet );
+ }
+
+ return Sizzle.filter( later, tmpSet );
+};
+
+
+window.Sizzle = Sizzle;
+
+})();
+
+;(function(engine) {
+ var extendElements = Prototype.Selector.extendElements;
+
+ function select(selector, scope) {
+ return extendElements(engine(selector, scope || document));
+ }
+
+ function match(element, selector) {
+ return engine.matches(selector, [element]).length == 1;
+ }
+
+ Prototype.Selector.engine = engine;
+ Prototype.Selector.select = select;
+ Prototype.Selector.match = match;
+})(Sizzle);
+
+window.Sizzle = Prototype._original_property;
+delete Prototype._original_property;
+
+var Form = {
+ reset: function(form) {
+ form = $(form);
+ form.reset();
+ return form;
+ },
+
+ serializeElements: function(elements, options) {
+ if (typeof options != 'object') options = { hash: !!options };
+ else if (Object.isUndefined(options.hash)) options.hash = true;
+ var key, value, submitted = false, submit = options.submit;
+
+ var data = elements.inject({ }, function(result, element) {
+ if (!element.disabled && element.name) {
+ key = element.name; value = $(element).getValue();
+ if (value != null && element.type != 'file' && (element.type != 'submi…
+ submit !== false && (!submit || key == submit) && (submitted = tru…
+ if (key in result) {
+ if (!Object.isArray(result[key])) result[key] = [result[key]];
+ result[key].push(value);
+ }
+ else result[key] = value;
+ }
+ }
+ return result;
+ });
+
+ return options.hash ? data : Object.toQueryString(data);
+ }
+};
+
+Form.Methods = {
+ serialize: function(form, options) {
+ return Form.serializeElements(Form.getElements(form), options);
+ },
+
+ getElements: function(form) {
+ var elements = $(form).getElementsByTagName('*'),
+ element,
+ arr = [ ],
+ serializers = Form.Element.Serializers;
+ for (var i = 0; element = elements[i]; i++) {
+ arr.push(element);
+ }
+ return arr.inject([], function(elements, child) {
+ if (serializers[child.tagName.toLowerCase()])
+ elements.push(Element.extend(child));
+ return elements;
+ })
+ },
+
+ getInputs: function(form, typeName, name) {
+ form = $(form);
+ var inputs = form.getElementsByTagName('input');
+
+ if (!typeName && !name) return $A(inputs).map(Element.extend);
+
+ for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i…
+ var input = inputs[i];
+ if ((typeName && input.type != typeName) || (name && input.name != name))
+ continue;
+ matchingInputs.push(Element.extend(input));
+ }
+
+ return matchingInputs;
+ },
+
+ disable: function(form) {
+ form = $(form);
+ Form.getElements(form).invoke('disable');
+ return form;
+ },
+
+ enable: function(form) {
+ form = $(form);
+ Form.getElements(form).invoke('enable');
+ return form;
+ },
+
+ findFirstElement: function(form) {
+ var elements = $(form).getElements().findAll(function(element) {
+ return 'hidden' != element.type && !element.disabled;
+ });
+ var firstByIndex = elements.findAll(function(element) {
+ return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
+ }).sortBy(function(element) { return element.tabIndex }).first();
+
+ return firstByIndex ? firstByIndex : elements.find(function(element) {
+ return /^(?:input|select|textarea)$/i.test(element.tagName);
+ });
+ },
+
+ focusFirstElement: function(form) {
+ form = $(form);
+ form.findFirstElement().activate();
+ return form;
+ },
+
+ request: function(form, options) {
+ form = $(form), options = Object.clone(options || { });
+
+ var params = options.parameters, action = form.readAttribute('action') || …
+ if (action.blank()) action = window.location.href;
+ options.parameters = form.serialize(true);
+
+ if (params) {
+ if (Object.isString(params)) params = params.toQueryParams();
+ Object.extend(options.parameters, params);
+ }
+
+ if (form.hasAttribute('method') && !options.method)
+ options.method = form.method;
+
+ return new Ajax.Request(action, options);
+ }
+};
+
+/*--------------------------------------------------------------------------*/
+
+
+Form.Element = {
+ focus: function(element) {
+ $(element).focus();
+ return element;
+ },
+
+ select: function(element) {
+ $(element).select();
+ return element;
+ }
+};
+
+Form.Element.Methods = {
+
+ serialize: function(element) {
+ element = $(element);
+ if (!element.disabled && element.name) {
+ var value = element.getValue();
+ if (value != undefined) {
+ var pair = { };
+ pair[element.name] = value;
+ return Object.toQueryString(pair);
+ }
+ }
+ return '';
+ },
+
+ getValue: function(element) {
+ element = $(element);
+ var method = element.tagName.toLowerCase();
+ return Form.Element.Serializers[method](element);
+ },
+
+ setValue: function(element, value) {
+ element = $(element);
+ var method = element.tagName.toLowerCase();
+ Form.Element.Serializers[method](element, value);
+ return element;
+ },
+
+ clear: function(element) {
+ $(element).value = '';
+ return element;
+ },
+
+ present: function(element) {
+ return $(element).value != '';
+ },
+
+ activate: function(element) {
+ element = $(element);
+ try {
+ element.focus();
+ if (element.select && (element.tagName.toLowerCase() != 'input' ||
+ !(/^(?:button|reset|submit)$/i.test(element.type))))
+ element.select();
+ } catch (e) { }
+ return element;
+ },
+
+ disable: function(element) {
+ element = $(element);
+ element.disabled = true;
+ return element;
+ },
+
+ enable: function(element) {
+ element = $(element);
+ element.disabled = false;
+ return element;
+ }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element.Serializers = {
+ input: function(element, value) {
+ switch (element.type.toLowerCase()) {
+ case 'checkbox':
+ case 'radio':
+ return Form.Element.Serializers.inputSelector(element, value);
+ default:
+ return Form.Element.Serializers.textarea(element, value);
+ }
+ },
+
+ inputSelector: function(element, value) {
+ if (Object.isUndefined(value)) return element.checked ? element.value : nu…
+ else element.checked = !!value;
+ },
+
+ textarea: function(element, value) {
+ if (Object.isUndefined(value)) return element.value;
+ else element.value = value;
+ },
+
+ select: function(element, value) {
+ if (Object.isUndefined(value))
+ return this[element.type == 'select-one' ?
+ 'selectOne' : 'selectMany'](element);
+ else {
+ var opt, currentValue, single = !Object.isArray(value);
+ for (var i = 0, length = element.length; i < length; i++) {
+ opt = element.options[i];
+ currentValue = this.optionValue(opt);
+ if (single) {
+ if (currentValue == value) {
+ opt.selected = true;
+ return;
+ }
+ }
+ else opt.selected = value.include(currentValue);
+ }
+ }
+ },
+
+ selectOne: function(element) {
+ var index = element.selectedIndex;
+ return index >= 0 ? this.optionValue(element.options[index]) : null;
+ },
+
+ selectMany: function(element) {
+ var values, length = element.length;
+ if (!length) return null;
+
+ for (var i = 0, values = []; i < length; i++) {
+ var opt = element.options[i];
+ if (opt.selected) values.push(this.optionValue(opt));
+ }
+ return values;
+ },
+
+ optionValue: function(opt) {
+ return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
+ }
+};
+
+/*--------------------------------------------------------------------------*/
+
+
+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
+ initialize: function($super, element, frequency, callback) {
+ $super(callback, frequency);
+ this.element = $(element);
+ this.lastValue = this.getValue();
+ },
+
+ execute: function() {
+ var value = this.getValue();
+ if (Object.isString(this.lastValue) && Object.isString(value) ?
+ this.lastValue != value : String(this.lastValue) != String(value)) {
+ this.callback(this.element, value);
+ this.lastValue = value;
+ }
+ }
+});
+
+Form.Element.Observer = Class.create(Abstract.TimedObserver, {
+ getValue: function() {
+ return Form.Element.getValue(this.element);
+ }
+});
+
+Form.Observer = Class.create(Abstract.TimedObserver, {
+ getValue: function() {
+ return Form.serialize(this.element);
+ }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = Class.create({
+ initialize: function(element, callback) {
+ this.element = $(element);
+ this.callback = callback;
+
+ this.lastValue = this.getValue();
+ if (this.element.tagName.toLowerCase() == 'form')
+ this.registerFormCallbacks();
+ else
+ this.registerCallback(this.element);
+ },
+
+ onElementEvent: function() {
+ var value = this.getValue();
+ if (this.lastValue != value) {
+ this.callback(this.element, value);
+ this.lastValue = value;
+ }
+ },
+
+ registerFormCallbacks: function() {
+ Form.getElements(this.element).each(this.registerCallback, this);
+ },
+
+ registerCallback: function(element) {
+ if (element.type) {
+ switch (element.type.toLowerCase()) {
+ case 'checkbox':
+ case 'radio':
+ Event.observe(element, 'click', this.onElementEvent.bind(this));
+ break;
+ default:
+ Event.observe(element, 'change', this.onElementEvent.bind(this));
+ break;
+ }
+ }
+ }
+});
+
+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
+ getValue: function() {
+ return Form.Element.getValue(this.element);
+ }
+});
+
+Form.EventObserver = Class.create(Abstract.EventObserver, {
+ getValue: function() {
+ return Form.serialize(this.element);
+ }
+});
+(function() {
+
+ var Event = {
+ KEY_BACKSPACE: 8,
+ KEY_TAB: 9,
+ KEY_RETURN: 13,
+ KEY_ESC: 27,
+ KEY_LEFT: 37,
+ KEY_UP: 38,
+ KEY_RIGHT: 39,
+ KEY_DOWN: 40,
+ KEY_DELETE: 46,
+ KEY_HOME: 36,
+ KEY_END: 35,
+ KEY_PAGEUP: 33,
+ KEY_PAGEDOWN: 34,
+ KEY_INSERT: 45,
+
+ cache: {}
+ };
+
+ var docEl = document.documentElement;
+ var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl
+ && 'onmouseleave' in docEl;
+
+ var _isButton;
+ if (Prototype.Browser.IE) {
+ var buttonMap = { 0: 1, 1: 4, 2: 2 };
+ _isButton = function(event, code) {
+ return event.button === buttonMap[code];
+ };
+ } else if (Prototype.Browser.WebKit) {
+ _isButton = function(event, code) {
+ switch (code) {
+ case 0: return event.which == 1 && !event.metaKey;
+ case 1: return event.which == 1 && event.metaKey;
+ default: return false;
+ }
+ };
+ } else {
+ _isButton = function(event, code) {
+ return event.which ? (event.which === code + 1) : (event.button === code…
+ };
+ }
+
+ function isLeftClick(event) { return _isButton(event, 0) }
+
+ function isMiddleClick(event) { return _isButton(event, 1) }
+
+ function isRightClick(event) { return _isButton(event, 2) }
+
+ function element(event) {
+ event = Event.extend(event);
+
+ var node = event.target, type = event.type,
+ currentTarget = event.currentTarget;
+
+ if (currentTarget && currentTarget.tagName) {
+ if (type === 'load' || type === 'error' ||
+ (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
+ && currentTarget.type === 'radio'))
+ node = currentTarget;
+ }
+
+ if (node.nodeType == Node.TEXT_NODE)
+ node = node.parentNode;
+
+ return Element.extend(node);
+ }
+
+ function findElement(event, expression) {
+ var element = Event.element(event);
+ if (!expression) return element;
+ while (element) {
+ if (Object.isElement(element) && Prototype.Selector.match(element, expre…
+ return Element.extend(element);
+ }
+ element = element.parentNode;
+ }
+ }
+
+ function pointer(event) {
+ return { x: pointerX(event), y: pointerY(event) };
+ }
+
+ function pointerX(event) {
+ var docElement = document.documentElement,
+ body = document.body || { scrollLeft: 0 };
+
+ return event.pageX || (event.clientX +
+ (docElement.scrollLeft || body.scrollLeft) -
+ (docElement.clientLeft || 0));
+ }
+
+ function pointerY(event) {
+ var docElement = document.documentElement,
+ body = document.body || { scrollTop: 0 };
+
+ return event.pageY || (event.clientY +
+ (docElement.scrollTop || body.scrollTop) -
+ (docElement.clientTop || 0));
+ }
+
+
+ function stop(event) {
+ Event.extend(event);
+ event.preventDefault();
+ event.stopPropagation();
+
+ event.stopped = true;
+ }
+
+ Event.Methods = {
+ isLeftClick: isLeftClick,
+ isMiddleClick: isMiddleClick,
+ isRightClick: isRightClick,
+
+ element: element,
+ findElement: findElement,
+
+ pointer: pointer,
+ pointerX: pointerX,
+ pointerY: pointerY,
+
+ stop: stop
+ };
+
+
+ var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
+ m[name] = Event.Methods[name].methodize();
+ return m;
+ });
+
+ if (Prototype.Browser.IE) {
+ function _relatedTarget(event) {
+ var element;
+ switch (event.type) {
+ case 'mouseover': element = event.fromElement; break;
+ case 'mouseout': element = event.toElement; break;
+ default: return null;
+ }
+ return Element.extend(element);
+ }
+
+ Object.extend(methods, {
+ stopPropagation: function() { this.cancelBubble = true },
+ preventDefault: function() { this.returnValue = false },
+ inspect: function() { return '[object Event]' }
+ });
+
+ Event.extend = function(event, element) {
+ if (!event) return false;
+ if (event._extendedByPrototype) return event;
+
+ event._extendedByPrototype = Prototype.emptyFunction;
+ var pointer = Event.pointer(event);
+
+ Object.extend(event, {
+ target: event.srcElement || element,
+ relatedTarget: _relatedTarget(event),
+ pageX: pointer.x,
+ pageY: pointer.y
+ });
+
+ return Object.extend(event, methods);
+ };
+ } else {
+ Event.prototype = window.Event.prototype || document.createEvent('HTMLEven…
+ Object.extend(Event.prototype, methods);
+ Event.extend = Prototype.K;
+ }
+
+ function _createResponder(element, eventName, handler) {
+ var registry = Element.retrieve(element, 'prototype_event_registry');
+
+ if (Object.isUndefined(registry)) {
+ CACHE.push(element);
+ registry = Element.retrieve(element, 'prototype_event_registry', $H());
+ }
+
+ var respondersForEvent = registry.get(eventName);
+ if (Object.isUndefined(respondersForEvent)) {
+ respondersForEvent = [];
+ registry.set(eventName, respondersForEvent);
+ }
+
+ if (respondersForEvent.pluck('handler').include(handler)) return false;
+
+ var responder;
+ if (eventName.include(":")) {
+ responder = function(event) {
+ if (Object.isUndefined(event.eventName))
+ return false;
+
+ if (event.eventName !== eventName)
+ return false;
+
+ Event.extend(event, element);
+ handler.call(element, event);
+ };
+ } else {
+ if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED &&
+ (eventName === "mouseenter" || eventName === "mouseleave")) {
+ if (eventName === "mouseenter" || eventName === "mouseleave") {
+ responder = function(event) {
+ Event.extend(event, element);
+
+ var parent = event.relatedTarget;
+ while (parent && parent !== element) {
+ try { parent = parent.parentNode; }
+ catch(e) { parent = element; }
+ }
+
+ if (parent === element) return;
+
+ handler.call(element, event);
+ };
+ }
+ } else {
+ responder = function(event) {
+ Event.extend(event, element);
+ handler.call(element, event);
+ };
+ }
+ }
+
+ responder.handler = handler;
+ respondersForEvent.push(responder);
+ return responder;
+ }
+
+ function _destroyCache() {
+ for (var i = 0, length = CACHE.length; i < length; i++) {
+ Event.stopObserving(CACHE[i]);
+ CACHE[i] = null;
+ }
+ }
+
+ var CACHE = [];
+
+ if (Prototype.Browser.IE)
+ window.attachEvent('onunload', _destroyCache);
+
+ if (Prototype.Browser.WebKit)
+ window.addEventListener('unload', Prototype.emptyFunction, false);
+
+
+ var _getDOMEventName = Prototype.K,
+ translations = { mouseenter: "mouseover", mouseleave: "mouseout" };
+
+ if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) {
+ _getDOMEventName = function(eventName) {
+ return (translations[eventName] || eventName);
+ };
+ }
+
+ function observe(element, eventName, handler) {
+ element = $(element);
+
+ var responder = _createResponder(element, eventName, handler);
+
+ if (!responder) return element;
+
+ if (eventName.include(':')) {
+ if (element.addEventListener)
+ element.addEventListener("dataavailable", responder, false);
+ else {
+ element.attachEvent("ondataavailable", responder);
+ element.attachEvent("onfilterchange", responder);
+ }
+ } else {
+ var actualEventName = _getDOMEventName(eventName);
+
+ if (element.addEventListener)
+ element.addEventListener(actualEventName, responder, false);
+ else
+ element.attachEvent("on" + actualEventName, responder);
+ }
+
+ return element;
+ }
+
+ function stopObserving(element, eventName, handler) {
+ element = $(element);
+
+ var registry = Element.retrieve(element, 'prototype_event_registry');
+ if (!registry) return element;
+
+ if (!eventName) {
+ registry.each( function(pair) {
+ var eventName = pair.key;
+ stopObserving(element, eventName);
+ });
+ return element;
+ }
+
+ var responders = registry.get(eventName);
+ if (!responders) return element;
+
+ if (!handler) {
+ responders.each(function(r) {
+ stopObserving(element, eventName, r.handler);
+ });
+ return element;
+ }
+
+ var responder = responders.find( function(r) { return r.handler === handle…
+ if (!responder) return element;
+
+ if (eventName.include(':')) {
+ if (element.removeEventListener)
+ element.removeEventListener("dataavailable", responder, false);
+ else {
+ element.detachEvent("ondataavailable", responder);
+ element.detachEvent("onfilterchange", responder);
+ }
+ } else {
+ var actualEventName = _getDOMEventName(eventName);
+ if (element.removeEventListener)
+ element.removeEventListener(actualEventName, responder, false);
+ else
+ element.detachEvent('on' + actualEventName, responder);
+ }
+
+ registry.set(eventName, responders.without(responder));
+
+ return element;
+ }
+
+ function fire(element, eventName, memo, bubble) {
+ element = $(element);
+
+ if (Object.isUndefined(bubble))
+ bubble = true;
+
+ if (element == document && document.createEvent && !element.dispatchEvent)
+ element = document.documentElement;
+
+ var event;
+ if (document.createEvent) {
+ event = document.createEvent('HTMLEvents');
+ event.initEvent('dataavailable', true, true);
+ } else {
+ event = document.createEventObject();
+ event.eventType = bubble ? 'ondataavailable' : 'onfilterchange';
+ }
+
+ event.eventName = eventName;
+ event.memo = memo || { };
+
+ if (document.createEvent)
+ element.dispatchEvent(event);
+ else
+ element.fireEvent(event.eventType, event);
+
+ return Event.extend(event);
+ }
+
+ Event.Handler = Class.create({
+ initialize: function(element, eventName, selector, callback) {
+ this.element = $(element);
+ this.eventName = eventName;
+ this.selector = selector;
+ this.callback = callback;
+ this.handler = this.handleEvent.bind(this);
+ },
+
+ start: function() {
+ Event.observe(this.element, this.eventName, this.handler);
+ return this;
+ },
+
+ stop: function() {
+ Event.stopObserving(this.element, this.eventName, this.handler);
+ return this;
+ },
+
+ handleEvent: function(event) {
+ var element = event.findElement(this.selector);
+ if (element) this.callback.call(this.element, event, element);
+ }
+ });
+
+ function on(element, eventName, selector, callback) {
+ element = $(element);
+ if (Object.isFunction(selector) && Object.isUndefined(callback)) {
+ callback = selector, selector = null;
+ }
+
+ return new Event.Handler(element, eventName, selector, callback).start();
+ }
+
+ Object.extend(Event, Event.Methods);
+
+ Object.extend(Event, {
+ fire: fire,
+ observe: observe,
+ stopObserving: stopObserving,
+ on: on
+ });
+
+ Element.addMethods({
+ fire: fire,
+
+ observe: observe,
+
+ stopObserving: stopObserving,
+
+ on: on
+ });
+
+ Object.extend(document, {
+ fire: fire.methodize(),
+
+ observe: observe.methodize(),
+
+ stopObserving: stopObserving.methodize(),
+
+ on: on.methodize(),
+
+ loaded: false
+ });
+
+ if (window.Event) Object.extend(window.Event, Event);
+ else window.Event = Event;
+})();
+
+(function() {
+ /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+ Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */
+
+ var timer;
+
+ function fireContentLoadedEvent() {
+ if (document.loaded) return;
+ if (timer) window.clearTimeout(timer);
+ document.loaded = true;
+ document.fire('dom:loaded');
+ }
+
+ function checkReadyState() {
+ if (document.readyState === 'complete') {
+ document.stopObserving('readystatechange', checkReadyState);
+ fireContentLoadedEvent();
+ }
+ }
+
+ function pollDoScroll() {
+ try { document.documentElement.doScroll('left'); }
+ catch(e) {
+ timer = pollDoScroll.defer();
+ return;
+ }
+ fireContentLoadedEvent();
+ }
+
+ if (document.addEventListener) {
+ document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, fals…
+ } else {
+ document.observe('readystatechange', checkReadyState);
+ if (window == top)
+ timer = pollDoScroll.defer();
+ }
+
+ Event.observe(window, 'load', fireContentLoadedEvent);
+})();
+
+Element.addMethods();
+
+/*------------------------------- DEPRECATED -------------------------------*/
+
+Hash.toQueryString = Object.toQueryString;
+
+var Toggle = { display: Element.toggle };
+
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+ Before: function(element, content) {
+ return Element.insert(element, {before:content});
+ },
+
+ Top: function(element, content) {
+ return Element.insert(element, {top:content});
+ },
+
+ Bottom: function(element, content) {
+ return Element.insert(element, {bottom:content});
+ },
+
+ After: function(element, content) {
+ return Element.insert(element, {after:content});
+ }
+};
+
+var $continue = new Error('"throw $continue" is deprecated, use "return" inste…
+
+var Position = {
+ includeScrollOffsets: false,
+
+ prepare: function() {
+ this.deltaX = window.pageXOffset
+ || document.documentElement.scrollLeft
+ || document.body.scrollLeft
+ || 0;
+ this.deltaY = window.pageYOffset
+ || document.documentElement.scrollTop
+ || document.body.scrollTop
+ || 0;
+ },
+
+ within: function(element, x, y) {
+ if (this.includeScrollOffsets)
+ return this.withinIncludingScrolloffsets(element, x, y);
+ this.xcomp = x;
+ this.ycomp = y;
+ this.offset = Element.cumulativeOffset(element);
+
+ return (y >= this.offset[1] &&
+ y < this.offset[1] + element.offsetHeight &&
+ x >= this.offset[0] &&
+ x < this.offset[0] + element.offsetWidth);
+ },
+
+ withinIncludingScrolloffsets: function(element, x, y) {
+ var offsetcache = Element.cumulativeScrollOffset(element);
+
+ this.xcomp = x + offsetcache[0] - this.deltaX;
+ this.ycomp = y + offsetcache[1] - this.deltaY;
+ this.offset = Element.cumulativeOffset(element);
+
+ return (this.ycomp >= this.offset[1] &&
+ this.ycomp < this.offset[1] + element.offsetHeight &&
+ this.xcomp >= this.offset[0] &&
+ this.xcomp < this.offset[0] + element.offsetWidth);
+ },
+
+ overlap: function(mode, element) {
+ if (!mode) return 0;
+ if (mode == 'vertical')
+ return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+ element.offsetHeight;
+ if (mode == 'horizontal')
+ return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+ element.offsetWidth;
+ },
+
+
+ cumulativeOffset: Element.Methods.cumulativeOffset,
+
+ positionedOffset: Element.Methods.positionedOffset,
+
+ absolutize: function(element) {
+ Position.prepare();
+ return Element.absolutize(element);
+ },
+
+ relativize: function(element) {
+ Position.prepare();
+ return Element.relativize(element);
+ },
+
+ realOffset: Element.Methods.cumulativeScrollOffset,
+
+ offsetParent: Element.Methods.getOffsetParent,
+
+ page: Element.Methods.viewportOffset,
+
+ clone: function(source, target, options) {
+ options = options || { };
+ return Element.clonePosition(target, source, options);
+ }
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!document.getElementsByClassName) document.getElementsByClassName = functi…
+ function iter(name) {
+ return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + na…
+ }
+
+ instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+ function(element, className) {
+ className = className.toString().strip();
+ var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(…
+ return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+ } : function(element, className) {
+ className = className.toString().strip();
+ var elements = [], classNames = (/\s/.test(className) ? $w(className) : nu…
+ if (!classNames && !className) return elements;
+
+ var nodes = $(element).getElementsByTagName('*');
+ className = ' ' + className + ' ';
+
+ for (var i = 0, child, cn; child = nodes[i]; i++) {
+ if (child.className && (cn = ' ' + child.className + ' ') && (cn.include…
+ (classNames && classNames.all(function(name) {
+ return !name.toString().blank() && cn.include(' ' + name + ' ');
+ }))))
+ elements.push(Element.extend(child));
+ }
+ return elements;
+ };
+
+ return function(className, parentElement) {
+ return $(parentElement || document.body).getElementsByClassName(className);
+ };
+}(Element.Methods);
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+ initialize: function(element) {
+ this.element = $(element);
+ },
+
+ _each: function(iterator) {
+ this.element.className.split(/\s+/).select(function(name) {
+ return name.length > 0;
+ })._each(iterator);
+ },
+
+ set: function(className) {
+ this.element.className = className;
+ },
+
+ add: function(classNameToAdd) {
+ if (this.include(classNameToAdd)) return;
+ this.set($A(this).concat(classNameToAdd).join(' '));
+ },
+
+ remove: function(classNameToRemove) {
+ if (!this.include(classNameToRemove)) return;
+ this.set($A(this).without(classNameToRemove).join(' '));
+ },
+
+ toString: function() {
+ return $A(this).join(' ');
+ }
+};
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+
+/*--------------------------------------------------------------------------*/
+
+(function() {
+ window.Selector = Class.create({
+ initialize: function(expression) {
+ this.expression = expression.strip();
+ },
+
+ findElements: function(rootElement) {
+ return Prototype.Selector.select(this.expression, rootElement);
+ },
+
+ match: function(element) {
+ return Prototype.Selector.match(element, this.expression);
+ },
+
+ toString: function() {
+ return this.expression;
+ },
+
+ inspect: function() {
+ return "#<Selector: " + this.expression + ">";
+ }
+ });
+
+ Object.extend(Selector, {
+ matchElements: function(elements, expression) {
+ var match = Prototype.Selector.match,
+ results = [];
+
+ for (var i = 0, length = elements.length; i < length; i++) {
+ var element = elements[i];
+ if (match(element, expression)) {
+ results.push(Element.extend(element));
+ }
+ }
+ return results;
+ },
+
+ findElement: function(elements, expression, index) {
+ index = index || 0;
+ var matchIndex = 0, element;
+ for (var i = 0, length = elements.length; i < length; i++) {
+ element = elements[i];
+ if (Prototype.Selector.match(element, expression) && index === matchIn…
+ return Element.extend(element);
+ }
+ }
+ },
+
+ findChildElements: function(element, expressions) {
+ var selector = expressions.toArray().join(', ');
+ return Prototype.Selector.select(selector, element || document);
+ }
+ });
+})();
diff --git a/web/public/javascripts/rails.js b/web/public/javascripts/rails.js
@@ -0,0 +1,175 @@
+(function() {
+ // Technique from Juriy Zaytsev
+ // http://thinkweb2.com/projects/prototype/detecting-event-support-without-b…
+ function isEventSupported(eventName) {
+ var el = document.createElement('div');
+ eventName = 'on' + eventName;
+ var isSupported = (eventName in el);
+ if (!isSupported) {
+ el.setAttribute(eventName, 'return;');
+ isSupported = typeof el[eventName] == 'function';
+ }
+ el = null;
+ return isSupported;
+ }
+
+ function isForm(element) {
+ return Object.isElement(element) && element.nodeName.toUpperCase() == 'FOR…
+ }
+
+ function isInput(element) {
+ if (Object.isElement(element)) {
+ var name = element.nodeName.toUpperCase()
+ return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
+ }
+ else return false
+ }
+
+ var submitBubbles = isEventSupported('submit'),
+ changeBubbles = isEventSupported('change')
+
+ if (!submitBubbles || !changeBubbles) {
+ // augment the Event.Handler class to observe custom events when needed
+ Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wr…
+ function(init, element, eventName, selector, callback) {
+ init(element, eventName, selector, callback)
+ // is the handler being attached to an element that doesn't support th…
+ if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.ele…
+ (!changeBubbles && this.eventName == 'change' && !isInput(this.el…
+ // "submit" => "emulated:submit"
+ this.eventName = 'emulated:' + this.eventName
+ }
+ }
+ )
+ }
+
+ if (!submitBubbles) {
+ // discover forms on the page by observing focus events which always bubble
+ document.on('focusin', 'form', function(focusEvent, form) {
+ // special handler for the real "submit" event (one-time operation)
+ if (!form.retrieve('emulated:submit')) {
+ form.on('submit', function(submitEvent) {
+ var emulated = form.fire('emulated:submit', submitEvent, true)
+ // if custom event received preventDefault, cancel the real one too
+ if (emulated.returnValue === false) submitEvent.preventDefault()
+ })
+ form.store('emulated:submit', true)
+ }
+ })
+ }
+
+ if (!changeBubbles) {
+ // discover form inputs on the page
+ document.on('focusin', 'input, select, texarea', function(focusEvent, inpu…
+ // special handler for real "change" events
+ if (!input.retrieve('emulated:change')) {
+ input.on('change', function(changeEvent) {
+ input.fire('emulated:change', changeEvent, true)
+ })
+ input.store('emulated:change', true)
+ }
+ })
+ }
+
+ function handleRemote(element) {
+ var method, url, params;
+
+ var event = element.fire("ajax:before");
+ if (event.stopped) return false;
+
+ if (element.tagName.toLowerCase() === 'form') {
+ method = element.readAttribute('method') || 'post';
+ url = element.readAttribute('action');
+ params = element.serialize();
+ } else {
+ method = element.readAttribute('data-method') || 'get';
+ url = element.readAttribute('href');
+ params = {};
+ }
+
+ new Ajax.Request(url, {
+ method: method,
+ parameters: params,
+ evalScripts: true,
+
+ onComplete: function(request) { element.fire("ajax:complete", request…
+ onSuccess: function(request) { element.fire("ajax:success", request…
+ onFailure: function(request) { element.fire("ajax:failure", request…
+ });
+
+ element.fire("ajax:after");
+ }
+
+ function handleMethod(element) {
+ var method = element.readAttribute('data-method'),
+ url = element.readAttribute('href'),
+ csrf_param = $$('meta[name=csrf-param]')[0],
+ csrf_token = $$('meta[name=csrf-token]')[0];
+
+ var form = new Element('form', { method: "POST", action: url, style: "disp…
+ element.parentNode.insert(form);
+
+ if (method !== 'post') {
+ var field = new Element('input', { type: 'hidden', name: '_method', valu…
+ form.insert(field);
+ }
+
+ if (csrf_param) {
+ var param = csrf_param.readAttribute('content'),
+ token = csrf_token.readAttribute('content'),
+ field = new Element('input', { type: 'hidden', name: param, value: t…
+ form.insert(field);
+ }
+
+ form.submit();
+ }
+
+
+ document.on("click", "*[data-confirm]", function(event, element) {
+ var message = element.readAttribute('data-confirm');
+ if (!confirm(message)) event.stop();
+ });
+
+ document.on("click", "a[data-remote]", function(event, element) {
+ if (event.stopped) return;
+ handleRemote(element);
+ event.stop();
+ });
+
+ document.on("click", "a[data-method]", function(event, element) {
+ if (event.stopped) return;
+ handleMethod(element);
+ event.stop();
+ });
+
+ document.on("submit", function(event) {
+ var element = event.findElement(),
+ message = element.readAttribute('data-confirm');
+ if (message && !confirm(message)) {
+ event.stop();
+ return false;
+ }
+
+ var inputs = element.select("input[type=submit][data-disable-with]");
+ inputs.each(function(input) {
+ input.disabled = true;
+ input.writeAttribute('data-original-value', input.value);
+ input.value = input.readAttribute('data-disable-with');
+ });
+
+ var element = event.findElement("form[data-remote]");
+ if (element) {
+ handleRemote(element);
+ event.stop();
+ }
+ });
+
+ document.on("ajax:after", "form", function(event, element) {
+ var inputs = element.select("input[type=submit][disabled=true][data-disabl…
+ inputs.each(function(input) {
+ input.value = input.readAttribute('data-original-value');
+ input.removeAttribute('data-original-value');
+ input.disabled = false;
+ });
+ });
+})();
diff --git a/web/public/robots.txt b/web/public/robots.txt
@@ -0,0 +1,5 @@
+# See http://www.robotstxt.org/wc/norobots.html for documentation on how to us…
+#
+# To ban all spiders from the entire site uncomment the next two lines:
+# User-Agent: *
+# Disallow: /
diff --git a/web/public/stylesheets/global.css b/web/public/stylesheets/global.…
@@ -0,0 +1,556 @@
+/* global element overrides */
+
+body {
+ height: 100%;
+ background: #fff;
+ margin: 0;
+ padding: 0;
+ font-family: Tahoma, sans-serif;
+ font-size: 12px;
+ color: #555;
+}
+
+img {
+ border: none;
+}
+
+table {
+ border: none;
+ padding: 0;
+ margin: 0;
+}
+
+a:link, a:visited {
+ color: #003366;
+ text-decoration: none;
+}
+
+a:hover, a:active {
+ color: #cc0033;
+ text-decoration: underline;
+}
+
+h1 {
+ color: #333;
+ margin-top: 0;
+ padding-top: 0;
+ font-weight: normal;
+ font-size: 36px;
+}
+
+h2 {
+ font-size: 1.4em;
+ margin-bottom: 4px;
+}
+
+h3 {
+ font-size: 1.05em;
+ font-weight: normal;
+ font-style: italic;
+}
+
+/* unique elements */
+
+#content {
+ padding: 0;
+ margin: 0 auto;
+ width: 800px;
+ line-height: 1.5;
+}
+
+#main {
+ border: 0;
+ padding: 0;
+ background-color: white;
+ padding-top: 6px;
+ padding-left: 18px;
+ padding-right: 20px;
+ margin-top: -3px;
+ margin-bottom: -3px;
+}
+
+#main img {
+ max-width: 530px;
+}
+
+#footer {
+
+ margin: 0;
+ text-align: center;
+}
+
+#quote {
+ font-size: 12px;
+ padding: 10px;
+ margin: 0 auto;
+ width: 70%;
+ background: #dddddd;
+ margin-bottom: 10px;
+}
+
+#copyright {
+ padding: 10px;
+ text-align: center;
+ font-size: 10px;
+ color: #cccccc;
+}
+
+#header {
+ width: 800px;
+ margin: 0 auto 10px;
+ color: white;
+
+}
+
+#logo {
+ float: left;
+ margin-top: 0px;
+}
+
+
+
+#warvox_stats {
+ margin-top: 5px;
+ border: 1px solid black;
+ font-size: 12px;
+ color: #555;
+}
+
+#warvox_stats td {
+ margin-left: 20px;
+ padding: 0 10px 0px 6px;
+}
+
+
+#warvox_conf {
+ margin-top: 5px;
+ border: 1px solid black;
+ font-size: 12px;
+ color: #555;
+}
+
+#warvox_conf td {
+ margin-left: 20px;
+ padding: 0 10px 0px 6px;
+}
+
+#warvox_blacklist {
+ margin-top: 5px;
+ border: 1px solid black;
+ font-size: 12px;
+ color: #555;
+}
+
+#warvox_blacklist td {
+ margin-left: 20px;
+ padding: 0 10px 0px 6px;
+}
+
+#home_logo {
+ border: 0;
+}
+
+#home_links {
+
+}
+
+#home_intro, #home_intro td {
+ border: 0;
+}
+
+#home_table {
+ border: 0;
+ margin-top: 0px;
+}
+
+#home_table td {
+ border: 0;
+}
+
+#home_text {
+ padding-left: 20px;
+}
+
+#navwrap {
+ margin-top: 10px;
+ padding-top: 10px;
+ text-align: center;
+}
+
+#nav {
+ margin: 0 0 0 0 auto;
+ padding: 10px;
+}
+
+#sections_container {
+ font-size: 14px;
+}
+
+#sections_ul {
+ margin: 0;
+ padding: 0;
+ white-space: nowrap;
+}
+
+#sections_ul li {
+ display: inline;
+ list-style-type: none;
+}
+
+#sections_container a {
+ padding: 5px 10px 5px 10px;
+ line-height: 28px;
+}
+
+#sections_container a:link, #sections_container a:visited {
+ color: #fff;
+ background: black repeat-x top;
+ text-decoration: none;
+ font-variant: small-caps;
+}
+
+#sections_container a:hover {
+ color: #fff;
+ background: red repeat-x top;
+ text-decoration: none;
+ font-variant: small-caps;
+}
+
+#sections_active {
+ font-weight: bold;
+}
+
+#subsections_active {
+ font-weight: bold;
+}
+
+#subsections_container {
+ font-size: 12px;
+ padding-top: 5px;
+}
+
+#subsections_ul {
+ margin: 0;
+ padding: 0;
+ white-space: nowrap;
+}
+
+#subsections_ul li {
+ display: inline;
+ list-style-type: none;
+}
+
+#subsections_container a {
+ padding: 5px 10px 5px 10px;
+ line-height: 20px;
+}
+
+#subsections_container a:link, #subsections_container a:visited {
+ color: #fff;
+ background: #333333 repeat-x top;
+ text-decoration: none;
+ font-variant: small-caps;
+}
+
+#subsections_container a:hover {
+ color: #fff;
+ background: #440000 repeat-x top;
+ text-decoration: none;
+ font-variant: small-caps;
+}
+
+
+#calls_pie1 {
+ text-align: center;
+}
+#calls_pie2 {
+ text-align: center;
+}
+#calls_pie3 {
+ text-align: center;
+}
+
+/* non-unique elements */
+
+
+.title {
+ font-size: 20px;
+ font-weight: bold;
+ color: black;
+ font-variant: small-caps;
+}
+
+.active_job_row {
+ background: yellow;
+}
+
+.table_scaffold {
+ margin-top: 5px;
+ border: 1px solid black;
+ font-size: 12px;
+ color: #555;
+}
+
+.table_scaffold th {
+ font-size: 14px;
+ text-decoration: underline;
+}
+
+.table_scaffold td {
+ margin-left: 20px;
+ padding: 0 10px 0px 6px;
+ border: 1px solid #cccccc;
+}
+
+.header_item {
+ font-weight: bold;
+}
+
+.date-header {
+ background: url('/images/bluefade.jpg') #222222 repeat-x top;
+ color: white;
+ padding: 2px;
+ margin: 2px;
+ font-weight: bold;
+ padding-left: 8px;
+ font-variant: small-caps;
+ border: 0;
+}
+
+.date-header-center {
+ background: url('/images/bluefade.jpg') #8c0000 repeat-x top;
+ color: white;
+ padding: 2px;
+ margin: 2px;
+ font-weight: bold;
+ padding-left: 8px;
+ font-variant: small-caps;
+ text-align: center;
+ border: 0;
+}
+
+
+.balloon {
+ background: url('/images/balloon.png') repeat-x top;
+ width: 277px;
+ height: 98px;
+ color: white;
+ margin: 40px auto;
+}
+
+.balloon_links {
+ float: left;
+ margin-top: 10px;
+ margin-left: 6px;
+
+}
+
+.balloon_links p {
+ font-size: 14px;
+ font-weight: bold;
+ margin: 0;
+}
+
+.balloon_links a, .balloon_links a:link, .balloon_links a:visited {
+ font-size: 12px;
+ color: white;
+ text-decoration: none;
+ margin-left: 12px;
+}
+
+.balloon_links a:hover, .balloon_links a:active {
+ color: yellow;
+ text-decoration: underline;
+}
+
+.balloon_icon {
+ float: left;
+ padding-top: 25px;
+ padding-left: 14px;
+}
+
+.box_full {
+ position: relative;
+ clear: both;
+ width: 790px;
+}
+
+#round_top {
+ padding: 0;
+ border: 0;
+ margin: 0;
+ height: 9px;
+ margin-bottom: -2px;
+ visibility: hidden;
+}
+
+#round_bot {
+ padding: 0;
+ border: 0;
+ margin-top: -2px;
+ height: 15px;
+ visibility: hidden;
+}
+
+.box_full p {
+ line-height: 1.5em;
+}
+
+
+.intro {
+ font-size: 14px;
+}
+
+.center {
+ text-align: center;
+}
+
+.code {
+ font-family: fixed, courier new;
+ color: black;
+ background: #dddddd;
+ padding: 0.25em 0.25em 0.25em 0.25em;
+}
+
+.fatp {
+ margin: 2.0em 0 0.5em;
+}
+
+.announce {
+ padding: 0px 20px 0px 20px;
+ line-height: 1.5;
+ font-size: 12px;
+ font-weight: bold;
+}
+
+.vulnTitle {
+ font-weight: bold;
+ font-size: 10pt;
+ padding-bottom: 2em;
+}
+
+.vulnHeader {
+ font-weight: bold;
+}
+
+.vulnText {
+ padding-bottom: 1em;
+}
+
+.vulnInfoTable {
+ background: #dddddd;
+
+}
+
+.vulnInfoHeader {
+ font-weight: bold;
+}
+
+.vulnInfoData {
+
+}
+
+.talk_title {
+ font-family: verdana, sans-serif, arial, helvetica;
+ font-size: 9pt;
+ padding: 0.1em 0.5em 0.15em .5em;
+ font-weight: bold;
+}
+
+.materials {
+ background: #dddddd;
+ padding: 10px 10px 10px 10px;
+ border: 1px solid black;
+}
+
+
+
+.level1
+{
+ list-style: none;
+ text-align: left;
+ padding: 0em 0em 1em 0em;
+ font-size: 16px;
+ font-weight: bold;
+}
+.level1 li
+{
+ padding: 1em .25em 0.25em 0.25em;
+}
+
+.level2
+{
+ list-style: circle;
+ text-align: left;
+ padding: 0em 0.25em 0.25em 4em;
+}
+.level2 li
+{
+ padding: 0.25em .25em 0.25em 0.25em;
+ font-size: 14px;
+ font-weight: bold;
+}
+
+.level3
+{
+ list-style: square;
+ text-align: left;
+ padding: 0em 0.25em 0.25em 4em;
+}
+.level3 li
+{
+ padding: 0.25em .25em 0.25em 0.25em;
+}
+
+
+
+.boxTable
+{
+ border-left-style: solid;
+ border-top-style: solid;
+ border-right-style: solid;
+ border-bottom-style: solid;
+ border-color: #dddddd;
+ font-family: verdana, sans-serif;
+ font-size: 8pt;
+ background-color: #dddddd;
+}
+.boxInnerTable
+{
+ font-family: verdana, sans-serif;
+ font-size: 8pt;
+}
+.boxTdHeader
+{
+ border-width: 0 0 0 0;
+ border-color: #dddddd;
+ border-left-style: solid;
+ border-top-style: solid;
+ border-right-style: solid;
+ border-bottom-style: solid;
+ background-color: #dddddd;
+ color: black;
+ height: 20px;
+ font-weight: bold
+}
+.boxTd
+{
+ border-width: 0 0 0 0;
+ border-left-style: solid;
+ border-top-style: solid;
+ border-right-style: solid;
+ border-bottom-style: solid;
+}
+.boxTd1
+{
+ background-color: #eeeeee;
+}
+.boxTd2
+{
+ background-color: #eeeeee;
+}
+.boxTh
+{
+ background-color: #dddddd;
+}
+
diff --git a/web/public/stylesheets/ie7.css b/web/public/stylesheets/ie7.css
@@ -0,0 +1,3 @@
+#logo {
+ margin-top: 7px;
+}
diff --git a/web/public/stylesheets/lightbox.css b/web/public/stylesheets/light…
@@ -0,0 +1,26 @@
+#lightbox{
+ background-color:#eee;
+ padding: 10px;
+ border-bottom: 2px solid #666;
+ border-right: 2px solid #666;
+ }
+#lightboxDetails{
+ font-size: 0.8em;
+ padding-top: 0.4em;
+ }
+#lightboxCaption{ float: left; }
+#keyboardMsg{ float: right; }
+#closeButton{ top: 5px; right: 5px; }
+
+#lightbox img{ border: none; clear: both;}
+#overlay img{ border: none; }
+
+#overlay{ background-image: url(overlay.png); }
+
+* html #overlay{
+ background-color: #333;
+ back\ground-color: transparent;
+ background-image: url(blank.gif);
+ filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="overla…
+ }
+
+\ No newline at end of file
diff --git a/web/public/stylesheets/overlay.png b/web/public/stylesheets/overla…
Binary files differ.
diff --git a/web/public/stylesheets/scaffold.css b/web/public/stylesheets/scaff…
@@ -0,0 +1,54 @@
+body { background-color: #fff; color: #333; }
+
+body, p, ol, ul, td {
+ font-family: verdana, arial, helvetica, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+}
+
+pre {
+ background-color: #eee;
+ padding: 10px;
+ font-size: 11px;
+}
+
+a { color: #000; }
+a:visited { color: #666; }
+a:hover { color: #fff; background-color:#000; }
+
+.fieldWithErrors {
+ padding: 2px;
+ background-color: red;
+ display: table;
+}
+
+#errorExplanation {
+ width: 400px;
+ border: 2px solid red;
+ padding: 7px;
+ padding-bottom: 12px;
+ margin-bottom: 20px;
+ background-color: #f0f0f0;
+}
+
+#errorExplanation h2 {
+ text-align: left;
+ font-weight: bold;
+ padding: 5px 5px 5px 15px;
+ font-size: 12px;
+ margin: -7px;
+ background-color: #c00;
+ color: #fff;
+}
+
+#errorExplanation p {
+ color: #333;
+ margin-bottom: 0;
+ padding: 5px;
+}
+
+#errorExplanation ul li {
+ font-size: 12px;
+ list-style: square;
+}
+
diff --git a/web/script/rails b/web/script/rails
@@ -0,0 +1,6 @@
+#!/usr/bin/env ruby
+# This command will automatically be run when you run "rails" with Rails 3 gem…
+
+APP_PATH = File.expand_path('../../config/application', __FILE__)
+require File.expand_path('../../config/boot', __FILE__)
+require 'rails/commands'
diff --git a/web/test/fixtures/dial_jobs.yml b/web/test/fixtures/dial_jobs.yml
@@ -0,0 +1,11 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+
+# This model initially had no columns defined. If you add columns to the
+# model remove the '{}' from the fixture names and add the columns immediately
+# below each fixture, per the syntax in the comments below
+#
+one: {}
+# column: value
+#
+two: {}
+# column: value
diff --git a/web/test/fixtures/dial_results.yml b/web/test/fixtures/dial_result…
@@ -0,0 +1,11 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+
+# This model initially had no columns defined. If you add columns to the
+# model remove the '{}' from the fixture names and add the columns immediately
+# below each fixture, per the syntax in the comments below
+#
+one: {}
+# column: value
+#
+two: {}
+# column: value
diff --git a/web/test/fixtures/providers.yml b/web/test/fixtures/providers.yml
@@ -0,0 +1,11 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+
+# This model initially had no columns defined. If you add columns to the
+# model remove the '{}' from the fixture names and add the columns immediately
+# below each fixture, per the syntax in the comments below
+#
+one: {}
+# column: value
+#
+two: {}
+# column: value
diff --git a/web/test/functional/analyze_controller_test.rb b/web/test/function…
@@ -0,0 +1,8 @@
+require 'test_helper'
+
+class AnalyzeControllerTest < ActionController::TestCase
+ # Replace this with your real tests.
+ test "the truth" do
+ assert true
+ end
+end
diff --git a/web/test/functional/dial_jobs_controller_test.rb b/web/test/functi…
@@ -0,0 +1,8 @@
+require 'test_helper'
+
+class DialJobsControllerTest < ActionController::TestCase
+ # Replace this with your real tests.
+ test "the truth" do
+ assert true
+ end
+end
diff --git a/web/test/functional/dial_results_controller_test.rb b/web/test/fun…
@@ -0,0 +1,8 @@
+require 'test_helper'
+
+class DialResultsControllerTest < ActionController::TestCase
+ # Replace this with your real tests.
+ test "the truth" do
+ assert true
+ end
+end
diff --git a/web/test/functional/home_controller_test.rb b/web/test/functional/…
@@ -0,0 +1,8 @@
+require 'test_helper'
+
+class HomeControllerTest < ActionController::TestCase
+ # Replace this with your real tests.
+ test "the truth" do
+ assert true
+ end
+end
diff --git a/web/test/functional/providers_controller_test.rb b/web/test/functi…
@@ -0,0 +1,8 @@
+require 'test_helper'
+
+class ProvidersControllerTest < ActionController::TestCase
+ # Replace this with your real tests.
+ test "the truth" do
+ assert true
+ end
+end
diff --git a/web/test/performance/browsing_test.rb b/web/test/performance/brows…
@@ -0,0 +1,9 @@
+require 'test_helper'
+require 'rails/performance_test_help'
+
+# Profiling results for each test method are written to tmp/performance.
+class BrowsingTest < ActionDispatch::PerformanceTest
+ def test_homepage
+ get '/'
+ end
+end
diff --git a/web/test/test_helper.rb b/web/test/test_helper.rb
@@ -0,0 +1,13 @@
+ENV["RAILS_ENV"] = "test"
+require File.expand_path('../../config/environment', __FILE__)
+require 'rails/test_help'
+
+class ActiveSupport::TestCase
+ # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabeti…
+ #
+ # Note: You'll currently still have to declare fixtures explicitly in integr…
+ # -- they do not yet inherit this setting
+ fixtures :all
+
+ # Add more helper methods to be used by all tests here...
+end
diff --git a/web/test/unit/dial_job_test.rb b/web/test/unit/dial_job_test.rb
@@ -0,0 +1,8 @@
+require 'test_helper'
+
+class DialJobTest < ActiveSupport::TestCase
+ # Replace this with your real tests.
+ test "the truth" do
+ assert true
+ end
+end
diff --git a/web/test/unit/dial_result_test.rb b/web/test/unit/dial_result_test…
@@ -0,0 +1,8 @@
+require 'test_helper'
+
+class DialResultTest < ActiveSupport::TestCase
+ # Replace this with your real tests.
+ test "the truth" do
+ assert true
+ end
+end
diff --git a/web/test/unit/helpers/analyze_helper_test.rb b/web/test/unit/helpe…
@@ -0,0 +1,4 @@
+require 'test_helper'
+
+class AnalyzeHelperTest < ActionView::TestCase
+end
diff --git a/web/test/unit/helpers/dial_jobs_helper_test.rb b/web/test/unit/hel…
@@ -0,0 +1,4 @@
+require 'test_helper'
+
+class DialJobsHelperTest < ActionView::TestCase
+end
diff --git a/web/test/unit/helpers/dial_results_helper_test.rb b/web/test/unit/…
@@ -0,0 +1,4 @@
+require 'test_helper'
+
+class DialResultsHelperTest < ActionView::TestCase
+end
diff --git a/web/test/unit/helpers/home_helper_test.rb b/web/test/unit/helpers/…
@@ -0,0 +1,4 @@
+require 'test_helper'
+
+class HomeHelperTest < ActionView::TestCase
+end
diff --git a/web/test/unit/helpers/providers_helper_test.rb b/web/test/unit/hel…
@@ -0,0 +1,4 @@
+require 'test_helper'
+
+class ProvidersHelperTest < ActionView::TestCase
+end
diff --git a/web/test/unit/provider_test.rb b/web/test/unit/provider_test.rb
@@ -0,0 +1,8 @@
+require 'test_helper'
+
+class ProviderTest < ActiveSupport::TestCase
+ # Replace this with your real tests.
+ test "the truth" do
+ assert true
+ end
+end
diff --git a/web/vendor/plugins/dynamic_form/MIT-LICENSE b/web/vendor/plugins/d…
@@ -0,0 +1,20 @@
+Copyright (c) 2010 David Heinemeier Hansson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/web/vendor/plugins/dynamic_form/README b/web/vendor/plugins/dynami…
@@ -0,0 +1,13 @@
+DynamicForm
+===========
+
+DynamicForm holds a few helpers method to help you deal with your models, they…
+
+* input(record, method, options = {})
+* form(record, options = {})
+* error_message_on(object, method, options={})
+* error_messages_for(record, options={})
+
+It also adds f.error_messages and f.error_messages_on to your form builders.
+
+Copyright (c) 2010 David Heinemeier Hansson, released under the MIT license
diff --git a/web/vendor/plugins/dynamic_form/Rakefile b/web/vendor/plugins/dyna…
@@ -0,0 +1,10 @@
+require 'rake/testtask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the active_model_helper plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'test'
+ t.pattern = 'test/**/*_test.rb'
+end
diff --git a/web/vendor/plugins/dynamic_form/dynamic_form.gemspec b/web/vendor/…
@@ -0,0 +1,12 @@
+Gem::Specification.new do |s|
+ s.name = 'dynamic_form'
+ s.version = '1.0.0'
+ s.author = 'David Heinemeier Hansson'
+ s.email = '[email protected]'
+ s.summary = 'Deprecated dynamic form helpers: input, form, error_messages_fo…
+
+ s.add_dependency('rails', '>= 3.0.0')
+
+ s.files = Dir['lib/**/*']
+ s.require_path = 'lib'
+end
diff --git a/web/vendor/plugins/dynamic_form/init.rb b/web/vendor/plugins/dynam…
@@ -0,0 +1 @@
+require 'dynamic_form'
diff --git a/web/vendor/plugins/dynamic_form/lib/action_view/helpers/dynamic_fo…
@@ -0,0 +1,300 @@
+require 'action_view/helpers'
+require 'active_support/i18n'
+require 'active_support/core_ext/enumerable'
+require 'active_support/core_ext/object/blank'
+
+module ActionView
+ module Helpers
+ # The Active Record Helper makes it easier to create forms for records kep…
+ # method that creates a complete form for all the basic content types of t…
+ # is a great way of making the record quickly available for editing, but l…
+ # In that case, it's better to use the +input+ method and the specialized …
+ module DynamicForm
+ # Returns a default input tag for the type of object returned by the met…
+ # has an attribute +title+ mapped to a +VARCHAR+ column that holds "Hell…
+ #
+ # input("post", "title")
+ # # => <input id="post_title" name="post[title]" size="30" type="text"…
+ def input(record_name, method, options = {})
+ InstanceTag.new(record_name, method, self).to_tag(options)
+ end
+
+ # Returns an entire form with all needed input tags for a specified Acti…
+ # has attributes named +title+ of type +VARCHAR+ and +body+ of type +TEX…
+ #
+ # form("post")
+ #
+ # would yield a form like the following (modulus formatting):
+ #
+ # <form action='/posts/create' method='post'>
+ # <p>
+ # <label for="post_title">Title</label><br />
+ # <input id="post_title" name="post[title]" size="30" type="text" …
+ # </p>
+ # <p>
+ # <label for="post_body">Body</label><br />
+ # <textarea cols="40" id="post_body" name="post[body]" rows="20"><…
+ # </p>
+ # <input name="commit" type="submit" value="Create" />
+ # </form>
+ #
+ # It's possible to specialize the form builder by using a different acti…
+ # block renderer. For example, if <tt>@entry</tt> has an attribute +mess…
+ #
+ # form("entry",
+ # :action => "sign",
+ # :input_block => Proc.new { |record, column|
+ # "#{column.human_name}: #{input(record, column.name)}<br />"
+ # })
+ #
+ # would yield a form like the following (modulus formatting):
+ #
+ # <form action="/entries/sign" method="post">
+ # Message:
+ # <input id="entry_message" name="entry[message]" size="30" type="te…
+ # <input name="commit" type="submit" value="Sign" />
+ # </form>
+ #
+ # It's also possible to add additional content to the form by giving it …
+ #
+ # form("entry", :action => "sign") do |form|
+ # form << content_tag("b", "Department")
+ # form << collection_select("department", "id", @departments, "id", …
+ # end
+ #
+ # The following options are available:
+ #
+ # * <tt>:action</tt> - The action used when submitting the form (default…
+ # * <tt>:input_block</tt> - Specialize the output using a different bloc…
+ # * <tt>:method</tt> - The method used when submitting the form (default…
+ # * <tt>:multipart</tt> - Whether to change the enctype of the form to "…
+ # * <tt>:submit_value</tt> - The text of the submit button (default: "Cr…
+ def form(record_name, options = {})
+ record = instance_variable_get("@#{record_name}")
+ record = convert_to_model(record)
+
+ options = options.symbolize_keys
+ options[:action] ||= record.persisted? ? "update" : "create"
+ action = url_for(:action => options[:action], :id => record)
+
+ submit_value = options[:submit_value] || options[:action].gsub(/[^\w]/…
+
+ contents = form_tag({:action => action}, :method =>(options[:method] |…
+ contents.safe_concat hidden_field(record_name, :id) if record.persiste…
+ contents.safe_concat all_input_tags(record, record_name, options)
+ yield contents if block_given?
+ contents.safe_concat submit_tag(submit_value)
+ contents.safe_concat('</form>')
+ end
+
+ # Returns a string containing the error message attached to the +method+…
+ # This error message is wrapped in a <tt>DIV</tt> tag by default or with…
+ # which can be extended to include a <tt>:prepend_text</tt> and/or <tt>:…
+ # the error), and a <tt>:css_class</tt> to style it accordingly. +object…
+ # instance variable or the actual object. The method can be passed in ei…
+ # As an example, let's say you have a model <tt>@post</tt> that has an e…
+ #
+ # <%= error_message_on "post", "title" %>
+ # # => <div class="formError">can't be empty</div>
+ #
+ # <%= error_message_on @post, :title %>
+ # # => <div class="formError">can't be empty</div>
+ #
+ # <%= error_message_on "post", "title",
+ # :prepend_text => "Title simply ",
+ # :append_text => " (or it won't work).",
+ # :html_tag => "span",
+ # :css_class => "inputError" %>
+ # # => <span class="inputError">Title simply can't be empty (or it won…
+ def error_message_on(object, method, *args)
+ options = args.extract_options!
+ unless args.empty?
+ ActiveSupport::Deprecation.warn('error_message_on takes an option ha…
+ 'prepend_text, append_text, html_tag, and css_class arguments', ca…
+
+ options[:prepend_text] = args[0] || ''
+ options[:append_text] = args[1] || ''
+ options[:html_tag] = args[2] || 'div'
+ options[:css_class] = args[3] || 'formError'
+ end
+ options.reverse_merge!(:prepend_text => '', :append_text => '', :html_…
+
+ object = convert_to_model(object)
+
+ if (obj = (object.respond_to?(:errors) ? object : instance_variable_ge…
+ (errors = obj.errors[method]).presence
+ content_tag(options[:html_tag],
+ (options[:prepend_text].html_safe << errors.first).safe_concat(opt…
+ :class => options[:css_class]
+ )
+ else
+ ''
+ end
+ end
+
+ # Returns a string with a <tt>DIV</tt> containing all of the error messa…
+ # given. If more than one object is specified, the errors for the objec…
+ # provided.
+ #
+ # This <tt>DIV</tt> can be tailored by the following options:
+ #
+ # * <tt>:header_tag</tt> - Used for the header of the error div (default…
+ # * <tt>:id</tt> - The id of the error div (default: "errorExplanation").
+ # * <tt>:class</tt> - The class of the error div (default: "errorExplana…
+ # * <tt>:object</tt> - The object (or array of objects) for which to dis…
+ # if you need to escape the instance variable convention.
+ # * <tt>:object_name</tt> - The object name to use in the header, or any…
+ # If <tt>:object_name</tt> is not set, the name of the first object wi…
+ # * <tt>:header_message</tt> - The message in the header of the error di…
+ # or an empty string to avoid the header message altogether. (Default:…
+ # prohibited this object from being saved").
+ # * <tt>:message</tt> - The explanation message after the header message…
+ # the error list. Pass +nil+ or an empty string to avoid the explanat…
+ # altogether. (Default: "There were problems with the following fields…
+ #
+ # To specify the display for one object, you simply provide its name as …
+ # For example, for the <tt>@user</tt> model:
+ #
+ # error_messages_for 'user'
+ #
+ # You can also supply an object:
+ #
+ # error_messages_for @user
+ #
+ # This will use the last part of the model name in the presentation. For…
+ # this is a MyKlass::User object, this will use "user" as the name in th…
+ # is taken from MyKlass::User.model_name.human, which can be overridden.
+ #
+ # To specify more than one object, you simply list them; optionally, you…
+ # will be the name used in the header message:
+ #
+ # error_messages_for 'user_common', 'user', :object_name => 'user'
+ #
+ # You can also use a number of objects, which will have the same naming …
+ # as a single object.
+ #
+ # error_messages_for @user, @post
+ #
+ # If the objects cannot be located as instance variables, you can add an…
+ # object (or array of objects to use):
+ #
+ # error_messages_for 'user', :object => @question.user
+ #
+ # NOTE: This is a pre-packaged presentation of the errors with embedded …
+ # you need is significantly different from the default presentation, it …
+ # instance yourself and set it up. View the source of this method to see…
+ def error_messages_for(*params)
+ options = params.extract_options!.symbolize_keys
+
+ objects = Array.wrap(options.delete(:object) || params).map do |object|
+ object = instance_variable_get("@#{object}") unless object.respond_t…
+ object = convert_to_model(object)
+
+ if object.class.respond_to?(:model_name)
+ options[:object_name] ||= object.class.model_name.human.downcase
+ end
+
+ object
+ end
+
+ objects.compact!
+ count = objects.inject(0) {|sum, object| sum + object.errors.count }
+
+ unless count.zero?
+ html = {}
+ [:id, :class].each do |key|
+ if options.include?(key)
+ value = options[key]
+ html[key] = value unless value.blank?
+ else
+ html[key] = 'errorExplanation'
+ end
+ end
+ options[:object_name] ||= params.first
+
+ I18n.with_options :locale => options[:locale], :scope => [:errors, :…
+ header_message = if options.include?(:header_message)
+ options[:header_message]
+ else
+ locale.t :header, :count => count, :model => options[:object_nam…
+ end
+
+ message = options.include?(:message) ? options[:message] : locale.…
+
+ error_messages = objects.sum do |object|
+ object.errors.full_messages.map do |msg|
+ content_tag(:li, msg)
+ end
+ end.join.html_safe
+
+ contents = ''
+ contents << content_tag(options[:header_tag] || :h2, header_messag…
+ contents << content_tag(:p, message) unless message.blank?
+ contents << content_tag(:ul, error_messages)
+
+ content_tag(:div, contents.html_safe, html)
+ end
+ else
+ ''
+ end
+ end
+
+ private
+
+ def all_input_tags(record, record_name, options)
+ input_block = options[:input_block] || default_input_block
+ record.class.content_columns.collect{ |column| input_block.call(record…
+ end
+
+ def default_input_block
+ Proc.new { |record, column| %(<p><label for="#{record}_#{column.name}"…
+ end
+
+ module InstanceTagMethods
+ def to_tag(options = {})
+ case column_type
+ when :string
+ field_type = @method_name.include?("password") ? "password" : "t…
+ to_input_field_tag(field_type, options)
+ when :text
+ to_text_area_tag(options)
+ when :integer, :float, :decimal
+ to_input_field_tag("text", options)
+ when :date
+ to_date_select_tag(options)
+ when :datetime, :timestamp
+ to_datetime_select_tag(options)
+ when :time
+ to_time_select_tag(options)
+ when :boolean
+ to_boolean_select_tag(options)
+ end
+ end
+
+ def column_type
+ object.send(:column_for_attribute, @method_name).type
+ end
+ end
+
+ module FormBuilderMethods
+ def error_message_on(method, *args)
+ @template.error_message_on(@object || @object_name, method, *args)
+ end
+
+ def error_messages(options = {})
+ @template.error_messages_for(@object_name, objectify_options(options…
+ end
+ end
+ end
+
+ class InstanceTag
+ include DynamicForm::InstanceTagMethods
+ end
+
+ class FormBuilder
+ include DynamicForm::FormBuilderMethods
+ end
+ end
+end
+
+I18n.load_path << File.expand_path("../../locale/en.yml", __FILE__)
diff --git a/web/vendor/plugins/dynamic_form/lib/action_view/locale/en.yml b/we…
@@ -0,0 +1,8 @@
+en:
+ errors:
+ template:
+ header:
+ one: "1 error prohibited this %{model} from being saved"
+ other: "%{count} errors prohibited this %{model} from being saved"
+ # The variable :count is also available
+ body: "There were problems with the following fields:"
diff --git a/web/vendor/plugins/dynamic_form/lib/dynamic_form.rb b/web/vendor/p…
@@ -0,0 +1,5 @@
+require 'action_view/helpers/dynamic_form'
+
+class ActionView::Base
+ include DynamicForm
+end
diff --git a/web/vendor/plugins/dynamic_form/test/dynamic_form_i18n_test.rb b/w…
@@ -0,0 +1,42 @@
+require 'test_helper'
+
+class DynamicFormI18nTest < Test::Unit::TestCase
+ include ActionView::Context
+ include ActionView::Helpers::DynamicForm
+
+ attr_reader :request
+
+ def setup
+ @object = stub :errors => stub(:count => 1, :full_messages => ['full_messa…
+ @object.stubs :to_model => @object
+ @object.stubs :class => stub(:model_name => stub(:human => ""))
+
+ @object_name = 'book_seller'
+ @object_name_without_underscore = 'book seller'
+
+ stubs(:content_tag).returns 'content_tag'
+
+ I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:errors, :templ…
+ I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:errors, :templat…
+ end
+
+ def test_error_messages_for_given_a_header_option_it_does_not_translate_head…
+ I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:errors, :tem…
+ error_messages_for(:object => @object, :header_message => 'header message'…
+ end
+
+ def test_error_messages_for_given_no_header_option_it_translates_header_mess…
+ I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:errors, :tem…
+ error_messages_for(:object => @object, :locale => 'en')
+ end
+
+ def test_error_messages_for_given_a_message_option_it_does_not_translate_mes…
+ I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:errors, :templ…
+ error_messages_for(:object => @object, :message => 'message', :locale => '…
+ end
+
+ def test_error_messages_for_given_no_message_option_it_translates_message
+ I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:errors, :templ…
+ error_messages_for(:object => @object, :locale => 'en')
+ end
+end
+\ No newline at end of file
diff --git a/web/vendor/plugins/dynamic_form/test/dynamic_form_test.rb b/web/ve…
@@ -0,0 +1,370 @@
+require 'test_helper'
+require 'action_view/template/handlers/erb'
+
+class DynamicFormTest < ActionView::TestCase
+ tests ActionView::Helpers::DynamicForm
+
+ def form_for(*)
+ @output_buffer = super
+ end
+
+ silence_warnings do
+ class Post < Struct.new(:title, :author_name, :body, :secret, :written_on)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+ end
+
+ class User < Struct.new(:email)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+ end
+
+ class Column < Struct.new(:type, :name, :human_name)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+ end
+ end
+
+ class DirtyPost
+ class Errors
+ def empty?
+ false
+ end
+
+ def count
+ 1
+ end
+
+ def full_messages
+ ["Author name can't be <em>empty</em>"]
+ end
+
+ def [](field)
+ ["can't be <em>empty</em>"]
+ end
+ end
+
+ def errors
+ Errors.new
+ end
+ end
+
+ def setup_post
+ @post = Post.new
+ def @post.errors
+ Class.new {
+ def [](field)
+ case field.to_s
+ when "author_name"
+ ["can't be empty"]
+ when "body"
+ ['foo']
+ else
+ []
+ end
+ end
+ def empty?() false end
+ def count() 1 end
+ def full_messages() [ "Author name can't be empty" ] end
+ }.new
+ end
+
+ def @post.persisted?() false end
+ def @post.to_param() nil end
+
+ def @post.column_for_attribute(attr_name)
+ Post.content_columns.select { |column| column.name == attr_name }.first
+ end
+
+ silence_warnings do
+ def Post.content_columns() [ Column.new(:string, "title", "Title"), Colu…
+ end
+
+ @post.title = "Hello World"
+ @post.author_name = ""
+ @post.body = "Back to the hill and over it again!"
+ @post.secret = 1
+ @post.written_on = Date.new(2004, 6, 15)
+ end
+
+ def setup_user
+ @user = User.new
+ def @user.errors
+ Class.new {
+ def [](field) field == "email" ? ['nonempty'] : [] end
+ def empty?() false end
+ def count() 1 end
+ def full_messages() [ "User email can't be empty" ] end
+ }.new
+ end
+
+ def @user.new_record?() true end
+ def @user.to_param() nil end
+
+ def @user.column_for_attribute(attr_name)
+ User.content_columns.select { |column| column.name == attr_name }.first
+ end
+
+ silence_warnings do
+ def User.content_columns() [ Column.new(:string, "email", "Email") ] end
+ end
+
+ @user.email = ""
+ end
+
+ def protect_against_forgery?
+ @protect_against_forgery ? true : false
+ end
+ attr_accessor :request_forgery_protection_token, :form_authenticity_token
+
+ def setup
+ super
+ setup_post
+ setup_user
+
+ @response = ActionController::TestResponse.new
+ end
+
+ def url_for(options)
+ options = options.symbolize_keys
+ [options[:action], options[:id].to_param].compact.join('/')
+ end
+
+ def test_generic_input_tag
+ assert_dom_equal(
+ %(<input id="post_title" name="post[title]" size="30" type="text" value=…
+ )
+ end
+
+ def test_text_area_with_errors
+ assert_dom_equal(
+ %(<div class="fieldWithErrors"><textarea cols="40" id="post_body" name="…
+ text_area("post", "body")
+ )
+ end
+
+ def test_text_field_with_errors
+ assert_dom_equal(
+ %(<div class="fieldWithErrors"><input id="post_author_name" name="post[a…
+ text_field("post", "author_name")
+ )
+ end
+
+ def test_field_error_proc
+ old_proc = ActionView::Base.field_error_proc
+ ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
+ %(<div class=\"fieldWithErrors\">#{html_tag} <span class="error">#{[inst…
+ end
+
+ assert_dom_equal(
+ %(<div class="fieldWithErrors"><input id="post_author_name" name="post[a…
+ text_field("post", "author_name")
+ )
+ ensure
+ ActionView::Base.field_error_proc = old_proc if old_proc
+ end
+
+ def test_form_with_string
+ assert_dom_equal(
+ %(<form action="create" method="post"><p><label for="post_title">Title</…
+ form("post")
+ )
+
+ silence_warnings do
+ class << @post
+ def persisted?() true end
+ def to_param() id end
+ def id() 1 end
+ end
+ end
+
+ assert_dom_equal(
+ %(<form action="update/1" method="post"><input id="post_id" name="post[i…
+ form("post")
+ )
+ end
+
+ def test_form_with_protect_against_forgery
+ @protect_against_forgery = true
+ @request_forgery_protection_token = 'authenticity_token'
+ @form_authenticity_token = '123'
+ assert_dom_equal(
+ %(<form action="create" method="post"><div style='margin:0;padding:0;dis…
+ form("post")
+ )
+ end
+
+ def test_form_with_method_option
+ assert_dom_equal(
+ %(<form action="create" method="get"><p><label for="post_title">Title</l…
+ form("post", :method=>'get')
+ )
+ end
+
+ def test_form_with_action_option
+ output_buffer << form("post", :action => "sign")
+ assert_select "form[action=sign]" do |form|
+ assert_select "input[type=submit][value=Sign]"
+ end
+ end
+
+ def test_form_with_date
+ silence_warnings do
+ def Post.content_columns() [ Column.new(:date, "written_on", "Written on…
+ end
+
+ assert_dom_equal(
+ %(<form action="create" method="post"><p><label for="post_written_on">Wr…
+ form("post")
+ )
+ end
+
+ def test_form_with_datetime
+ silence_warnings do
+ def Post.content_columns() [ Column.new(:datetime, "written_on", "Writte…
+ end
+ @post.written_on = Time.gm(2004, 6, 15, 16, 30)
+
+ assert_dom_equal(
+ %(<form action="create" method="post"><p><label for="post_written_on">Wr…
+ form("post")
+ )
+ end
+
+ def test_error_for_block
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2…
+ assert_equal %(<div class="errorDeathByClass" id="errorDeathById"><h1>1 er…
+ assert_equal %(<div id="errorDeathById"><h1>1 error prohibited this post f…
+ assert_equal %(<div class="errorDeathByClass"><h1>1 error prohibited this …
+ end
+
+ def test_error_messages_for_escapes_html
+ @dirty_post = DirtyPost.new
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2…
+ end
+
+ def test_error_messages_for_handles_nil
+ assert_equal "", error_messages_for("notthere")
+ end
+
+ def test_error_message_on_escapes_html
+ @dirty_post = DirtyPost.new
+ assert_dom_equal "<div class=\"formError\">can't be &lt;em&gt;empty&lt;/em…
+ end
+
+ def test_error_message_on_handles_nil
+ assert_equal "", error_message_on("notthere", "notthere")
+ end
+
+ def test_error_message_on
+ assert_dom_equal "<div class=\"formError\">can't be empty</div>", error_me…
+ end
+
+ def test_error_message_on_no_instance_variable
+ other_post = @post
+ assert_dom_equal "<div class=\"formError\">can't be empty</div>", error_me…
+ end
+
+ def test_error_message_on_with_options_hash
+ assert_dom_equal "<div class=\"differentError\">beforecan't be emptyafter<…
+ end
+
+ def test_error_message_on_with_tag_option_in_options_hash
+ assert_dom_equal "<span class=\"differentError\">beforecan't be emptyafter…
+ end
+
+ def test_error_message_on_handles_empty_errors
+ assert_equal "", error_message_on(@post, :tag)
+ end
+
+ def test_error_messages_for_many_objects
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2…
+
+ # reverse the order, error order changes and so does the title
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2…
+
+ # add the default to put post back in the title
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2…
+
+ # symbols work as well
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2…
+
+ # any default works too
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2…
+
+ # should space object name
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2…
+
+ # hide header and explanation messages with nil or empty string
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><ul…
+
+ # override header and explanation messages
+ header_message = "Yikes! Some errors"
+ message = "Please fix the following fields and resubmit:"
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2…
+ end
+
+ def test_error_messages_for_non_instance_variable
+ actual_user = @user
+ actual_post = @post
+ @user = nil
+ @post = nil
+
+ #explicitly set object
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2…
+
+ #multiple objects
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2…
+
+ #nil object
+ assert_equal '', error_messages_for('user', :object => nil)
+ end
+
+ def test_error_messages_for_model_objects
+ error = error_messages_for(@post)
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2…
+ error
+
+ error = error_messages_for(@user, @post)
+ assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2…
+ error
+ end
+
+ def test_form_with_string_multipart
+ assert_dom_equal(
+ %(<form action="create" enctype="multipart/form-data" method="post"><p><…
+ form("post", :multipart => true)
+ )
+ end
+
+ def test_default_form_builder_with_dynamic_form_helpers
+ form_for(@post, :as => :post, :url => {}) do |f|
+ concat f.error_message_on('author_name')
+ concat f.error_messages
+ end
+
+ expected = %(<form class="post_new" method="post" action="" id="post_new">…
+ %(<div class="formError">can't be empty</div>) +
+ %(<div class="errorExplanation" id="errorExplanation"><h2>1 err…
+ %(</form>)
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_default_form_builder_no_instance_variable
+ post = @post
+ @post = nil
+
+ form_for(post, :as => :post, :url => {}) do |f|
+ concat f.error_message_on('author_name')
+ concat f.error_messages
+ end
+
+ expected = %(<form class="post_new" method="post" action="" id="post_new">…
+ %(<div class="formError">can't be empty</div>) +
+ %(<div class="errorExplanation" id="errorExplanation"><h2>1 err…
+ %(</form>)
+
+ assert_dom_equal expected, output_buffer
+ end
+end
+\ No newline at end of file
diff --git a/web/vendor/plugins/dynamic_form/test/test_helper.rb b/web/vendor/p…
@@ -0,0 +1,9 @@
+require 'rubygems'
+require 'test/unit'
+require 'active_support'
+require 'active_support/core_ext'
+require 'action_view'
+require 'action_controller'
+require 'action_controller/test_case'
+require 'active_model'
+require 'action_view/helpers/dynamic_form'
diff --git a/web/vendor/plugins/ezgraphix/FusionChartsFreeLICENSE.textile b/web…
@@ -0,0 +1,48 @@
+h2. FusionCharts Free License Agreement
+
+InfoSoft Global grants you a nonexclusive right to use FusionCharts Free, subj…
+
+FusionCharts Free can be used for free if you are a individual/research/commer…
+FusionCharts Free can be distributed for free with your free or commercial sof…
+You must not sell FusionCharts Free as a component in itself. However, your co…
+You must not represent in any way that you're the author of FusionCharts Free.
+
+h3. RE-DISTRIBUTION AS A PART OF A PRODUCT/SOFTWARE/APPLICATION
+
+FusionCharts Free is completely free to OEM and distributed with your open or …
+Send us an email at [email protected] with the following information:
+Your company name & address
+Name and description of your product with which you'll be re-distributing "Fus…
+Mention "FusionCharts Free" with relevant link back to www.fusioncharts.com/fr…
+We would also appreciate a link back from your product website to our website …
+
+h3. AS-IS DISTRIBUTION
+
+InfoSoft Global allows and encourages 3rd parties to distribute and make as ma…
+Exact copies of the complete software package, and that it is unmodified and i…
+The Software may not be made available on any site, CD-ROM, or with any packag…
+Nothing may be charged for the software other than a nominal fee for the distr…
+You must disclose that this product is copyrighted by InfoSoft Global
+You must not represent in any way that you are selling the software itself
+
+InfoSoft Global would be thankful to be notified of your distribution efforts:…
+
+h3. RESTRICTIONS
+
+You agree not to modify, adapt, translate, reverse engineer, decompile, disass…
+
+h3. OWNERSHIP & COPYRIGHT
+
+The Software is owned by InfoSoft Global and is protected by international cop…
+
+h3. WARRANTY
+
+InfoSoft Global expressly disclaims any warranty for the software. THE SOFTWAR…
+
+h3. INDEMNIFICATION
+
+You hereby imdemnify and hold harmless InfoSoft Global and its affiliates agai…
+
+h3. TERM AND TERMINATION.
+
+This Agreement shall commence on the Effective Date of installation of softwar…
+\ No newline at end of file
diff --git a/web/vendor/plugins/ezgraphix/README.textile b/web/vendor/plugins/e…
@@ -0,0 +1,157 @@
+h2. EZGraphix
+
+h3. Documentation
+
+"API Documentation (generated with RDoc)":http://ezgraphix.rubyforge.org
+
+h3. Demo
+
+"Online Demo (hosted by Heroku)":http://ezgraphixdemo.heroku.com/
+
+h3. Installation Notes
+
+<b>Install the plugin:</b>
+
+<pre>script/plugin install git://github.com/jpemberthy/ezgraphix.git</pre>
+
+<b>Run the tasks:</b> (it will move the necessary files to use the plugin)
+
+<pre>rake ezgraphix:setup # run from your project's root </pre>
+
+After the installation <b>include the FusionCharts.js</b> in each layout that …
+
+<pre><%= javascript_include_tag "FusionCharts" %></pre>
+
+and that's it!.
+
+h3. Usage
+
+<b> In your Controller: </b>
+
+For single series charts:
+
+<pre>
+def index
+ @g = Ezgraphix::Graphic.new
+ @g.data = {:ruby => 1, :perl => 2, :smalltalk => 3}
+end
+</pre>
+
+or you can specify the data directly in the constructor:
+
+<pre>
+def index
+ @g = Ezgraphix::Graphic.new(:data => {:ruby => 1, :perl => 2, :smalltalk => …
+end
+</pre>
+
+
+For multiseries charts:
+
+In this case you have multiple values for each serie. Also you need to specify…
+
+<pre>
+def index
+ @g = Ezgraphix::Graphic.new(:c_type => 'mscol3d')
+ @g.data = { "Registrations" => [4,2,12,7,0,3,6], "Payments" => [7,3,5,2,1,0,…
+ @g.labels = ["March", "April", "May", "June", "July", "August", "September"]
+end
+</pre>
+
+You can also pass the data collection to the constructor:
+
+<pre>
+ @g = Ezgraphix::Graphic.new(:data => {:ruby => 1, :perl => 2, :smalltalk => …
+</pre>
+
+There are 2 ways to <b> pass render options: </b>
+
+<b>1.</b> When the Graphic object is created, you can define it also as:
+
+<pre>@g = Ezgraphix::Graphic.new(:w => 200, :h => 300, :c_type => 'bar3d', :di…
+
+The defaults are: <pre>{:c_type => 'col3d', :w => 300, :h => 300, :div_name =>…
+At the moment, you can render the following graphics:
+
+<b> Single Series </b>
+
+<pre>
+Column 2D => :c_type => 'col2d'
+Column 3D => :c_type => 'col3d'
+Bar 2D => :c_type => 'bar2d'
+Pie 2D => :c_type => 'pie2d'
+Pie 3D => :c_type => 'pie3d'
+Line => :c_type => 'line'
+Doughnut 2D => :c_type => 'doug2d'
+</pre>
+
+<b> Multi Series </b>
+
+<pre>
+MultiSeriesLine => :ctype => 'msline'
+MultiSeriesColumn3D => :ctype => 'mscol3d'
+MultiSeriesColumn2D => :ctype => 'mscol2d'
+MultiSeriesArea2D => :ctype => 'msarea2d'
+MultiSeriesBar2D => :ctype => 'msbar2d'
+</pre>
+
+<b>2.</b> Anytime you want, by accessing the render_options attribute.
+
+<pre>@g.render_options(:caption => 'cool languages', :w => 400) #Merges new o…
+
+<b> In your view. </b>
+
+Add the following line wherever you want to render the graphic.
+
+<pre><%= render_ezgraphix @g %></pre>
+
+or just
+
+<pre><%= @g %></pre>
+
+<b> Tests </b>
+
+<pre> rake spec #run from the plugin's </pre>
+
+<b> Note.</b>
+
+Full set of render_options are specified in the rdoc, and <b>more chart's supp…
+
+<b> Pending </b>
+
+Full and detailed specs.
+
+h3. EZGraphix
+
+A Ruby gem to generate flash based graphics for rails applications using a fre…
+
+Copyright (c) 2008 Juan Esteban Pemberthy, released under the MIT License.
+
+EzGraphix uses FusionCharts Free, It's license is specified in the FusionChart…
+
+Fork and feel free to contribute!
+
+h3. LICENSE:
+
+(The MIT License)
+
+Copyright (c) 2008 Juan Esteban Pemberthy
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/web/vendor/plugins/ezgraphix/init.rb b/web/vendor/plugins/ezgraphi…
@@ -0,0 +1,2 @@
+require 'ezgraphix'
+ActionView::Base.send :include, EzgraphixHelper
+\ No newline at end of file
diff --git a/web/vendor/plugins/ezgraphix/lib/ezgraphix.rb b/web/vendor/plugins…
@@ -0,0 +1,199 @@
+# == ezgraphix.rb
+# This file contains the Ezgraphix module, and the Ezgraphix::Graphic class.
+#
+# == Summary
+# A rails plugin to generate flash based graphics
+# for rails applications using a free and customizable chart's set.
+#
+# == Installation
+# Instructions are listed in the respository's README[http://github.com/jpembe…
+#
+# == Online demo
+# Online demo[http://ezgraphixdemo.heroku.com/] Hosted by Heroku!
+#
+# == Contact
+#
+# Author:: Juan E Pemberthy
+# Mail:: [email protected]
+# Copyright:: Copyright (c) 2008
+# License:: Distributes under MIT License.
+
+unless defined? Ezgraphix
+ module Ezgraphix
+ require File.dirname(__FILE__) + '/ezgraphix/ezgraphix_helper'
+ require 'builder'
+
+ # This class contains the neccesary methods and attributes to render a Grap…
+ # most of time you will be playing with the render_options and _data_ attri…
+ # define the graphic's properties, also you can re-define those properties …
+ # at any time.
+ #
+ # == Example
+ # Define the Graphic in your controller.
+ # @g = Ezgraphix::Graphic.new # render_options can also be passed from h…
+ # # @g = Ezgraphix::Graphic.new(:div_name =>…
+ #
+ # @g.defaults
+ # => {:c_type=>'col3d', :div_name=>'ez_graphic', :w=>300, :h=>300}
+ #
+ # @g.render_options #equals to defaults if not options were passed to the…
+ # => {:c_type=>'col3d', :div_name=>'ez_graphic', :w=>300, :h=>300}
+ #
+ # It's always a good idea to change the div_name if your planning to render…
+ # than one Graphic in the same page, this makes the graphic unique.
+ # @g.render_options(:div_name => 'my_graph')
+ # => {:c_type=>'col3d', :div_name=>'my_graph', :w=>300, :h=>300}
+ #
+ # In order to render, you have to feed the graphic with data you want to sh…
+ # a Hash to represent that data where the keys represents names, for exampl…
+ # @g.data = {:ruby => 1, :perl => 2, :smalltalk => 3}
+ # => {:smalltalk => 3, :ruby => 1, :perl => 2}
+ #
+ # With this information, the graphic will be a column 3D, with a size of 30…
+ # "my_graph" name, with 3 columns containing the names: 'ruby', 'perl', and…
+ #
+ # To render the graphic, from a view call the render_ezgraphix method defin…
+ # <%= render_ezgraphix @g %>
+ #
+ class Graphic
+ include EzgraphixHelper
+
+ #Hash containing the names and values to render.
+ attr_accessor :data
+
+ # Array containing the categories to render multi series charts
+ attr_accessor :labels
+
+ # Hash containing all the render options. basic options are:
+ # * <tt> :c_type</tt> -- Chart type to render.
+ # * <tt> :div_name</tt> -- Name for the graphic, should be unique.
+ # * <tt> :w </tt> -- Width in pixels.
+ # * <tt> :h </tt> -- Height in pixels.
+ # Full list of options are listed below render_options
+ attr_accessor :render_options
+
+ COLORS = ['AFD8f6', '8E468E', '588526', 'B3A000', 'B2FF66',
+ 'F984A1', 'A66EDD', 'B20000', '3300CC', '000033',
+ '66FF33', '000000', 'FFFF00', '669966', 'FF3300',
+ 'F19CBB', '9966CC', '00FFFF', '4B5320', '007FFF',
+ '0000FF', '66FF00', 'CD7F32', '964B00', 'CC5500']
+
+ #Creates a new Graphic with the given _options_, if no _options_ are spe…
+ #the new Graphic will be initalized with the Graphic#defaults options.
+ def initialize(options={})
+ @render_options = defaults.merge!(options)
+ @data = options[:data] || Hash.new
+ end
+
+ #Returns defaults render options.
+ def defaults
+ {:c_type => 'col3d', :w => 300, :h => 300, :div_name => 'ez_graphic'}
+ end
+
+ # Receives a Hash containing a set of render options that will be merged…
+ #
+ # ==== Options
+ # Basics:
+ # * <tt>:c_type</tt></tt> -- Chart type to render, default: "col3d" for …
+ # :c_type => "col3d"
+ # :c_type => "bar3d" #Bar3D
+ # :c_type => "bar2d" #Bar2D
+ # :c_type => "pie2d" #Pie2D
+ # :c_type => "pie3D" #Pie3D
+ # :c_type => "line" #Line
+ # :c_type => "doug2d" #Doughnut2D
+ # * <tt>:div_name</tt></tt> -- Name for the graphic, would be unique, de…
+ # * <tt>:w</tt></tt> -- Width in pixels, default: 300
+ # * <tt>:h</tt></tt> -- Height in pixels, default: 300
+ # * <tt> :caption</tt> -- Graphic's caption, default: ""
+ # * <tt> :subcaption</tt> -- Graphic's subcaption, default: ""
+ # * <tt> :y_name</tt> -- Y axis name, default: ""
+ # * <tt> :x_name</tt> -- X axis name, default: ""
+ # Numbers:
+ # * <tt> :prefix</tt> -- Prefix to values defined in the _data_ attribut…
+ # :prefix => "$" or :prefix => "€"
+ # * <tt> :precision</tt> -- Number of decimal places to which all number…
+ # * <tt> :f_number</tt> -- Format number. if set to 0, numbers will not …
+ # * <tt> :d_separator</tt> -- Decimal Separator, default: "."
+ # * <tt> :t_separator</tt> -- Thousand Separator, default: ","
+ # Design:
+ # * <tt> :background</tt> -- Background Color
+ # * <tt> :names</tt> -- Hide/Show(0/1) labels names, default: 1
+ # * <tt> :values</tt> -- Hide/Show(0/1) Values, default: 1
+ # * <tt> :limits</tt> -- Hide/Show(0/1) Limits.
+ #
+ def render_options(options={})
+ @render_options.merge!(options)
+ end
+
+ #Returns the Graphic's type.
+ def c_type
+ self.render_options[:c_type]
+ end
+
+ #Returns the Graphic's width.
+ def w
+ self.render_options[:w]
+ end
+
+ #Returns the Graphic's height.
+ def h
+ self.render_options[:h]
+ end
+
+ #Returns the div's tag name would be unique if you want to render multip…
+ def div_name
+ self.render_options[:div_name]
+ end
+
+
+ #Returns a random color from the Graphic#COLORS collection.
+ def rand_color
+ @available_colors = COLORS.clone if @available_colors.to_a.empty?
+ @available_colors.delete_at rand(@available_colors.size)
+ end
+
+ #Builds the xml to feed the chart.
+ def to_xml
+ options = parse_options(self.render_options)
+ g_xml = Builder::XmlMarkup.new
+ #For single series charts
+ if ["area2d"].include? self.c_type
+ # These graphics should be one color only
+ escaped_xml = g_xml.graph(options) do
+ self.data.each{ |k,v|
+ g_xml.set :value => v, :name => k
+ }
+ end
+ elsif !['msline', 'mscol2d', 'msbar2d', 'mscol3d'].include?(self.c_typ…
+ escaped_xml = g_xml.graph(options) do
+ self.data.each{ |k,v|
+ g_xml.set :value => v, :name => k, :color => self.rand_color
+ }
+ end
+ else
+ #For multiseries charts
+ escaped_xml = g_xml.graph(options) do
+ g_xml.categories do
+ for label in self.labels
+ g_xml.category :name => label
+ end
+ end
+ for d in self.data
+ g_xml.dataset(:color => self.rand_color, :seriesName => d.first …
+ d[1].each do |v|
+ g_xml.set :value => v
+ end
+ end
+ end
+ end
+ end
+ escaped_xml.gsub("\"", "'")
+ end
+
+ def to_s
+ render_ezgraphix self
+ end
+ end
+ end
+end
diff --git a/web/vendor/plugins/ezgraphix/lib/ezgraphix/ezgraphix_helper.rb b/w…
@@ -0,0 +1,107 @@
+module EzgraphixHelper
+ #method used in ActionView::Base to render graphics.
+ def render_ezgraphix(g)
+ result = ""
+ html = Builder::XmlMarkup.new(:target => result)
+ html.div("test", :id => g.div_name)
+ html = Builder::XmlMarkup.new(:target => result)
+ html.script(:type => 'text/javascript') do
+ html << "var ezChart = new FusionCharts('#{f_type(g.c_type)}','#{g.div_n…
+ html << "ezChart.setDataXML(\"#{g.to_xml}\");\n" unless g.data.is_a?(Str…
+ html << "ezChart.setDataURL(\"#{g.data}\");\n" if g.data.is_a?(String)
+ html << "ezChart.render(\"#{g.div_name}\");\n"
+ end
+ result
+ end
+
+ def f_type(c_type)
+ case c_type
+ when 'area2d'
+ '/FusionCharts/FCF_Area2D.swf'
+ when 'col3d'
+ '/FusionCharts/FCF_Column3D.swf'
+ when 'bar2d'
+ '/FusionCharts/FCF_Bar2D.swf'
+ when 'barline3d'
+ '/FusionCharts/FCF_MSColumn3DLineDY.swf'
+ when 'col2d'
+ '/FusionCharts/FCF_Column2D.swf'
+ when 'pie2d'
+ '/FusionCharts/FCF_Pie2D.swf'
+ when 'pie3d'
+ '/FusionCharts/FCF_Pie3D.swf'
+ when 'line'
+ '/FusionCharts/FCF_Line.swf'
+ when 'doug2d'
+ '/FusionCharts/FCF_Doughnut2D.swf'
+ when 'msline'
+ '/FusionCharts/FCF_MSLine.swf'
+ when 'mscol3d'
+ '/FusionCharts/FCF_MSColumn3D.swf'
+ when 'mscol2d'
+ '/FusionCharts/FCF_MSColumn2D.swf'
+ when 'msarea2d'
+ '/FusionCharts/FCF_MSArea2D.swf'
+ when 'msbar2d'
+ '/FusionCharts/FCF_MSBar2D.swf'
+ end
+ end
+
+ def parse_options(options)
+ original_names = Hash.new
+
+ options.each{|k,v|
+ case k
+ when :animation
+ original_names['animation'] = v
+ when :y_name
+ original_names['yAxisName'] = v
+ when :caption
+ original_names['caption'] = v
+ when :subcaption
+ original_names['subCaption'] = v
+ when :prefix
+ original_names['numberPrefix'] = v
+ when :precision
+ original_names['decimalPrecision'] = v
+ when :div_line_precision
+ original_names['divlinedecimalPrecision'] = v
+ when :limits_precision
+ original_names['limitsdecimalPrecision'] = v
+ when :f_number
+ original_names['formatNumber'] = v
+ when :f_number_scale
+ original_names['formatNumberScale'] = v
+ when :rotate
+ original_names['rotateNames'] = v
+ when :background
+ original_names['bgColor'] = v
+ when :line
+ original_names['lineColor'] = v
+ when :names
+ original_names['showNames'] = v
+ when :values
+ original_names['showValues'] = v
+ when :limits
+ original_names['showLimits'] = v
+ when :y_lines
+ original_names['numdivlines'] = v
+ when :p_y
+ original_names['parentYAxis'] = v
+ when :d_separator
+ original_names['decimalSeparator'] = v
+ when :t_separator
+ original_name['thousandSeparator'] = v
+ when :left_label_name
+ original_names['PYAxisName'] = v
+ when :right_label_name
+ original_names['SYAxisName'] = v
+ when :x_name
+ original_names['xAxisName'] = v
+ when :show_column_shadow
+ original_names['showColumnShadow'] = v
+ end
+ }
+ original_names
+ end
+end
diff --git a/web/vendor/plugins/ezgraphix/lib/tasks/ezgraphix_tasks.rake b/web/…
@@ -0,0 +1,19 @@
+ namespace :ezgraphix do
+ task :dir_setup do
+ Dir.mkdir("#{RAILS_ROOT}/public/FusionCharts", 0700)
+ puts "Created FusionCharts directory in public/"
+ end
+
+ task :cp_charts do
+ FileUtils.cp_r("#{RAILS_ROOT}/vendor/plugins/ezgraphix/public/FusionChar…
+ puts "Charts copied."
+ end
+
+ task :cp_javascript do
+ FileUtils.cp_r("#{RAILS_ROOT}/vendor/plugins/ezgraphix/public/javascript…
+ puts "FusionCharts.js copied"
+ end
+
+ desc "Creates and copies all necessary files in order to use ezgraphix!"
+ task :setup => [:dir_setup, :cp_charts, :cp_javascript]
+ end
+\ No newline at end of file
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_Area2D.swf b/…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_Bar2D.swf b/w…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_Candlestick.s…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_Column2D.swf …
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_Column3D.swf …
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_Doughnut2D.sw…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_Funnel.swf b/…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_Gantt.swf b/w…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_Line.swf b/we…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_MSArea2D.swf …
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_MSBar2D.swf b…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_MSColumn2D.sw…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_MSColumn2DLin…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_MSColumn3D.sw…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_MSColumn3DLin…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_MSLine.swf b/…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_Pie2D.swf b/w…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_Pie3D.swf b/w…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_StackedArea2D…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_StackedBar2D.…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_StackedColumn…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/FusionCharts/FCF_StackedColumn…
Binary files differ.
diff --git a/web/vendor/plugins/ezgraphix/public/javascripts/FusionCharts.js b/…
@@ -0,0 +1,361 @@
+/**
+ * FusionCharts: Flash Player detection and Chart embedding.
+ * Version 1.2.3F ( 22 November 2008) - Specialized for FusionChartsFREE
+ * Checking Flash Version >=6 and adde…
+ * Version: 1.2.3 (1st September, 2008) - Added Fix for % and & characters, sc…
+ * Version: 1.2.2 (10th July, 2008) - Added Fix for % scaled dimensions, fixes…
+ * Version: 1.2.1 (21st December, 2007) - Added setting up Transparent/opaque …
+ * Version: 1.2 (1st November, 2007) - Added FORM fixes for IE
+ * Version: 1.1 (29th June, 2007) - Added Player detection, New conditional fi…
+ *
+ * Morphed from SWFObject (http://blog.deconcept.com/swfobject/) under MIT Lic…
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ */
+if(typeof infosoftglobal == "undefined") var infosoftglobal = new Object();
+if(typeof infosoftglobal.FusionChartsUtil == "undefined") infosoftglobal.Fusio…
+infosoftglobal.FusionCharts = function(swf, id, w, h, debugMode, registerWithJ…
+ if (!document.getElementById) { return; }
+
+ //Flag to see whether data has been set initially
+ this.initialDataSet = false;
+
+ //Create container objects
+ this.params = new Object();
+ this.variables = new Object();
+ this.attributes = new Array();
+
+ //Set attributes for the SWF
+ if(swf) { this.setAttribute('swf', swf); }
+ if(id) { this.setAttribute('id', id); }
+
+ w=w.toString().replace(/\%$/,"%25");
+ if(w) { this.setAttribute('width', w); }
+ h=h.toString().replace(/\%$/,"%25");
+ if(h) { this.setAttribute('height', h); }
+
+
+ //Set background color
+ if(c) { this.addParam('bgcolor', c); }
+
+ //Set Quality
+ this.addParam('quality', 'high');
+
+ //Add scripting access parameter
+ this.addParam('allowScriptAccess', 'always');
+
+ //Pass width and height to be appended as chartWidth and chartHeight
+ this.addVariable('chartWidth', w);
+ this.addVariable('chartHeight', h);
+
+ //Whether in debug mode
+ debugMode = debugMode ? debugMode : 0;
+ this.addVariable('debugMode', debugMode);
+ //Pass DOM ID to Chart
+ this.addVariable('DOMId', id);
+ //Whether to registed with JavaScript
+ registerWithJS = registerWithJS ? registerWithJS : 0;
+ this.addVariable('registerWithJS', registerWithJS);
+
+ //Scale Mode of chart
+ scaleMode = scaleMode ? scaleMode : 'noScale';
+ this.addVariable('scaleMode', scaleMode);
+
+ //Application Message Language
+ lang = lang ? lang : 'EN';
+ this.addVariable('lang', lang);
+
+ //Whether to auto detect and re-direct to Flash Player installation
+ this.detectFlashVersion = detectFlashVersion?detectFlashVersion:1;
+ this.autoInstallRedirect = autoInstallRedirect?autoInstallRedirect:1;
+
+ //Ger Flash Player version
+ this.installedVer = infosoftglobal.FusionChartsUtil.getPlayerVersion();
+
+ if (!window.opera && document.all && this.installedVer.major > 7) {
+ // Only add the onunload cleanup if the Flash Player version s…
+ infosoftglobal.FusionCharts.doPrepUnload = true;
+ }
+}
+
+infosoftglobal.FusionCharts.prototype = {
+ setAttribute: function(name, value){
+ this.attributes[name] = value;
+ },
+ getAttribute: function(name){
+ return this.attributes[name];
+ },
+ addParam: function(name, value){
+ this.params[name] = value;
+ },
+ getParams: function(){
+ return this.params;
+ },
+ addVariable: function(name, value){
+ this.variables[name] = value;
+ },
+ getVariable: function(name){
+ return this.variables[name];
+ },
+ getVariables: function(){
+ return this.variables;
+ },
+ getVariablePairs: function(){
+ var variablePairs = new Array();
+ var key;
+ var variables = this.getVariables();
+ for(key in variables){
+ variablePairs.push(key +"="+ variables[key]);
+ }
+ return variablePairs;
+ },
+ getSWFHTML: function() {
+ var swfNode = "";
+ if (navigator.plugins && navigator.mimeTypes && navigator.mime…
+ // netscape plugin architecture
+ swfNode = '<embed type="application/x-shockwave-flash"…
+ swfNode += ' id="'+ this.getAttribute('id') +'" name="…
+ var params = this.getParams();
+ for(var key in params){ swfNode += [key] +'="'+ param…
+ var pairs = this.getVariablePairs().join("&");
+ if (pairs.length > 0){ swfNode += 'flashvars="'+ pair…
+ swfNode += '/>';
+ } else { // PC IE
+ swfNode = '<object id="'+ this.getAttribute('id') +'" …
+ swfNode += '<param name="movie" value="'+ this.getAttr…
+ var params = this.getParams();
+ for(var key in params) {
+ swfNode += '<param name="'+ key +'" value="'+ params[…
+ }
+ var pairs = this.getVariablePairs().join("&"); …
+ if(pairs.length > 0) {swfNode += '<param name="flashva…
+ swfNode += "</object>";
+ }
+ return swfNode;
+ },
+ setDataURL: function(strDataURL){
+ //This method sets the data URL for the chart.
+ //If being set initially
+ if (this.initialDataSet==false){
+ this.addVariable('dataURL',strDataURL);
+ //Update flag
+ this.initialDataSet = true;
+ }else{
+ //Else, we update the chart data using External Interf…
+ //Get reference to chart object
+ var chartObj = infosoftglobal.FusionChartsUtil.getChar…
+
+ if (!chartObj.setDataURL)
+ {
+ __flash__addCallback(chartObj, "setDataURL");
+ }
+
+ chartObj.setDataURL(strDataURL);
+ }
+ },
+ //This function :
+ //fixes the double quoted attributes to single quotes
+ //Encodes all quotes inside attribute values
+ //Encodes % to %25 and & to %26;
+ encodeDataXML: function(strDataXML){
+
+ var regExpReservedCharacters=["\\$","\\+"];
+ var arrDQAtt=strDataXML.match(/=\s*\".*?\"/g);
+ if (arrDQAtt){
+ for(var i=0;i<arrDQAtt.length;i++){
+ var repStr=arrDQAtt[i].replace(/^=\s*\…
+ repStr=repStr.replace(/\'/g,"%26apos;"…
+ var strTo=strDataXML.indexOf(arrDQAtt[…
+ var repStrr="='"+repStr+"'";
+ var strStart=strDataXML.substring(0,st…
+ var strEnd=strDataXML.substring(strTo+…
+ var strDataXML=strStart+repStrr+strEnd;
+ }
+ }
+
+ strDataXML=strDataXML.replace(/\"/g,"%26quot;");
+ strDataXML=strDataXML.replace(/%(?![\da-f]{2}|[\da-f]{…
+ strDataXML=strDataXML.replace(/\&/g,"%26");
+
+ return strDataXML;
+
+ },
+ setDataXML: function(strDataXML){
+ //If being set initially
+ if (this.initialDataSet==false){
+ //This method sets the data XML for the chart INITIALL…
+ this.addVariable('dataXML',this.encodeDataXML(strDataX…
+ //Update flag
+ this.initialDataSet = true;
+ }else{
+ //Else, we update the chart data using External Interf…
+ //Get reference to chart object
+ var chartObj = infosoftglobal.FusionChartsUtil.getChar…
+ chartObj.setDataXML(strDataXML);
+ }
+ },
+ setTransparent: function(isTransparent){
+ //Sets chart to transparent mode when isTransparent is true (d…
+ //When no parameter is passed, we assume transparent to be tru…
+ if(typeof isTransparent=="undefined") {
+ isTransparent=true;
+ }
+ //Set the property
+ if(isTransparent)
+ this.addParam('WMode', 'transparent');
+ else
+ this.addParam('WMode', 'Opaque');
+ },
+
+ render: function(elementId){
+ //First check for installed version of Flash Player - we need …
+ if((this.detectFlashVersion==1) && (this.installedVer.major < …
+ if (this.autoInstallRedirect==1){
+ //If we can auto redirect to install the playe…
+ var installationConfirm = window.confirm("You …
+ if (installationConfirm){
+ window.location = "http://www.adobe.co…
+ }else{
+ return false;
+ }
+ }else{
+ //Else, do not take an action. It means the de…
+ //So, expect the developers to provide a cours…
+ //window.alert("You need Adobe Flash Player 8 …
+ return false;
+ }
+ }else{
+ //Render the chart
+ var n = (typeof elementId == 'string') ? document.getE…
+ n.innerHTML = this.getSWFHTML();
+
+ //Added <FORM> compatibility
+ //Check if it's added in Mozilla embed array or if alr…
+ if(!document.embeds[this.getAttribute('id')] && !windo…
+ window[this.getAttribute('id')]=document.getElem…
+ //or else document.forms[formName/formIndex][c…
+ return true;
+ }
+ }
+}
+
+/* ---- detection functions ---- */
+infosoftglobal.FusionChartsUtil.getPlayerVersion = function(){
+ var PlayerVersion = new infosoftglobal.PlayerVersion([0,0,0]);
+ if(navigator.plugins && navigator.mimeTypes.length){
+ var x = navigator.plugins["Shockwave Flash"];
+ if(x && x.description) {
+ PlayerVersion = new infosoftglobal.PlayerVersion(x.des…
+ }
+ }else if (navigator.userAgent && navigator.userAgent.indexOf("Windows …
+ //If Windows CE
+ var axo = 1;
+ var counter = 3;
+ while(axo) {
+ try {
+ counter++;
+ axo = new ActiveXObject("ShockwaveFlash.Shockw…
+ PlayerVersion = new infosoftglobal.PlayerVersi…
+ } catch (e) {
+ axo = null;
+ }
+ }
+ } else {
+ // Win IE (non mobile)
+ // Do minor version lookup in IE, but avoid Flash Player 6 cra…
+ try{
+ var axo = new ActiveXObject("ShockwaveFlash.ShockwaveF…
+ }catch(e){
+ try {
+ var axo = new ActiveXObject("ShockwaveFlash.Sh…
+ PlayerVersion = new infosoftglobal.PlayerVersi…
+ axo.AllowScriptAccess = "always"; // error if …
+ } catch(e) {
+ if (PlayerVersion.major == 6) {
+ return PlayerVersion;
+ }
+ }
+ try {
+ axo = new ActiveXObject("ShockwaveFlash.Shockw…
+ } catch(e) {}
+ }
+ if (axo != null) {
+ PlayerVersion = new infosoftglobal.PlayerVersion(axo.G…
+ }
+ }
+ return PlayerVersion;
+}
+infosoftglobal.PlayerVersion = function(arrVersion){
+ this.major = arrVersion[0] != null ? parseInt(arrVersion[0]) : 0;
+ this.minor = arrVersion[1] != null ? parseInt(arrVersion[1]) : 0;
+ this.rev = arrVersion[2] != null ? parseInt(arrVersion[2]) : 0;
+}
+// ------------ Fix for Out of Memory Bug in IE in FP9 ---------------//
+/* Fix for video streaming bug */
+infosoftglobal.FusionChartsUtil.cleanupSWFs = function() {
+ var objects = document.getElementsByTagName("OBJECT");
+ for (var i = objects.length - 1; i >= 0; i--) {
+ objects[i].style.display = 'none';
+ for (var x in objects[i]) {
+ if (typeof objects[i][x] == 'function') {
+ objects[i][x] = function(){};
+ }
+ }
+ }
+}
+// Fixes bug in fp9
+if (infosoftglobal.FusionCharts.doPrepUnload) {
+ if (!infosoftglobal.unloadSet) {
+ infosoftglobal.FusionChartsUtil.prepUnload = function() {
+ __flash_unloadHandler = function(){};
+ __flash_savedUnloadHandler = function(){};
+ window.attachEvent("onunload", infosoftglobal.FusionCh…
+ }
+ window.attachEvent("onbeforeunload", infosoftglobal.FusionChar…
+ infosoftglobal.unloadSet = true;
+ }
+}
+/* Add document.getElementById if needed (mobile IE < 5) */
+if (!document.getElementById && document.all) { document.getElementById = func…
+/* Add Array.push if needed (ie5) */
+if (Array.prototype.push == null) { Array.prototype.push = function(item) { th…
+
+/* Function to return Flash Object from ID */
+infosoftglobal.FusionChartsUtil.getChartObject = function(id)
+{
+ var chartRef=null;
+ if (navigator.appName.indexOf("Microsoft Internet")==-1) {
+ if (document.embeds && document.embeds[id])
+ chartRef = document.embeds[id];
+ else
+ chartRef = window.document[id];
+ }
+ else {
+ chartRef = window[id];
+ }
+ if (!chartRef)
+ chartRef = document.getElementById(id);
+
+ return chartRef;
+}
+/*
+ Function to update chart's data at client side (FOR FusionCharts vFREE and 2.x
+*/
+infosoftglobal.FusionChartsUtil.updateChartXML = function(chartId, strXML){
+ //Get reference to chart object
+ var chartObj = infosoftglobal.FusionChartsUtil.getChartObject(chartId)…
+ //Set dataURL to null
+ chartObj.SetVariable("_root.dataURL","");
+ //Set the flag
+ chartObj.SetVariable("_root.isNewData","1");
+ //Set the actual data
+ chartObj.SetVariable("_root.newData",strXML);
+ //Go to the required frame
+ chartObj.TGotoLabel("/", "JavaScriptHandler");
+}
+
+
+/* Aliases for easy usage */
+var getChartFromId = infosoftglobal.FusionChartsUtil.getChartObject;
+var updateChartXML = infosoftglobal.FusionChartsUtil.updateChartXML;
+var FusionCharts = infosoftglobal.FusionCharts;
+\ No newline at end of file
diff --git a/web/vendor/plugins/ezgraphix/spec/ezgraphix_spec.rb b/web/vendor/p…
@@ -0,0 +1,85 @@
+#using rspec 1.1.11
+require 'rubygems'
+require 'spec'
+require File.dirname(__FILE__) + '/../lib/ezgraphix'
+require File.dirname(__FILE__) + '/../lib/ezgraphix/ezgraphix_helper'
+
+include EzgraphixHelper
+include Ezgraphix
+
+describe Graphic do
+
+ before do
+ @g = Graphic.new
+ end
+
+ it do
+ @g.should be_an_instance_of(Graphic)
+ end
+
+ it do
+ @g.should have(4).defaults
+ end
+
+ it "should have right defaults" do
+ @g.defaults.values_at(:c_type, :w, :h, :div_name).should == ['col3d', 300,…
+ end
+
+ before do
+ @g = Graphic.new(:c_type => 'bar2d', :w => 200, :caption => 'ezgraphix spe…
+ end
+
+ it "should merge defaults and options" do
+ @g.render_options.values_at(:c_type, :w, :h, :div_name, :caption).should =…
+ end
+
+ it "should have chart type, width, height and div_name" do
+ @g.c_type.should == 'bar2d'
+ @g.w.should == 200
+ @g.h.should == 300
+ @g.div_name.should == 'ez_graphic'
+ end
+
+ it "should have colors" do
+ Graphic::COLORS.should_not be_empty
+ end
+
+ it "should have valid colors" do
+ @g.rand_color.should be_instance_of(String)
+ @g.rand_color.length.should == 6
+ end
+
+ specify "colors should not repeat" do
+ Graphic::COLORS.inject([]){|used, color| used << @g.rand_color}.uniq.count…
+ end
+
+ before do
+ @g.data = {:ruby => 1, :perl => 2, :smalltalk => 3}
+ end
+
+ it "should have valid data" do
+ @g.data.values_at(:ruby, :perl, :smalltalk).should == [1,2,3]
+ end
+
+ before do
+ @g.render_options(:y_name => 'score')
+ end
+
+ it "should update render options" do
+ @g.render_options.values_at(:c_type, :w, :h, :div_name, :caption, :y_name…
+ end
+
+ it "should parse render options" do
+ parsed = parse_options(@g.render_options)
+ parsed.values_at('caption', 'yAxisName').should == ['ezgraphix spec', 'sco…
+ end
+
+ it "should have original filename/location" do
+ f_type(@g.c_type).should == '/FusionCharts/FCF_Bar2D.swf'
+ end
+
+ it "should have style" do
+ get_style(@g).should == 'render_simple'
+ end
+
+end
diff --git a/web/vendor/plugins/will_paginate/CHANGELOG.rdoc b/web/vendor/plugi…
@@ -0,0 +1,139 @@
+= 2.3.12, released 2009-12-01
+
+* make view helpers "HTML safe" for Rails 2.3.5 with rails_xss plugin
+
+= 2.3.11, released 2009-06-02
+
+* fix `enable_actionpack`
+
+= 2.3.10, released 2009-05-21
+
+* count_by_sql: don't use table alias with any adapters starting with "oracle"
+* Add back "AS count_table" alias to `paginate_by_sql` counter SQL
+
+= 2.3.9, released 2009-05-29
+
+* remove "AS count_table" alias from `paginate_by_sql` counter SQL
+* Rails 2.3.2 compat: monkeypatch Rails issue #2189 (count breaks has_many :th…
+* fix generation of page URLs that contain the "@" character
+* check for method existance in a ruby 1.8- and 1.9-compatible way
+* load will_paginate view helpers even if ActiveRecord is not loaded
+
+== 2.3.8, released 2009-03-09
+
+* Rails 2.3 compat: query parameter parsing with Rack
+
+== 2.3.7, released 2009-02-09
+
+* Removed all unnecessary &block variables since they cause serious memory dam…
+
+== 2.3.6, released 2008-10-26
+
+* Rails 2.2 fix: stop using `extract_attribute_names_from_match` inernal AR me…
+
+== 2.3.5, released 2008-10-07
+
+* update the backported named_scope implementation for Rails versions older th…
+* break out of scope of paginated_each() yielded block when used on named scop…
+* fix paginate(:from)
+
+== 2.3.4, released 2008-09-16
+
+* Removed gem dependency to Active Support (causes trouble with vendored rails…
+* Rails 2.1: fix a failing test and a deprecation warning.
+* Cope with scoped :select when counting.
+
+== 2.3.3, released 2008-08-29
+
+* Ensure that paginate_by_sql doesn't change the original SQL query.
+* RDoc love (now live at http://gitrdoc.com/mislav/will_paginate/tree/master)
+* Rename :prev_label to :previous_label for consistency. old name still functi…
+* ActiveRecord 2.1: Remove :include option from count_all query when it's poss…
+
+== 2.3.2, released 2008-05-16
+
+* Fixed LinkRenderer#stringified_merge by removing "return" from iterator block
+* Ensure that 'href' values in pagination links are escaped URLs
+
+== 2.3.1, released 2008-05-04
+
+* Fixed page numbers not showing with custom routes and implicit first page
+* Try to use Hanna for documentation (falls back to default RDoc template if n…
+
+== 2.3.0, released 2008-04-29
+
+* Changed LinkRenderer to receive collection, options and reference to view te…
+ constructor, but with the #prepare method. This is a step towards supporting…
+ LinkRenderer (or subclass) instances that may be preconfigured in some way
+* LinkRenderer now has #page_link and #page_span methods for easier customizat…
+ subclasses
+* Changed page_entries_info() method to adjust its output according to humaniz…
+ collection items. Override this with :entry_name parameter (singular).
+
+ page_entries_info(@posts)
+ #-> "Displaying all 12 posts"
+ page_entries_info(@posts, :entry_name => 'item')
+ #-> "Displaying all 12 items"
+
+== 2.2.3, released 2008-04-26
+
+* will_paginate gem is no longer published on RubyForge, but on
+ gems.github.com:
+
+ gem sources -a http://gems.github.com/ (you only need to do this once)
+ gem install mislav-will_paginate
+
+* extract reusable pagination testing stuff into WillPaginate::View
+* rethink the page URL construction mechanizm to be more bulletproof when
+ combined with custom routing for page parameter
+* test that anchor parameter can be used in pagination links
+
+== 2.2.2, released 2008-04-21
+
+* Add support for page parameter in custom routes like "/foo/page/2"
+* Change output of "page_entries_info" on single-page collection and erraneous
+ output with empty collection as reported by Tim Chater
+
+== 2.2.1, released 2008-04-08
+
+* take less risky path when monkeypatching named_scope; fix that it no longer
+ requires ActiveRecord::VERSION
+* use strings in "respond_to?" calls to work around a bug in acts_as_ferret
+ stable (ugh)
+* add rake release task
+
+
+== 2.2.0, released 2008-04-07
+
+=== API changes
+* Rename WillPaginate::Collection#page_count to "total_pages" for consistency.
+ If you implemented this interface, change your implementation accordingly.
+* Remove old, deprecated style of calling Array#paginate as "paginate(page,
+ per_page)". If you want to specify :page, :per_page or :total_entries, use a
+ parameter hash.
+* Rename LinkRenderer#url_options to "url_for" and drastically optimize it
+
+=== View changes
+* Added "prev_page" and "next_page" CSS classes on previous/next page buttons
+* Add examples of pagination links styling in "examples/index.html"
+* Change gap in pagination links from "..." to
+ "<span class="gap">&hellip;</span>".
+* Add "paginated_section", a block helper that renders pagination both above a…
+ below content in the block
+* Add rel="prev|next|start" to page links
+
+=== Other
+
+* Add ability to opt-in for Rails 2.1 feature "named_scope" by calling
+ WillPaginate.enable_named_scope (tested in Rails 1.2.6 and 2.0.2)
+* Support complex page parameters like "developers[page]"
+* Move Array#paginate definition to will_paginate/array.rb. You can now easily
+ use pagination on arrays outside of Rails:
+
+ gem 'will_paginate'
+ require 'will_paginate/array'
+
+* Add "paginated_each" method for iterating through every record by loading on…
+ one page of records at the time
+* Rails 2: Rescue from WillPaginate::InvalidPage error with 404 Not Found by
+ default
diff --git a/web/vendor/plugins/will_paginate/LICENSE b/web/vendor/plugins/will…
@@ -0,0 +1,18 @@
+Copyright (c) 2007 PJ Hyett and Mislav Marohnić
+
+Permission is hereby granted, free of charge, to any person obtaining a copy o…
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies …
+the Software, and to permit persons to whom the Software is furnished to do so…
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNE…
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/web/vendor/plugins/will_paginate/README.rdoc b/web/vendor/plugins/…
@@ -0,0 +1,107 @@
+= WillPaginate
+
+Pagination is just limiting the number of records displayed. Why should you let
+it get in your way while developing, then? This plugin makes magic happen. Did
+you ever want to be able to do just this on a model:
+
+ Post.paginate :page => 1, :order => 'created_at DESC'
+
+... and then render the page links with a single view helper? Well, now you
+can.
+
+Some resources to get you started:
+
+* {Installation instructions}[http://github.com/mislav/will_paginate/wikis/ins…
+ on {the wiki}[http://github.com/mislav/will_paginate/wikis]
+* Your mind reels with questions? Join our
+ {Google group}[http://groups.google.com/group/will_paginate].
+* {How to report bugs}[http://github.com/mislav/will_paginate/wikis/report-bug…
+
+
+== Example usage
+
+Use a paginate finder in the controller:
+
+ @posts = Post.paginate_by_board_id @board.id, :page => params[:page], :order…
+
+Yeah, +paginate+ works just like +find+ -- it just doesn't fetch all the
+records. Don't forget to tell it which page you want, or it will complain!
+Read more on WillPaginate::Finder::ClassMethods.
+
+Render the posts in your view like you would normally do. When you need to ren…
+pagination, just stick this in:
+
+ <%= will_paginate @posts %>
+
+You're done. (You can find the option list at WillPaginate::ViewHelpers.)
+
+How does it know how much items to fetch per page? It asks your model by calli…
+its <tt>per_page</tt> class method. You can define it like this:
+
+ class Post < ActiveRecord::Base
+ cattr_reader :per_page
+ @@per_page = 50
+ end
+
+... or like this:
+
+ class Post < ActiveRecord::Base
+ def self.per_page
+ 50
+ end
+ end
+
+... or don't worry about it at all. WillPaginate defines it to be <b>30</b> by…
+But you can always specify the count explicitly when calling +paginate+:
+
+ @posts = Post.paginate :page => params[:page], :per_page => 50
+
+The +paginate+ finder wraps the original finder and returns your resultset tha…
+some new properties. You can use the collection as you would with any ActiveRe…
+resultset. WillPaginate view helpers also need that object to be able to rende…
+
+ <ol>
+ <% for post in @posts -%>
+ <li>Render `post` in some nice way.</li>
+ <% end -%>
+ </ol>
+
+ <p>Now let's render us some pagination!</p>
+ <%= will_paginate @posts %>
+
+More detailed documentation:
+
+* WillPaginate::Finder::ClassMethods for pagination on your models;
+* WillPaginate::ViewHelpers for your views.
+
+
+== Authors and credits
+
+Authors:: Mislav Marohnić, PJ Hyett
+Original announcement:: http://errtheblog.com/post/929
+Original PHP source:: http://www.strangerstudios.com/sandbox/pagination/dig…
+
+All these people helped making will_paginate what it is now with their code
+contributions or just simply awesome ideas:
+
+Chris Wanstrath, Dr. Nic Williams, K. Adam Christensen, Mike Garey, Bence
+Golda, Matt Aimonetti, Charles Brian Quinn, Desi McAdam, James Coglan, Matijs
+van Zuijlen, Maria, Brendan Ribera, Todd Willey, Bryan Helmkamp, Jan Berkel,
+Lourens Naudé, Rick Olson, Russell Norris, Piotr Usewicz, Chris Eppstein,
+Denis Barushev, Ben Pickles.
+
+
+== Usable pagination in the UI
+
+There are some CSS styles to get you started in the "examples/" directory. They
+are {showcased online here}[http://mislav.uniqpath.com/will_paginate/].
+
+More reading about pagination as design pattern:
+
+* {Pagination 101}[http://kurafire.net/log/archive/2007/06/22/pagination-101]
+* {Pagination gallery}[http://www.smashingmagazine.com/2007/11/16/pagination-g…
+* {Pagination on Yahoo Design Pattern Library}[http://developer.yahoo.com/ypat…
+
+Want to discuss, request features, ask questions? Join the
+{Google group}[http://groups.google.com/group/will_paginate].
+
diff --git a/web/vendor/plugins/will_paginate/Rakefile b/web/vendor/plugins/wil…
@@ -0,0 +1,53 @@
+require 'rubygems'
+begin
+ hanna_dir = '/Users/mislav/Projects/Hanna/lib'
+ $:.unshift hanna_dir if File.exists? hanna_dir
+ require 'hanna/rdoctask'
+rescue LoadError
+ require 'rake'
+ require 'rake/rdoctask'
+end
+load 'test/tasks.rake'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Generate RDoc documentation for the will_paginate plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_files.include('README.rdoc', 'LICENSE', 'CHANGELOG.rdoc').
+ include('lib/**/*.rb').
+ exclude('lib/will_paginate/named_scope*').
+ exclude('lib/will_paginate/array.rb').
+ exclude('lib/will_paginate/version.rb')
+
+ rdoc.main = "README.rdoc" # page to start on
+ rdoc.title = "will_paginate documentation"
+
+ rdoc.rdoc_dir = 'doc' # rdoc output folder
+ rdoc.options << '--inline-source' << '--charset=UTF-8'
+ rdoc.options << '--webcvs=http://github.com/mislav/will_paginate/tree/master…
+end
+
+desc %{Update ".manifest" with the latest list of project filenames. Respect\
+.gitignore by excluding everything that git ignores. Update `files` and\
+`test_files` arrays in "*.gemspec" file if it's present.}
+task :manifest do
+ list = `git ls-files --full-name --exclude=*.gemspec --exclude=.*`.chomp.spl…
+
+ if spec_file = Dir['*.gemspec'].first
+ spec = File.read spec_file
+ spec.gsub! /^(\s* s.(test_)?files \s* = \s* )( \[ [^\]]* \] | %w\( [^)]* \…
+ assignment = $1
+ bunch = $2 ? list.grep(/^test\//) : list
+ '%s%%w(%s)' % [assignment, bunch.join(' ')]
+ end
+
+ File.open(spec_file, 'w') { |f| f << spec }
+ end
+ File.open('.manifest', 'w') { |f| f << list.join("\n") }
+end
+
+task :examples do
+ %x(haml examples/index.haml examples/index.html)
+ %x(sass examples/pagination.sass examples/pagination.css)
+end
diff --git a/web/vendor/plugins/will_paginate/examples/apple-circle.gif b/web/v…
Binary files differ.
diff --git a/web/vendor/plugins/will_paginate/examples/index.haml b/web/vendor/…
@@ -0,0 +1,69 @@
+!!!
+%html
+%head
+ %title Samples of pagination styling for will_paginate
+ %link{ :rel => 'stylesheet', :type => 'text/css', :href => 'pagination.css' }
+ %style{ :type => 'text/css' }
+ :sass
+ html
+ :margin 0
+ :padding 0
+ :background #999
+ :font normal 76% "Lucida Grande", Verdana, Helvetica, sans-serif
+ body
+ :margin 2em
+ :padding 2em
+ :border 2px solid gray
+ :background white
+ :color #222
+ h1
+ :font-size 2em
+ :font-weight normal
+ :margin 0 0 1em 0
+ h2
+ :font-size 1.4em
+ :margin 1em 0 .5em 0
+ pre
+ :font-size 13px
+ :font-family Monaco, "DejaVu Sans Mono", "Bitstream Vera Mono", "Couri…
+
+- pagination = '<span class="disabled prev_page">&laquo; Previous</span> <span…
+- pagination_no_page_links = '<span class="disabled prev_page">&laquo; Previou…
+
+%body
+ %h1 Samples of pagination styling for will_paginate
+ %p
+ Find these styles in <b>"examples/pagination.css"</b> of <i>will_paginate<…
+ There is a Sass version of it for all you sassy people.
+ %p
+ Read about good rules for pagination:
+ %a{ :href => 'http://kurafire.net/log/archive/2007/06/22/pagination-101' }…
+ %p
+ %em Warning:
+ page links below don't lead anywhere (so don't click on them).
+
+ %h2 Unstyled pagination <span style="font-weight:normal">(<i>ewww!</i>)</spa…
+ %div= pagination
+
+ %h2 Digg.com
+ .digg_pagination= pagination
+
+ %h2 Digg-style, no page links
+ .digg_pagination= pagination_no_page_links
+ %p Code that renders this:
+ %pre= '<code>%s</code>' % %[<%= will_paginate @posts, :page_links => false %…
+
+ %h2 Digg-style, extra content
+ .digg_pagination
+ .page_info Displaying entries <b>1&nbsp;-&nbsp;6</b> of <b>180</b> in total
+ = pagination
+ %p Code that renders this:
+ %pre= '<code>%s</code>' % %[<div class="digg_pagination">\n <div clas="page…
+
+ %h2 Apple.com store
+ .apple_pagination= pagination
+
+ %h2 Flickr.com
+ .flickr_pagination
+ = pagination
+ .page_info (118 photos)
diff --git a/web/vendor/plugins/will_paginate/examples/index.html b/web/vendor/…
@@ -0,0 +1,92 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.…
+<html>
+</html>
+<head>
+ <title>Samples of pagination styling for will_paginate</title>
+ <link href='pagination.css' rel='stylesheet' type='text/css' />
+ <style type='text/css'>
+ html {
+ margin: 0;
+ padding: 0;
+ background: #999;
+ font: normal 76% "Lucida Grande", Verdana, Helvetica, sans-serif; }
+
+ body {
+ margin: 2em;
+ padding: 2em;
+ border: 2px solid gray;
+ background: white;
+ color: #222; }
+
+ h1 {
+ font-size: 2em;
+ font-weight: normal;
+ margin: 0 0 1em 0; }
+
+ h2 {
+ font-size: 1.4em;
+ margin: 1em 0 .5em 0; }
+
+ pre {
+ font-size: 13px;
+ font-family: Monaco, "DejaVu Sans Mono", "Bitstream Vera Mono", "Courier…
+ </style>
+</head>
+<body>
+ <h1>Samples of pagination styling for will_paginate</h1>
+ <p>
+ Find these styles in <b>"examples/pagination.css"</b> of <i>will_paginate<…
+ There is a Sass version of it for all you sassy people.
+ </p>
+ <p>
+ Read about good rules for pagination:
+ <a href='http://kurafire.net/log/archive/2007/06/22/pagination-101'>Pagina…
+ </p>
+ <p>
+ <em>Warning:</em>
+ page links below don't lead anywhere (so don't click on them).
+ </p>
+ <h2>
+ Unstyled pagination <span style="font-weight:normal">(<i>ewww!</i>)</span>
+ </h2>
+ <div>
+ <span class="disabled prev_page">&laquo; Previous</span> <span class="curr…
+ </div>
+ <h2>Digg.com</h2>
+ <div class='digg_pagination'>
+ <span class="disabled prev_page">&laquo; Previous</span> <span class="curr…
+ </div>
+ <h2>Digg-style, no page links</h2>
+ <div class='digg_pagination'>
+ <span class="disabled prev_page">&laquo; Previous</span> <a href="./?page…
+ </div>
+ <p>Code that renders this:</p>
+ <pre>
+ <code>&lt;%= will_paginate @posts, :page_links =&gt; false %&gt;</code>
+ </pre>
+ <h2>Digg-style, extra content</h2>
+ <div class='digg_pagination'>
+ <div class='page_info'>
+ Displaying entries <b>1&nbsp;-&nbsp;6</b> of <b>180</b> in total
+ </div>
+ <span class="disabled prev_page">&laquo; Previous</span> <span class="curr…
+ </div>
+ <p>Code that renders this:</p>
+ <pre>
+ <code>&lt;div class="digg_pagination"&gt;
+ &lt;div clas="page_info"&gt;
+ &lt;%= page_entries_info @posts %&gt;
+ &lt;/div&gt;
+ &lt;%= will_paginate @posts, :container =&gt; false %&gt;
+ &lt;/div&gt;</code>
+ </pre>
+ <h2>Apple.com store</h2>
+ <div class='apple_pagination'>
+ <span class="disabled prev_page">&laquo; Previous</span> <span class="curr…
+ </div>
+ <h2>Flickr.com</h2>
+ <div class='flickr_pagination'>
+ <span class="disabled prev_page">&laquo; Previous</span> <span class="curr…
+ <div class='page_info'>(118 photos)</div>
+ </div>
+</body>
diff --git a/web/vendor/plugins/will_paginate/examples/pagination.css b/web/ven…
@@ -0,0 +1,91 @@
+.digg_pagination {
+ background: white;
+ /* self-clearing method: */ }
+ .digg_pagination a, .digg_pagination span, .digg_pagination em {
+ padding: .2em .5em;
+ display: block;
+ float: left;
+ margin-right: 1px; }
+ .digg_pagination span.disabled {
+ color: #999;
+ border: 1px solid #DDD; }
+ .digg_pagination em {
+ font-weight: bold;
+ background: #2E6AB1;
+ color: white;
+ border: 1px solid #2E6AB1; }
+ .digg_pagination a {
+ text-decoration: none;
+ color: #105CB6;
+ border: 1px solid #9AAFE5; }
+ .digg_pagination a:hover, .digg_pagination a:focus {
+ color: #003;
+ border-color: #003; }
+ .digg_pagination .page_info {
+ background: #2E6AB1;
+ color: white;
+ padding: .4em .6em;
+ width: 22em;
+ margin-bottom: .3em;
+ text-align: center; }
+ .digg_pagination .page_info b {
+ color: #003;
+ background: #6aa6ed;
+ padding: .1em .25em; }
+ .digg_pagination:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden; }
+ * html .digg_pagination {
+ height: 1%; }
+ *:first-child+html .digg_pagination {
+ overflow: hidden; }
+
+.apple_pagination {
+ background: #F1F1F1;
+ border: 1px solid #E5E5E5;
+ text-align: center;
+ padding: 1em; }
+ .apple_pagination a, .apple_pagination span, .digg_pagination em {
+ padding: .2em .3em; }
+ .apple_pagination span.disabled {
+ color: #AAA; }
+ .apple_pagination em {
+ font-weight: bold;
+ background: transparent url(apple-circle.gif) no-repeat 50% 50%; }
+ .apple_pagination a {
+ text-decoration: none;
+ color: black; }
+ .apple_pagination a:hover, .apple_pagination a:focus {
+ text-decoration: underline; }
+
+.flickr_pagination {
+ text-align: center;
+ padding: .3em; }
+ .flickr_pagination a, .flickr_pagination span, .digg_pagination em {
+ padding: .2em .5em; }
+ .flickr_pagination span.disabled {
+ color: #AAA; }
+ .flickr_pagination em {
+ font-weight: bold;
+ color: #FF0084; }
+ .flickr_pagination a {
+ border: 1px solid #DDDDDD;
+ color: #0063DC;
+ text-decoration: none; }
+ .flickr_pagination a:hover, .flickr_pagination a:focus {
+ border-color: #003366;
+ background: #0063DC;
+ color: white; }
+ .flickr_pagination .page_info {
+ color: #aaa;
+ padding-top: .8em; }
+ .flickr_pagination .prev_page, .flickr_pagination .next_page {
+ border-width: 2px; }
+ .flickr_pagination .prev_page {
+ margin-right: 1em; }
+ .flickr_pagination .next_page {
+ margin-left: 1em; }
+
diff --git a/web/vendor/plugins/will_paginate/examples/pagination.sass b/web/ve…
@@ -0,0 +1,91 @@
+.digg_pagination
+ :background white
+ a, span, em
+ :padding .2em .5em
+ :display block
+ :float left
+ :margin-right 1px
+ span.disabled
+ :color #999
+ :border 1px solid #DDD
+ em
+ :font-weight bold
+ :background #2E6AB1
+ :color white
+ :border 1px solid #2E6AB1
+ a
+ :text-decoration none
+ :color #105CB6
+ :border 1px solid #9AAFE5
+ &:hover, &:focus
+ :color #003
+ :border-color #003
+ .page_info
+ :background #2E6AB1
+ :color white
+ :padding .4em .6em
+ :width 22em
+ :margin-bottom .3em
+ :text-align center
+ b
+ :color #003
+ :background = #2E6AB1 + 60
+ :padding .1em .25em
+
+ /* self-clearing method:
+ &:after
+ :content "."
+ :display block
+ :height 0
+ :clear both
+ :visibility hidden
+ * html &
+ :height 1%
+ *:first-child+html &
+ :overflow hidden
+
+.apple_pagination
+ :background #F1F1F1
+ :border 1px solid #E5E5E5
+ :text-align center
+ :padding 1em
+ a, span, em
+ :padding .2em .3em
+ span.disabled
+ :color #AAA
+ em
+ :font-weight bold
+ :background transparent url(apple-circle.gif) no-repeat 50% 50%
+ a
+ :text-decoration none
+ :color black
+ &:hover, &:focus
+ :text-decoration underline
+
+.flickr_pagination
+ :text-align center
+ :padding .3em
+ a, span, em
+ :padding .2em .5em
+ span.disabled
+ :color #AAA
+ em
+ :font-weight bold
+ :color #FF0084
+ a
+ :border 1px solid #DDDDDD
+ :color #0063DC
+ :text-decoration none
+ &:hover, &:focus
+ :border-color #003366
+ :background #0063DC
+ :color white
+ .page_info
+ :color #aaa
+ :padding-top .8em
+ .prev_page, .next_page
+ :border-width 2px
+ .prev_page
+ :margin-right 1em
+ .next_page
+ :margin-left 1em
diff --git a/web/vendor/plugins/will_paginate/init.rb b/web/vendor/plugins/will…
@@ -0,0 +1 @@
+require 'will_paginate'
diff --git a/web/vendor/plugins/will_paginate/lib/will_paginate.rb b/web/vendor…
@@ -0,0 +1,90 @@
+require 'active_support'
+require 'will_paginate/core_ext'
+
+# = You *will* paginate!
+#
+# First read about WillPaginate::Finder::ClassMethods, then see
+# WillPaginate::ViewHelpers. The magical array you're handling in-between is
+# WillPaginate::Collection.
+#
+# Happy paginating!
+module WillPaginate
+ class << self
+ # shortcut for <tt>enable_actionpack</tt> and <tt>enable_activerecord</tt>…
+ def enable
+ enable_actionpack
+ enable_activerecord
+ end
+
+ # hooks WillPaginate::ViewHelpers into ActionView::Base
+ def enable_actionpack
+ return if ActionView::Base.instance_methods.include_method? :will_pagina…
+ require 'will_paginate/view_helpers'
+ ActionView::Base.send :include, ViewHelpers
+
+ if defined?(ActionController::Base) and ActionController::Base.respond_t…
+ ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] =…
+ end
+ end
+
+ # hooks WillPaginate::Finder into ActiveRecord::Base and classes that deal
+ # with associations
+ def enable_activerecord
+ return if ActiveRecord::Base.respond_to? :paginate
+ require 'will_paginate/finder'
+ ActiveRecord::Base.send :include, Finder
+
+ # support pagination on associations
+ a = ActiveRecord::Associations
+ [ a::AssociationCollection ].tap { |classes|
+ # detect http://dev.rubyonrails.org/changeset/9230
+ unless a::HasManyThroughAssociation.superclass == a::HasManyAssociation
+ classes << a::HasManyThroughAssociation
+ end
+ }.each do |klass|
+ klass.send :include, Finder::ClassMethods
+ klass.class_eval { alias_method_chain :method_missing, :paginate }
+ end
+
+ # monkeypatch Rails ticket #2189: "count breaks has_many :through"
+ ActiveRecord::Base.class_eval do
+ protected
+ def self.construct_count_options_from_args(*args)
+ result = super
+ result[0] = '*' if result[0].is_a?(String) and result[0] =~ /\.\*$/
+ result
+ end
+ end
+ end
+
+ # Enable named_scope, a feature of Rails 2.1, even if you have older Rails
+ # (tested on Rails 2.0.2 and 1.2.6).
+ #
+ # You can pass +false+ for +patch+ parameter to skip monkeypatching
+ # *associations*. Use this if you feel that <tt>named_scope</tt> broke
+ # has_many, has_many :through or has_and_belongs_to_many associations in
+ # your app. By passing +false+, you can still use <tt>named_scope</tt> in
+ # your models, but not through associations.
+ def enable_named_scope(patch = true)
+ return if defined? ActiveRecord::NamedScope
+ require 'will_paginate/named_scope'
+ require 'will_paginate/named_scope_patch' if patch
+
+ ActiveRecord::Base.send :include, WillPaginate::NamedScope
+ end
+ end
+
+ module Deprecation # :nodoc:
+ extend ActiveSupport::Deprecation
+
+ def self.warn(message, callstack = caller)
+ message = 'WillPaginate: ' + message.strip.gsub(/\s+/, ' ')
+ ActiveSupport::Deprecation.warn(message, callstack)
+ end
+ end
+end
+
+if defined? Rails
+ WillPaginate.enable_activerecord if defined? ActiveRecord
+ WillPaginate.enable_actionpack if defined? ActionController
+end
diff --git a/web/vendor/plugins/will_paginate/lib/will_paginate/array.rb b/web/…
@@ -0,0 +1,16 @@
+require 'will_paginate/collection'
+
+# http://www.desimcadam.com/archives/8
+Array.class_eval do
+ def paginate(options = {})
+ raise ArgumentError, "parameter hash expected (got #{options.inspect})" un…
+
+ WillPaginate::Collection.create(
+ options[:page] || 1,
+ options[:per_page] || 30,
+ options[:total_entries] || self.length
+ ) { |pager|
+ pager.replace self[pager.offset, pager.per_page].to_a
+ }
+ end
+end
diff --git a/web/vendor/plugins/will_paginate/lib/will_paginate/collection.rb b…
@@ -0,0 +1,144 @@
+module WillPaginate
+ # = Invalid page number error
+ # This is an ArgumentError raised in case a page was requested that is either
+ # zero or negative number. You should decide how do deal with such errors in
+ # the controller.
+ #
+ # If you're using Rails 2, then this error will automatically get handled li…
+ # 404 Not Found. The hook is in "will_paginate.rb":
+ #
+ # ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] = :…
+ #
+ # If you don't like this, use your preffered method of rescuing exceptions in
+ # public from your controllers to handle this differently. The +rescue_from+
+ # method is a nice addition to Rails 2.
+ #
+ # This error is *not* raised when a page further than the last page is
+ # requested. Use <tt>WillPaginate::Collection#out_of_bounds?</tt> method to
+ # check for those cases and manually deal with them as you see fit.
+ class InvalidPage < ArgumentError
+ def initialize(page, page_num)
+ super "#{page.inspect} given as value, which translates to '#{page_num}'…
+ end
+ end
+
+ # = The key to pagination
+ # Arrays returned from paginating finds are, in fact, instances of this litt…
+ # class. You may think of WillPaginate::Collection as an ordinary array with
+ # some extra properties. Those properties are used by view helpers to genera…
+ # correct page links.
+ #
+ # WillPaginate::Collection also assists in rolling out your own pagination
+ # solutions: see +create+.
+ #
+ # If you are writing a library that provides a collection which you would li…
+ # to conform to this API, you don't have to copy these methods over; simply
+ # make your plugin/gem dependant on this library and do:
+ #
+ # require 'will_paginate/collection'
+ # # WillPaginate::Collection is now available for use
+ class Collection < Array
+ attr_reader :current_page, :per_page, :total_entries, :total_pages
+
+ # Arguments to the constructor are the current page number, per-page limit
+ # and the total number of entries. The last argument is optional because it
+ # is best to do lazy counting; in other words, count *conditionally* after
+ # populating the collection using the +replace+ method.
+ def initialize(page, per_page, total = nil)
+ @current_page = page.to_i
+ raise InvalidPage.new(page, @current_page) if @current_page < 1
+ @per_page = per_page.to_i
+ raise ArgumentError, "`per_page` setting cannot be less than 1 (#{@per_p…
+
+ self.total_entries = total if total
+ end
+
+ # Just like +new+, but yields the object after instantiation and returns it
+ # afterwards. This is very useful for manual pagination:
+ #
+ # @entries = WillPaginate::Collection.create(1, 10) do |pager|
+ # result = Post.find(:all, :limit => pager.per_page, :offset => pager.…
+ # # inject the result array into the paginated collection:
+ # pager.replace(result)
+ #
+ # unless pager.total_entries
+ # # the pager didn't manage to guess the total count, do it manually
+ # pager.total_entries = Post.count
+ # end
+ # end
+ #
+ # The possibilities with this are endless. For another example, here is how
+ # WillPaginate used to define pagination for Array instances:
+ #
+ # Array.class_eval do
+ # def paginate(page = 1, per_page = 15)
+ # WillPaginate::Collection.create(page, per_page, size) do |pager|
+ # pager.replace self[pager.offset, pager.per_page].to_a
+ # end
+ # end
+ # end
+ #
+ # The Array#paginate API has since then changed, but this still serves as a
+ # fine example of WillPaginate::Collection usage.
+ def self.create(page, per_page, total = nil)
+ pager = new(page, per_page, total)
+ yield pager
+ pager
+ end
+
+ # Helper method that is true when someone tries to fetch a page with a
+ # larger number than the last page. Can be used in combination with flashes
+ # and redirecting.
+ def out_of_bounds?
+ current_page > total_pages
+ end
+
+ # Current offset of the paginated collection. If we're on the first page,
+ # it is always 0. If we're on the 2nd page and there are 30 entries per pa…
+ # the offset is 30. This property is useful if you want to render ordinals
+ # side by side with records in the view: simply start with offset + 1.
+ def offset
+ (current_page - 1) * per_page
+ end
+
+ # current_page - 1 or nil if there is no previous page
+ def previous_page
+ current_page > 1 ? (current_page - 1) : nil
+ end
+
+ # current_page + 1 or nil if there is no next page
+ def next_page
+ current_page < total_pages ? (current_page + 1) : nil
+ end
+
+ # sets the <tt>total_entries</tt> property and calculates <tt>total_pages<…
+ def total_entries=(number)
+ @total_entries = number.to_i
+ @total_pages = (@total_entries / per_page.to_f).ceil
+ end
+
+ # This is a magic wrapper for the original Array#replace method. It serves
+ # for populating the paginated collection after initialization.
+ #
+ # Why magic? Because it tries to guess the total number of entries judging
+ # by the size of given array. If it is shorter than +per_page+ limit, then…
+ # know we're on the last page. This trick is very useful for avoiding
+ # unnecessary hits to the database to do the counting after we fetched the
+ # data for the current page.
+ #
+ # However, after using +replace+ you should always test the value of
+ # +total_entries+ and set it to a proper value if it's +nil+. See the exam…
+ # in +create+.
+ def replace(array)
+ result = super
+
+ # The collection is shorter then page limit? Rejoice, because
+ # then we know that we are on the last page!
+ if total_entries.nil? and length < per_page and (current_page == 1 or le…
+ self.total_entries = offset + length
+ end
+
+ result
+ end
+ end
+end
diff --git a/web/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb b/w…
@@ -0,0 +1,43 @@
+require 'set'
+require 'will_paginate/array'
+
+# helper to check for method existance in ruby 1.8- and 1.9-compatible way
+# because `methods`, `instance_methods` and others return strings in 1.8 and s…
+#
+# ['foo', 'bar'].include_method?(:foo) # => true
+class Array
+ def include_method?(name)
+ name = name.to_sym
+ !!(find { |item| item.to_sym == name })
+ end
+end
+
+unless Hash.instance_methods.include_method? :except
+ Hash.class_eval do
+ # Returns a new hash without the given keys.
+ def except(*keys)
+ rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_…
+ reject { |key,| rejected.include?(key) }
+ end
+
+ # Replaces the hash without only the given keys.
+ def except!(*keys)
+ replace(except(*keys))
+ end
+ end
+end
+
+unless Hash.instance_methods.include_method? :slice
+ Hash.class_eval do
+ # Returns a new hash with only the given keys.
+ def slice(*keys)
+ allowed = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_k…
+ reject { |key,| !allowed.include?(key) }
+ end
+
+ # Replaces the hash with only the given keys.
+ def slice!(*keys)
+ replace(slice(*keys))
+ end
+ end
+end
diff --git a/web/vendor/plugins/will_paginate/lib/will_paginate/finder.rb b/web…
@@ -0,0 +1,264 @@
+require 'will_paginate/core_ext'
+
+module WillPaginate
+ # A mixin for ActiveRecord::Base. Provides +per_page+ class method
+ # and hooks things up to provide paginating finders.
+ #
+ # Find out more in WillPaginate::Finder::ClassMethods
+ #
+ module Finder
+ def self.included(base)
+ base.extend ClassMethods
+ class << base
+ alias_method_chain :method_missing, :paginate
+ # alias_method_chain :find_every, :paginate
+ define_method(:per_page) { 30 } unless respond_to?(:per_page)
+ end
+ end
+
+ # = Paginating finders for ActiveRecord models
+ #
+ # WillPaginate adds +paginate+, +per_page+ and other methods to
+ # ActiveRecord::Base class methods and associations. It also hooks into
+ # +method_missing+ to intercept pagination calls to dynamic finders such as
+ # +paginate_by_user_id+ and translate them to ordinary finders
+ # (+find_all_by_user_id+ in this case).
+ #
+ # In short, paginating finders are equivalent to ActiveRecord finders; the
+ # only difference is that we start with "paginate" instead of "find" and
+ # that <tt>:page</tt> is required parameter:
+ #
+ # @posts = Post.paginate :all, :page => params[:page], :order => 'create…
+ #
+ # In paginating finders, "all" is implicit. There is no sense in paginating
+ # a single record, right? So, you can drop the <tt>:all</tt> argument:
+ #
+ # Post.paginate(...) => Post.find :all
+ # Post.paginate_all_by_something => Post.find_all_by_something
+ # Post.paginate_by_something => Post.find_all_by_something
+ #
+ # == The importance of the <tt>:order</tt> parameter
+ #
+ # In ActiveRecord finders, <tt>:order</tt> parameter specifies columns for
+ # the <tt>ORDER BY</tt> clause in SQL. It is important to have it, since
+ # pagination only makes sense with ordered sets. Without the <tt>ORDER
+ # BY</tt> clause, databases aren't required to do consistent ordering when
+ # performing <tt>SELECT</tt> queries; this is especially true for
+ # PostgreSQL.
+ #
+ # Therefore, make sure you are doing ordering on a column that makes the
+ # most sense in the current context. Make that obvious to the user, also.
+ # For perfomance reasons you will also want to add an index to that column.
+ module ClassMethods
+ # This is the main paginating finder.
+ #
+ # == Special parameters for paginating finders
+ # * <tt>:page</tt> -- REQUIRED, but defaults to 1 if false or nil
+ # * <tt>:per_page</tt> -- defaults to <tt>CurrentModel.per_page</tt> (wh…
+ # * <tt>:total_entries</tt> -- use only if you manually count total entr…
+ # * <tt>:count</tt> -- additional options that are passed on to +count+
+ # * <tt>:finder</tt> -- name of the ActiveRecord finder used (default: "…
+ #
+ # All other options (+conditions+, +order+, ...) are forwarded to +find+
+ # and +count+ calls.
+ def paginate(*args)
+ options = args.pop
+ page, per_page, total_entries = wp_parse_options(options)
+ finder = (options[:finder] || 'find').to_s
+
+ if finder == 'find'
+ # an array of IDs may have been given:
+ total_entries ||= (Array === args.first and args.first.size)
+ # :all is implicit
+ args.unshift(:all) if args.empty?
+ end
+
+ WillPaginate::Collection.create(page, per_page, total_entries) do |pag…
+ count_options = options.except :page, :per_page, :total_entries, :fi…
+ find_options = count_options.except(:count).update(:offset => pager.…
+
+ args << find_options
+ # @options_from_last_find = nil
+ pager.replace(send(finder, *args) { |*a| yield(*a) if block_given? })
+
+ # magic counting for user convenience:
+ pager.total_entries = wp_count(count_options, args, finder) unless p…
+ end
+ end
+
+ # Iterates through all records by loading one page at a time. This is us…
+ # for migrations or any other use case where you don't want to load all …
+ # records in memory at once.
+ #
+ # It uses +paginate+ internally; therefore it accepts all of its options.
+ # You can specify a starting page with <tt>:page</tt> (default is 1). De…
+ # <tt>:order</tt> is <tt>"id"</tt>, override if necessary.
+ #
+ # See {Faking Cursors in ActiveRecord}[http://weblog.jamisbuck.org/2007/…
+ # where Jamis Buck describes this and a more efficient way for MySQL.
+ def paginated_each(options = {})
+ options = { :order => 'id', :page => 1 }.merge options
+ options[:page] = options[:page].to_i
+ options[:total_entries] = 0 # skip the individual count queries
+ total = 0
+
+ begin
+ collection = paginate(options)
+ with_exclusive_scope(:find => {}) do
+ # using exclusive scope so that the block is yielded in scope-free…
+ total += collection.each { |item| yield item }.size
+ end
+ options[:page] += 1
+ end until collection.size < collection.per_page
+
+ total
+ end
+
+ # Wraps +find_by_sql+ by simply adding LIMIT and OFFSET to your SQL stri…
+ # based on the params otherwise used by paginating finds: +page+ and
+ # +per_page+.
+ #
+ # Example:
+ #
+ # @developers = Developer.paginate_by_sql ['select * from developers w…
+ # :page => params[:page], :per_page => 3
+ #
+ # A query for counting rows will automatically be generated if you don't
+ # supply <tt>:total_entries</tt>. If you experience problems with this
+ # generated SQL, you might want to perform the count manually in your
+ # application.
+ #
+ def paginate_by_sql(sql, options)
+ WillPaginate::Collection.create(*wp_parse_options(options)) do |pager|
+ query = sanitize_sql(sql.dup)
+ original_query = query.dup
+ # add limit, offset
+ add_limit! query, :offset => pager.offset, :limit => pager.per_page
+ # perfom the find
+ pager.replace find_by_sql(query)
+
+ unless pager.total_entries
+ count_query = original_query.sub /\bORDER\s+BY\s+[\w`,\s]+$/mi, ''
+ count_query = "SELECT COUNT(*) FROM (#{count_query})"
+
+ unless self.connection.adapter_name =~ /^(oracle|oci$)/i
+ count_query << ' AS count_table'
+ end
+ # perform the count query
+ pager.total_entries = count_by_sql(count_query)
+ end
+ end
+ end
+
+ def respond_to?(method, include_priv = false) #:nodoc:
+ case method.to_sym
+ when :paginate, :paginate_by_sql
+ true
+ else
+ super || super(method.to_s.sub(/^paginate/, 'find'), include_priv)
+ end
+ end
+
+ protected
+
+ def method_missing_with_paginate(method, *args) #:nodoc:
+ # did somebody tried to paginate? if not, let them be
+ unless method.to_s.index('paginate') == 0
+ if block_given?
+ return method_missing_without_paginate(method, *args) { |*a| yield…
+ else
+ return method_missing_without_paginate(method, *args)
+ end
+ end
+
+ # paginate finders are really just find_* with limit and offset
+ finder = method.to_s.sub('paginate', 'find')
+ finder.sub!('find', 'find_all') if finder.index('find_by_') == 0
+
+ options = args.pop
+ raise ArgumentError, 'parameter hash expected' unless options.respond_…
+ options = options.dup
+ options[:finder] = finder
+ args << options
+
+ paginate(*args) { |*a| yield(*a) if block_given? }
+ end
+
+ # Does the not-so-trivial job of finding out the total number of entries
+ # in the database. It relies on the ActiveRecord +count+ method.
+ def wp_count(options, args, finder)
+ excludees = [:count, :order, :limit, :offset, :readonly]
+ excludees << :from unless ActiveRecord::Calculations::CALCULATIONS_OPT…
+
+ # we may be in a model or an association proxy
+ klass = (@owner and @reflection) ? @reflection.klass : self
+
+ # Use :select from scope if it isn't already present.
+ options[:select] = scope(:find, :select) unless options[:select]
+
+ if options[:select] and options[:select] =~ /^\s*DISTINCT\b/i
+ # Remove quoting and check for table_name.*-like statement.
+ if options[:select].gsub(/[`"]/, '') =~ /\w+\.\*/
+ options[:select] = "DISTINCT #{klass.table_name}.#{klass.primary_k…
+ end
+ else
+ excludees << :select # only exclude the select param if it doesn't b…
+ end
+
+ # count expects (almost) the same options as find
+ count_options = options.except *excludees
+
+ # merge the hash found in :count
+ # this allows you to specify :select, :order, or anything else just fo…
+ count_options.update options[:count] if options[:count]
+
+ # forget about includes if they are irrelevant (Rails 2.1)
+ if count_options[:include] and
+ klass.private_methods.include_method?(:references_eager_loaded_tab…
+ !klass.send(:references_eager_loaded_tables?, count_options)
+ count_options.delete :include
+ end
+
+ # we may have to scope ...
+ counter = Proc.new { count(count_options) }
+
+ count = if finder.index('find_') == 0 and klass.respond_to?(scoper = f…
+ # scope_out adds a 'with_finder' method which acts like with…
+ # then execute the count with the scoping provided by the wi…
+ send(scoper, &counter)
+ elsif finder =~ /^find_(all_by|by)_([_a-zA-Z]\w*)$/
+ # extract conditions from calls like "paginate_by_foo_and_ba…
+ attribute_names = $2.split('_and_')
+ conditions = construct_attributes_from_arguments(attribute_n…
+ with_scope(:find => { :conditions => conditions }, &counter)
+ else
+ counter.call
+ end
+
+ (!count.is_a?(Integer) && count.respond_to?(:length)) ? count.length :…
+ end
+
+ def wp_parse_options(options) #:nodoc:
+ raise ArgumentError, 'parameter hash expected' unless options.respond_…
+ options = options.symbolize_keys
+ raise ArgumentError, ':page parameter required' unless options.key? :p…
+
+ if options[:count] and options[:total_entries]
+ raise ArgumentError, ':count and :total_entries are mutually exclusi…
+ end
+
+ page = options[:page] || 1
+ per_page = options[:per_page] || self.per_page
+ total = options[:total_entries]
+ [page, per_page, total]
+ end
+
+ private
+
+ # def find_every_with_paginate(options)
+ # @options_from_last_find = options
+ # find_every_without_paginate(options)
+ # end
+ end
+ end
+end
diff --git a/web/vendor/plugins/will_paginate/lib/will_paginate/named_scope.rb …
@@ -0,0 +1,170 @@
+module WillPaginate
+ # This is a feature backported from Rails 2.1 because of its usefullness not…
+ # but in other aspects when managing complex conditions that you want to be …
+ module NamedScope
+ # All subclasses of ActiveRecord::Base have two named_scopes:
+ # * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and
+ # * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on…
+ #
+ # These anonymous scopes tend to be useful when procedurally generating co…
+ # intermediate values (scopes) around as first-class objects is convenient.
+ def self.included(base)
+ base.class_eval do
+ extend ClassMethods
+ named_scope :scoped, lambda { |scope| scope }
+ end
+ end
+
+ module ClassMethods
+ def scopes
+ read_inheritable_attribute(:scopes) || write_inheritable_attribute(:sc…
+ end
+
+ # Adds a class method for retrieving and querying objects. A scope repre…
+ # such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :i…
+ #
+ # class Shirt < ActiveRecord::Base
+ # named_scope :red, :conditions => {:color => 'red'}
+ # named_scope :dry_clean_only, :joins => :washing_instructions, :con…
+ # end
+ #
+ # The above calls to <tt>named_scope</tt> define class methods <tt>Shirt…
+ # in effect, represents the query <tt>Shirt.find(:all, :conditions => {:…
+ #
+ # Unlike Shirt.find(...), however, the object returned by <tt>Shirt.red<…
+ # constructed by a <tt>has_many</tt> declaration. For instance, you can …
+ # <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also…
+ # as with the association objects, name scopes acts like an Array, imple…
+ # <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> …
+ #
+ # These named scopes are composable. For instance, <tt>Shirt.red.dry_cle…
+ # Nested finds and calculations also work with these compositions: <tt>S…
+ # for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clea…
+ #
+ # All scopes are available as class methods on the ActiveRecord::Base de…
+ # <tt>has_many</tt> associations. If,
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :shirts
+ # end
+ #
+ # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton…
+ # only shirts.
+ #
+ # Named scopes can also be procedural.
+ #
+ # class Shirt < ActiveRecord::Base
+ # named_scope :colored, lambda { |color|
+ # { :conditions => { :color => color } }
+ # }
+ # end
+ #
+ # In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
+ #
+ # Named scopes can also have extensions, just as with <tt>has_many</tt> …
+ #
+ # class Shirt < ActiveRecord::Base
+ # named_scope :red, :conditions => {:color => 'red'} do
+ # def dom_id
+ # 'red_shirts'
+ # end
+ # end
+ # end
+ #
+ #
+ # For testing complex named scopes, you can examine the scoping options …
+ # <tt>proxy_options</tt> method on the proxy itself.
+ #
+ # class Shirt < ActiveRecord::Base
+ # named_scope :colored, lambda { |color|
+ # { :conditions => { :color => color } }
+ # }
+ # end
+ #
+ # expected_options = { :conditions => { :colored => 'red' } }
+ # assert_equal expected_options, Shirt.colored('red').proxy_options
+ def named_scope(name, options = {})
+ name = name.to_sym
+ scopes[name] = lambda do |parent_scope, *args|
+ Scope.new(parent_scope, case options
+ when Hash
+ options
+ when Proc
+ options.call(*args)
+ end) { |*a| yield(*a) if block_given? }
+ end
+ (class << self; self end).instance_eval do
+ define_method name do |*args|
+ scopes[name].call(self, *args)
+ end
+ end
+ end
+ end
+
+ class Scope
+ attr_reader :proxy_scope, :proxy_options
+
+ [].methods.each do |m|
+ unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|^find$|count|s…
+ delegate m, :to => :proxy_found
+ end
+ end
+
+ delegate :scopes, :with_scope, :to => :proxy_scope
+
+ def initialize(proxy_scope, options)
+ [options[:extend]].flatten.each { |extension| extend extension } if op…
+ extend Module.new { |*args| yield(*args) } if block_given?
+ @proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
+ end
+
+ def reload
+ load_found; self
+ end
+
+ def first(*args)
+ if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Has…
+ proxy_found.first(*args)
+ else
+ find(:first, *args)
+ end
+ end
+
+ def last(*args)
+ if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Has…
+ proxy_found.last(*args)
+ else
+ find(:last, *args)
+ end
+ end
+
+ def empty?
+ @found ? @found.empty? : count.zero?
+ end
+
+ def respond_to?(method, include_private = false)
+ super || @proxy_scope.respond_to?(method, include_private)
+ end
+
+ protected
+ def proxy_found
+ @found || load_found
+ end
+
+ private
+ def method_missing(method, *args)
+ if scopes.include?(method)
+ scopes[method].call(self, *args)
+ else
+ with_scope :find => proxy_options do
+ proxy_scope.send(method, *args) { |*a| yield(*a) if block_given? }
+ end
+ end
+ end
+
+ def load_found
+ @found = find(:all)
+ end
+ end
+ end
+end
diff --git a/web/vendor/plugins/will_paginate/lib/will_paginate/named_scope_pat…
@@ -0,0 +1,37 @@
+ActiveRecord::Associations::AssociationProxy.class_eval do
+ protected
+ def with_scope(*args)
+ @reflection.klass.send(:with_scope, *args) { |*a| yield(*a) if block_given…
+ end
+end
+
+[ ActiveRecord::Associations::AssociationCollection,
+ ActiveRecord::Associations::HasManyThroughAssociation ].each do |klass|
+ klass.class_eval do
+ protected
+ alias :method_missing_without_scopes :method_missing_without_paginate
+ def method_missing_without_paginate(method, *args)
+ if @reflection.klass.scopes.include?(method)
+ @reflection.klass.scopes[method].call(self, *args) { |*a| yield(*a) if…
+ else
+ method_missing_without_scopes(method, *args) { |*a| yield(*a) if block…
+ end
+ end
+ end
+end
+
+# Rails 1.2.6
+ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do
+ protected
+ def method_missing(method, *args)
+ if @target.respond_to?(method) || ([email protected]_to?(method) …
+ super
+ elsif @reflection.klass.scopes.include?(method)
+ @reflection.klass.scopes[method].call(self, *args)
+ else
+ @reflection.klass.with_scope(:find => { :conditions => @finder_sql, :joi…
+ @reflection.klass.send(method, *args) { |*a| yield(*a) if block_given?…
+ end
+ end
+ end
+end if ActiveRecord::Base.respond_to? :find_first
diff --git a/web/vendor/plugins/will_paginate/lib/will_paginate/version.rb b/we…
@@ -0,0 +1,9 @@
+module WillPaginate
+ module VERSION
+ MAJOR = 2
+ MINOR = 3
+ TINY = 15
+
+ STRING = [MAJOR, MINOR, TINY].join('.')
+ end
+end
diff --git a/web/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb…
@@ -0,0 +1,410 @@
+require 'will_paginate/core_ext'
+
+module WillPaginate
+ # = Will Paginate view helpers
+ #
+ # The main view helper, #will_paginate, renders
+ # pagination links for the given collection. The helper itself is lightweight
+ # and serves only as a wrapper around LinkRenderer instantiation; the
+ # renderer then does all the hard work of generating the HTML.
+ #
+ # == Global options for helpers
+ #
+ # Options for pagination helpers are optional and get their default values f…
+ # <tt>WillPaginate::ViewHelpers.pagination_options</tt> hash. You can write …
+ # override default options on the global level:
+ #
+ # WillPaginate::ViewHelpers.pagination_options[:previous_label] = 'Previou…
+ #
+ # By putting this into "config/initializers/will_paginate.rb" (or simply env…
+ # older versions of Rails) you can easily translate link texts to previous
+ # and next pages, as well as override some other defaults to your liking.
+ module ViewHelpers
+ # default options that can be overridden on the global level
+ @@pagination_options = {
+ :class => 'pagination',
+ :previous_label => '&laquo; Previous',
+ :next_label => 'Next &raquo;',
+ :inner_window => 4, # links around the current page
+ :outer_window => 1, # links around beginning and end
+ :separator => ' ', # single space is friendly to spiders and non-gr…
+ :param_name => :page,
+ :params => nil,
+ :renderer => 'WillPaginate::LinkRenderer',
+ :page_links => true,
+ :container => true
+ }
+ mattr_reader :pagination_options
+
+ # Renders Digg/Flickr-style pagination for a WillPaginate::Collection
+ # object. Nil is returned if there is only one page in total; no point in
+ # rendering the pagination in that case...
+ #
+ # ==== Options
+ # Display options:
+ # * <tt>:previous_label</tt> -- default: "« Previous" (this parameter is …
+ # * <tt>:next_label</tt> -- default: "Next »"
+ # * <tt>:page_links</tt> -- when false, only previous/next links are rende…
+ # * <tt>:inner_window</tt> -- how many links are shown around the current …
+ # * <tt>:outer_window</tt> -- how many links are around the first and the …
+ # * <tt>:separator</tt> -- string separator for page HTML elements (defaul…
+ #
+ # HTML options:
+ # * <tt>:class</tt> -- CSS class name for the generated DIV (default: "pag…
+ # * <tt>:container</tt> -- toggles rendering of the DIV container for pagi…
+ # false only when you are rendering your own pagination markup (default:…
+ # * <tt>:id</tt> -- HTML ID for the container (default: nil). Pass +true+ …
+ # automatically generated from the class name of objects in collection: …
+ # ArticleComment models would yield an ID of "article_comments_paginatio…
+ #
+ # Advanced options:
+ # * <tt>:param_name</tt> -- parameter name for page number in URLs (defaul…
+ # * <tt>:params</tt> -- additional parameters when generating pagination l…
+ # (eg. <tt>:controller => "foo", :action => nil</tt>)
+ # * <tt>:renderer</tt> -- class name, class or instance of a link renderer…
+ # <tt>WillPaginate::LinkRenderer</tt>)
+ #
+ # All options not recognized by will_paginate will become HTML attributes …
+ # element for pagination links (the DIV). For example:
+ #
+ # <%= will_paginate @posts, :style => 'font-size: small' %>
+ #
+ # ... will result in:
+ #
+ # <div class="pagination" style="font-size: small"> ... </div>
+ #
+ # ==== Using the helper without arguments
+ # If the helper is called without passing in the collection object, it will
+ # try to read from the instance variable inferred by the controller name.
+ # For example, calling +will_paginate+ while the current controller is
+ # PostsController will result in trying to read from the <tt>@posts</tt>
+ # variable. Example:
+ #
+ # <%= will_paginate :id => true %>
+ #
+ # ... will result in <tt>@post</tt> collection getting paginated:
+ #
+ # <div class="pagination" id="posts_pagination"> ... </div>
+ #
+ def will_paginate(collection = nil, options = {})
+ options, collection = collection, nil if collection.is_a? Hash
+ unless collection or !controller
+ collection_name = "@#{controller.controller_name}"
+ collection = instance_variable_get(collection_name)
+ raise ArgumentError, "The #{collection_name} variable appears to be em…
+ "forget to pass the collection object for will_paginate?" unless col…
+ end
+ # early exit if there is nothing to render
+ return nil unless WillPaginate::ViewHelpers.total_pages_for_collection(c…
+
+ options = options.symbolize_keys.reverse_merge WillPaginate::ViewHelpers…
+ if options[:prev_label]
+ WillPaginate::Deprecation::warn(":prev_label view parameter is now :pr…
+ options[:previous_label] = options.delete(:prev_label)
+ end
+
+ # get the renderer instance
+ renderer = case options[:renderer]
+ when String
+ options[:renderer].to_s.constantize.new
+ when Class
+ options[:renderer].new
+ else
+ options[:renderer]
+ end
+ # render HTML for pagination
+ renderer.prepare collection, options, self
+ renderer.to_html
+ end
+
+ # Wrapper for rendering pagination links at both top and bottom of a block
+ # of content.
+ #
+ # <% paginated_section @posts do %>
+ # <ol id="posts">
+ # <% for post in @posts %>
+ # <li> ... </li>
+ # <% end %>
+ # </ol>
+ # <% end %>
+ #
+ # will result in:
+ #
+ # <div class="pagination"> ... </div>
+ # <ol id="posts">
+ # ...
+ # </ol>
+ # <div class="pagination"> ... </div>
+ #
+ # Arguments are passed to a <tt>will_paginate</tt> call, so the same optio…
+ # apply. Don't use the <tt>:id</tt> option; otherwise you'll finish with t…
+ # blocks of pagination links sharing the same ID (which is invalid HTML).
+ def paginated_section(*args, &block)
+ pagination = will_paginate(*args).to_s
+
+ unless ActionView::Base.respond_to? :erb_variable
+ concat pagination
+ yield
+ concat pagination
+ else
+ content = pagination + capture(&block) + pagination
+ concat(content, block.binding)
+ end
+ end
+
+ # Renders a helpful message with numbers of displayed vs. total entries.
+ # You can use this as a blueprint for your own, similar helpers.
+ #
+ # <%= page_entries_info @posts %>
+ # #-> Displaying posts 6 - 10 of 26 in total
+ #
+ # By default, the message will use the humanized class name of objects
+ # in collection: for instance, "project types" for ProjectType models.
+ # Override this with the <tt>:entry_name</tt> parameter:
+ #
+ # <%= page_entries_info @posts, :entry_name => 'item' %>
+ # #-> Displaying items 6 - 10 of 26 in total
+ def page_entries_info(collection, options = {})
+ entry_name = options[:entry_name] ||
+ (collection.empty?? 'entry' : collection.first.class.name.underscore.s…
+
+ if collection.total_pages < 2
+ case collection.size
+ when 0; "No #{entry_name.pluralize} found"
+ when 1; "Displaying <b>1</b> #{entry_name}"
+ else; "Displaying <b>all #{collection.size}</b> #{entry_name.plurali…
+ end
+ else
+ %{Displaying #{entry_name.pluralize} <b>%d&nbsp;-&nbsp;%d</b> of <b>%d…
+ collection.offset + 1,
+ collection.offset + collection.length,
+ collection.total_entries
+ ]
+ end
+ end
+
+ if respond_to? :safe_helper
+ safe_helper :will_paginate, :paginated_section, :page_entries_info
+ end
+
+ def self.total_pages_for_collection(collection) #:nodoc:
+ if collection.respond_to?('page_count') and !collection.respond_to?('tot…
+ WillPaginate::Deprecation.warn %{
+ You are using a paginated collection of class #{collection.class.nam…
+ which conforms to the old API of WillPaginate::Collection by using
+ `page_count`, while the current method name is `total_pages`. Please
+ upgrade yours or 3rd-party code that provides the paginated collecti…
+ class << collection
+ def total_pages; page_count; end
+ end
+ end
+ collection.total_pages
+ end
+ end
+
+ # This class does the heavy lifting of actually building the pagination
+ # links. It is used by the <tt>will_paginate</tt> helper internally.
+ class LinkRenderer
+
+ # The gap in page links is represented by:
+ #
+ # <span class="gap">&hellip;</span>
+ attr_accessor :gap_marker
+
+ def initialize
+ @gap_marker = '<span class="gap">&hellip;</span>'
+ end
+
+ # * +collection+ is a WillPaginate::Collection instance or any other object
+ # that conforms to that API
+ # * +options+ are forwarded from +will_paginate+ view helper
+ # * +template+ is the reference to the template being rendered
+ def prepare(collection, options, template)
+ @collection = collection
+ @options = options
+ @template = template
+
+ # reset values in case we're re-using this instance
+ @total_pages = @param_name = @url_string = nil
+ end
+
+ # Process it! This method returns the complete HTML string which contains
+ # pagination links. Feel free to subclass LinkRenderer and change this
+ # method as you see fit.
+ def to_html
+ links = @options[:page_links] ? windowed_links : []
+ # previous/next buttons
+ links.unshift page_link_or_span(@collection.previous_page, 'disabled pre…
+ links.push page_link_or_span(@collection.next_page, 'disabled nex…
+
+ html = links.join(@options[:separator])
+ html = html.html_safe if html.respond_to? :html_safe
+ @options[:container] ? @template.content_tag(:div, html, html_attributes…
+ end
+
+ # Returns the subset of +options+ this instance was initialized with that
+ # represent HTML attributes for the container element of pagination links.
+ def html_attributes
+ return @html_attributes if @html_attributes
+ @html_attributes = @options.except *(WillPaginate::ViewHelpers.paginatio…
+ # pagination of Post models will have the ID of "posts_pagination"
+ if @options[:container] and @options[:id] === true
+ @html_attributes[:id] = @collection.first.class.name.underscore.plural…
+ end
+ @html_attributes
+ end
+
+ protected
+
+ # Collects link items for visible page numbers.
+ def windowed_links
+ prev = nil
+
+ visible_page_numbers.inject [] do |links, n|
+ # detect gaps:
+ links << gap_marker if prev and n > prev + 1
+ links << page_link_or_span(n, 'current')
+ prev = n
+ links
+ end
+ end
+
+ # Calculates visible page numbers using the <tt>:inner_window</tt> and
+ # <tt>:outer_window</tt> options.
+ def visible_page_numbers
+ inner_window, outer_window = @options[:inner_window].to_i, @options[:out…
+ window_from = current_page - inner_window
+ window_to = current_page + inner_window
+
+ # adjust lower or upper limit if other is out of bounds
+ if window_to > total_pages
+ window_from -= window_to - total_pages
+ window_to = total_pages
+ end
+ if window_from < 1
+ window_to += 1 - window_from
+ window_from = 1
+ window_to = total_pages if window_to > total_pages
+ end
+
+ visible = (1..total_pages).to_a
+ left_gap = (2 + outer_window)...window_from
+ right_gap = (window_to + 1)...(total_pages - outer_window)
+ visible -= left_gap.to_a if left_gap.last - left_gap.first > 1
+ visible -= right_gap.to_a if right_gap.last - right_gap.first > 1
+
+ visible
+ end
+
+ def page_link_or_span(page, span_class, text = nil)
+ text ||= page.to_s
+ text = text.html_safe if text.respond_to? :html_safe
+
+ if page and page != current_page
+ classnames = span_class && span_class.index(' ') && span_class.split('…
+ page_link page, text, :rel => rel_value(page), :class => classnames
+ else
+ page_span page, text, :class => span_class
+ end
+ end
+
+ def page_link(page, text, attributes = {})
+ @template.link_to text, url_for(page), attributes
+ end
+
+ def page_span(page, text, attributes = {})
+ @template.content_tag :span, text, attributes
+ end
+
+ # Returns URL params for +page_link_or_span+, taking the current GET params
+ # and <tt>:params</tt> option into account.
+ def url_for(page)
+ page_one = page == 1
+ unless @url_string and !page_one
+ @url_params = {}
+ # page links should preserve GET parameters
+ stringified_merge @url_params, @template.params if @template.request.g…
+ stringified_merge @url_params, @options[:params] if @options[:params]
+
+ if complex = param_name.index(/[^\w-]/)
+ page_param = parse_query_parameters("#{param_name}=#{page}")
+
+ stringified_merge @url_params, page_param
+ else
+ @url_params[param_name] = page_one ? 1 : 2
+ end
+
+ url = @template.url_for(@url_params)
+ return url if page_one
+
+ if complex
+ @url_string = url.sub(%r!((?:\?|&amp;)#{CGI.escape param_name}=)#{pa…
+ return url
+ else
+ @url_string = url
+ @url_params[param_name] = 3
+ @template.url_for(@url_params).split(//).each_with_index do |char, i|
+ if char == '3' and url[i, 1] == '2'
+ @url_string[i] = "\0"
+ break
+ end
+ end
+ end
+ end
+ # finally!
+ @url_string.sub "\0", page.to_s
+ end
+
+ private
+
+ def rel_value(page)
+ case page
+ when @collection.previous_page; 'prev' + (page == 1 ? ' start' : '')
+ when @collection.next_page; 'next'
+ when 1; 'start'
+ end
+ end
+
+ def current_page
+ @collection.current_page
+ end
+
+ def total_pages
+ @total_pages ||= WillPaginate::ViewHelpers.total_pages_for_collection(@c…
+ end
+
+ def param_name
+ @param_name ||= @options[:param_name].to_s
+ end
+
+ # Recursively merge into target hash by using stringified keys from the ot…
+ def stringified_merge(target, other)
+ other.each do |key, value|
+ key = key.to_s # this line is what it's all about!
+ existing = target[key]
+
+ if value.is_a?(Hash) and (existing.is_a?(Hash) or existing.nil?)
+ stringified_merge(existing || (target[key] = {}), value)
+ else
+ target[key] = value
+ end
+ end
+ end
+
+ def parse_query_parameters(params)
+ if defined? Rack::Utils
+ # For Rails > 2.3
+ Rack::Utils.parse_nested_query(params)
+ elsif defined?(ActionController::AbstractRequest)
+ ActionController::AbstractRequest.parse_query_parameters(params)
+ elsif defined?(ActionController::UrlEncodedPairParser)
+ # For Rails > 2.2
+ ActionController::UrlEncodedPairParser.parse_query_parameters(params)
+ elsif defined?(CGIMethods)
+ CGIMethods.parse_query_parameters(params)
+ else
+ raise "unsupported ActionPack version"
+ end
+ end
+ end
+end
diff --git a/web/vendor/plugins/will_paginate/test/boot.rb b/web/vendor/plugins…
@@ -0,0 +1,21 @@
+plugin_root = File.join(File.dirname(__FILE__), '..')
+version = ENV['RAILS_VERSION']
+version = nil if version and version == ""
+
+# first look for a symlink to a copy of the framework
+if !version and framework_root = ["#{plugin_root}/rails", "#{plugin_root}/../.…
+ puts "found framework root: #{framework_root}"
+ # this allows for a plugin to be tested outside of an app and without Rails …
+ $:.unshift "#{framework_root}/activesupport/lib", "#{framework_root}/activer…
+else
+ # simply use installed gems if available
+ puts "using Rails#{version ? ' ' + version : nil} gems"
+ require 'rubygems'
+
+ if version
+ gem 'rails', version
+ else
+ gem 'actionpack', '< 3.0.0.a'
+ gem 'activerecord', '< 3.0.0.a'
+ end
+end
diff --git a/web/vendor/plugins/will_paginate/test/collection_test.rb b/web/ven…
@@ -0,0 +1,143 @@
+require 'helper'
+require 'will_paginate/array'
+
+class ArrayPaginationTest < Test::Unit::TestCase
+
+ def setup ; end
+
+ def test_simple
+ collection = ('a'..'e').to_a
+
+ [{ :page => 1, :per_page => 3, :expected => %w( a b c ) },
+ { :page => 2, :per_page => 3, :expected => %w( d e ) },
+ { :page => 1, :per_page => 5, :expected => %w( a b c d e ) },
+ { :page => 3, :per_page => 5, :expected => [] },
+ ].
+ each do |conditions|
+ expected = conditions.delete :expected
+ assert_equal expected, collection.paginate(conditions)
+ end
+ end
+
+ def test_defaults
+ result = (1..50).to_a.paginate
+ assert_equal 1, result.current_page
+ assert_equal 30, result.size
+ end
+
+ def test_deprecated_api
+ assert_raise(ArgumentError) { [].paginate(2) }
+ assert_raise(ArgumentError) { [].paginate(2, 10) }
+ end
+
+ def test_total_entries_has_precedence
+ result = %w(a b c).paginate :total_entries => 5
+ assert_equal 5, result.total_entries
+ end
+
+ def test_argument_error_with_params_and_another_argument
+ assert_raise ArgumentError do
+ [].paginate({}, 5)
+ end
+ end
+
+ def test_paginated_collection
+ entries = %w(a b c)
+ collection = create(2, 3, 10) do |pager|
+ assert_equal entries, pager.replace(entries)
+ end
+
+ assert_equal entries, collection
+ assert_respond_to_all collection, %w(total_pages each offset size current_…
+ assert_kind_of Array, collection
+ assert_instance_of Array, collection.entries
+ assert_equal 3, collection.offset
+ assert_equal 4, collection.total_pages
+ assert !collection.out_of_bounds?
+ end
+
+ def test_previous_next_pages
+ collection = create(1, 1, 3)
+ assert_nil collection.previous_page
+ assert_equal 2, collection.next_page
+
+ collection = create(2, 1, 3)
+ assert_equal 1, collection.previous_page
+ assert_equal 3, collection.next_page
+
+ collection = create(3, 1, 3)
+ assert_equal 2, collection.previous_page
+ assert_nil collection.next_page
+ end
+
+ def test_out_of_bounds
+ entries = create(2, 3, 2){}
+ assert entries.out_of_bounds?
+
+ entries = create(1, 3, 2){}
+ assert !entries.out_of_bounds?
+ end
+
+ def test_guessing_total_count
+ entries = create do |pager|
+ # collection is shorter than limit
+ pager.replace array
+ end
+ assert_equal 8, entries.total_entries
+
+ entries = create(2, 5, 10) do |pager|
+ # collection is shorter than limit, but we have an explicit count
+ pager.replace array
+ end
+ assert_equal 10, entries.total_entries
+
+ entries = create do |pager|
+ # collection is the same as limit; we can't guess
+ pager.replace array(5)
+ end
+ assert_equal nil, entries.total_entries
+
+ entries = create do |pager|
+ # collection is empty; we can't guess
+ pager.replace array(0)
+ end
+ assert_equal nil, entries.total_entries
+
+ entries = create(1) do |pager|
+ # collection is empty and we're on page 1,
+ # so the whole thing must be empty, too
+ pager.replace array(0)
+ end
+ assert_equal 0, entries.total_entries
+ end
+
+ def test_invalid_page
+ bad_inputs = [0, -1, nil, '', 'Schnitzel']
+
+ bad_inputs.each do |bad|
+ assert_raise(WillPaginate::InvalidPage) { create bad }
+ end
+ end
+
+ def test_invalid_per_page_setting
+ assert_raise(ArgumentError) { create(1, -1) }
+ end
+
+ def test_page_count_was_removed
+ assert_raise(NoMethodError) { create.page_count }
+ # It's `total_pages` now.
+ end
+
+ private
+ def create(page = 2, limit = 5, total = nil, &block)
+ if block_given?
+ WillPaginate::Collection.create(page, limit, total, &block)
+ else
+ WillPaginate::Collection.new(page, limit, total)
+ end
+ end
+
+ def array(size = 3)
+ Array.new(size)
+ end
+end
diff --git a/web/vendor/plugins/will_paginate/test/console b/web/vendor/plugins…
@@ -0,0 +1,8 @@
+#!/usr/bin/env ruby
+irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
+libs = []
+
+libs << 'irb/completion'
+libs << File.join('lib', 'load_fixtures')
+
+exec "#{irb} -Ilib:test#{libs.map{ |l| " -r #{l}" }.join} --simple-prompt"
diff --git a/web/vendor/plugins/will_paginate/test/database.yml b/web/vendor/pl…
@@ -0,0 +1,22 @@
+sqlite3:
+ database: ":memory:"
+ adapter: sqlite3
+ timeout: 500
+
+sqlite2:
+ database: ":memory:"
+ adapter: sqlite2
+
+mysql:
+ adapter: mysql
+ username: root
+ password:
+ encoding: utf8
+ database: will_paginate_unittest
+
+postgres:
+ adapter: postgresql
+ username: mislav
+ password:
+ database: will_paginate_unittest
+ min_messages: warning
diff --git a/web/vendor/plugins/will_paginate/test/finder_test.rb b/web/vendor/…
@@ -0,0 +1,496 @@
+require 'helper'
+require 'lib/activerecord_test_case'
+
+require 'will_paginate'
+WillPaginate.enable_activerecord
+WillPaginate.enable_named_scope
+
+class FinderTest < ActiveRecordTestCase
+ fixtures :topics, :replies, :users, :projects, :developers_projects
+
+ def test_new_methods_presence
+ assert_respond_to_all Topic, %w(per_page paginate paginate_by_sql paginate…
+ end
+
+ def test_simple_paginate
+ assert_queries(1) do
+ entries = Topic.paginate :page => nil
+ assert_equal 1, entries.current_page
+ assert_equal 1, entries.total_pages
+ assert_equal 4, entries.size
+ end
+
+ assert_queries(2) do
+ entries = Topic.paginate :page => 2
+ assert_equal 1, entries.total_pages
+ assert entries.empty?
+ end
+ end
+
+ def test_parameter_api
+ # :page parameter in options is required!
+ assert_raise(ArgumentError){ Topic.paginate }
+ assert_raise(ArgumentError){ Topic.paginate({}) }
+
+ # explicit :all should not break anything
+ assert_equal Topic.paginate(:page => nil), Topic.paginate(:all, :page => 1)
+
+ # :count could be nil and we should still not cry
+ assert_nothing_raised { Topic.paginate :page => 1, :count => nil }
+ end
+
+ def test_counting_when_integer_has_length_method
+ Integer.module_eval { def length; to_s.length; end }
+ begin
+ assert_equal 2, 11.length
+ entries = Developer.paginate :page => 1, :per_page => 5
+ assert_equal 11, entries.total_entries
+ assert_equal 5, entries.size
+ assert_equal 3, entries.total_pages
+ ensure
+ begin
+ Integer.module_eval { remove_method :length }
+ rescue
+ end
+ end
+ end
+
+ def test_paginate_with_per_page
+ entries = Topic.paginate :page => 1, :per_page => 1
+ assert_equal 1, entries.size
+ assert_equal 4, entries.total_pages
+
+ # Developer class has explicit per_page at 10
+ entries = Developer.paginate :page => 1
+ assert_equal 10, entries.size
+ assert_equal 2, entries.total_pages
+
+ entries = Developer.paginate :page => 1, :per_page => 5
+ assert_equal 11, entries.total_entries
+ assert_equal 5, entries.size
+ assert_equal 3, entries.total_pages
+ end
+
+ def test_paginate_with_order
+ entries = Topic.paginate :page => 1, :order => 'created_at desc'
+ expected = [topics(:futurama), topics(:harvey_birdman), topics(:rails), to…
+ assert_equal expected, entries.to_a
+ assert_equal 1, entries.total_pages
+ end
+
+ def test_paginate_with_conditions
+ entries = Topic.paginate :page => 1, :conditions => ["created_at > ?", 30.…
+ expected = [topics(:rails), topics(:ar)]
+ assert_equal expected, entries.to_a
+ assert_equal 1, entries.total_pages
+ end
+
+ def test_paginate_with_include_and_conditions
+ entries = Topic.paginate \
+ :page => 1,
+ :include => :replies,
+ :conditions => "replies.content LIKE 'Bird%' ",
+ :per_page => 10
+
+ expected = Topic.find :all,
+ :include => 'replies',
+ :conditions => "replies.content LIKE 'Bird%' ",
+ :limit => 10
+
+ assert_equal expected, entries.to_a
+ assert_equal 1, entries.total_entries
+ end
+
+ def test_paginate_with_include_and_order
+ entries = nil
+ assert_queries(2) do
+ entries = Topic.paginate \
+ :page => 1,
+ :include => :replies,
+ :order => 'replies.created_at asc, topics.created_at asc',
+ :per_page => 10
+ end
+
+ expected = Topic.find :all,
+ :include => 'replies',
+ :order => 'replies.created_at asc, topics.created_at asc',
+ :limit => 10
+
+ assert_equal expected, entries.to_a
+ assert_equal 4, entries.total_entries
+ end
+
+ def test_paginate_associations_with_include
+ entries, project = nil, projects(:active_record)
+
+ assert_nothing_raised "THIS IS A BUG in Rails 1.2.3 that was fixed in [732…
+ "Please upgrade to a newer version of Rails." do
+ entries = project.topics.paginate \
+ :page => 1,
+ :include => :replies,
+ :conditions => "replies.content LIKE 'Nice%' ",
+ :per_page => 10
+ end
+
+ expected = Topic.find :all,
+ :include => 'replies',
+ :conditions => "project_id = #{project.id} AND replies.content LIKE 'Nic…
+ :limit => 10
+
+ assert_equal expected, entries.to_a
+ end
+
+ def test_paginate_associations
+ dhh = users :david
+ expected_name_ordered = [projects(:action_controller), projects(:active_re…
+ expected_id_ordered = [projects(:active_record), projects(:action_contro…
+
+ assert_queries(2) do
+ # with association-specified order
+ entries = dhh.projects.paginate(:page => 1)
+ assert_equal expected_name_ordered, entries
+ assert_equal 2, entries.total_entries
+ end
+
+ # with explicit order
+ entries = dhh.projects.paginate(:page => 1, :order => 'projects.id')
+ assert_equal expected_id_ordered, entries
+ assert_equal 2, entries.total_entries
+
+ assert_nothing_raised { dhh.projects.find(:all, :order => 'projects.id', :…
+ entries = dhh.projects.paginate(:page => 1, :order => 'projects.id', :per_…
+ assert_equal expected_id_ordered, entries
+
+ # has_many with implicit order
+ topic = Topic.find(1)
+ expected = [replies(:spam), replies(:witty_retort)]
+ assert_equal expected.map(&:id).sort, topic.replies.paginate(:page => 1).m…
+ assert_equal expected.reverse, topic.replies.paginate(:page => 1, :order =…
+ end
+
+ def test_paginate_association_extension
+ project = Project.find(:first)
+
+ assert_queries(2) do
+ entries = project.replies.paginate_recent :page => 1
+ assert_equal [replies(:brave)], entries
+ end
+ end
+
+ def test_paginate_with_joins
+ entries = nil
+
+ assert_queries(1) do
+ entries = Developer.paginate :page => 1,
+ :joins => 'LEFT JOIN developers_projects ON users.id…
+ :conditions => 'project_id = 1'
+ assert_equal 2, entries.size
+ developer_names = entries.map &:name
+ assert developer_names.include?('David')
+ assert developer_names.include?('Jamis')
+ end
+
+ assert_queries(1) do
+ expected = entries.to_a
+ entries = Developer.paginate :page => 1,
+ :joins => 'LEFT JOIN developers_projects ON users.id…
+ :conditions => 'project_id = 1', :count => { :select…
+ assert_equal expected, entries.to_a
+ assert_equal 2, entries.total_entries
+ end
+ end
+
+ def test_paginate_with_group
+ entries = nil
+ assert_queries(1) do
+ entries = Developer.paginate :page => 1, :per_page => 10,
+ :group => 'salary', :select => 'salary', :o…
+ end
+
+ expected = [ users(:david), users(:jamis), users(:dev_10), users(:poor_jam…
+ assert_equal expected, entries.map(&:salary)
+ end
+
+ def test_paginate_with_dynamic_finder
+ expected = [replies(:witty_retort), replies(:spam)]
+ assert_equal expected, Reply.paginate_by_topic_id(1, :page => 1, :order =>…
+
+ entries = Developer.paginate :conditions => { :salary => 100000 }, :page =…
+ assert_equal 8, entries.total_entries
+ assert_equal entries, Developer.paginate_by_salary(100000, :page => 1, :pe…
+
+ # dynamic finder + conditions
+ entries = Developer.paginate_by_salary(100000, :page => 1,
+ :conditions => ['id > ?', 6])
+ assert_equal 4, entries.total_entries
+ assert_equal (7..10).to_a, entries.map(&:id)
+
+ assert_raises NoMethodError do
+ Developer.paginate_by_inexistent_attribute 100000, :page => 1
+ end
+ end
+
+ def test_scoped_paginate
+ entries = Developer.with_poor_ones { Developer.paginate :page => 1 }
+
+ assert_equal 2, entries.size
+ assert_equal 2, entries.total_entries
+ end
+
+ ## named_scope ##
+
+ def test_paginate_in_named_scope
+ entries = Developer.poor.paginate :page => 1, :per_page => 1
+
+ assert_equal 1, entries.size
+ assert_equal 2, entries.total_entries
+ end
+
+ def test_paginate_in_named_scope_on_habtm_association
+ project = projects(:active_record)
+ assert_queries(2) do
+ entries = project.developers.poor.paginate :page => 1, :per_page => 1
+
+ assert_equal 1, entries.size, 'one developer should be found'
+ assert_equal 1, entries.total_entries, 'only one developer should be fou…
+ end
+ end
+
+ def test_paginate_in_named_scope_on_hmt_association
+ project = projects(:active_record)
+ expected = [replies(:brave)]
+
+ assert_queries(2) do
+ entries = project.replies.recent.paginate :page => 1, :per_page => 1
+ assert_equal expected, entries
+ assert_equal 1, entries.total_entries, 'only one reply should be found'
+ end
+ end
+
+ def test_paginate_in_named_scope_on_has_many_association
+ project = projects(:active_record)
+ expected = [topics(:ar)]
+
+ assert_queries(2) do
+ entries = project.topics.mentions_activerecord.paginate :page => 1, :per…
+ assert_equal expected, entries
+ assert_equal 1, entries.total_entries, 'only one topic should be found'
+ end
+ end
+
+ def test_named_scope_with_include
+ project = projects(:active_record)
+ entries = project.topics.with_replies_starting_with('AR ').paginate(:page …
+ assert_equal 1, entries.size
+ end
+
+ ## misc ##
+
+ def test_count_and_total_entries_options_are_mutually_exclusive
+ e = assert_raise ArgumentError do
+ Developer.paginate :page => 1, :count => {}, :total_entries => 1
+ end
+ assert_match /exclusive/, e.to_s
+ end
+
+ def test_readonly
+ assert_nothing_raised { Developer.paginate :readonly => true, :page => 1 }
+ end
+
+ # this functionality is temporarily removed
+ def xtest_pagination_defines_method
+ pager = "paginate_by_created_at"
+ assert !User.methods.include_method?(pager), "User methods should not incl…
+ # paginate!
+ assert 0, User.send(pager, nil, :page => 1).total_entries
+ # the paging finder should now be defined
+ assert User.methods.include_method?(pager), "`#{pager}` method should be d…
+ end
+
+ # Is this Rails 2.0? Find out by testing find_all which was removed in [6998]
+ unless ActiveRecord::Base.respond_to? :find_all
+ def test_paginate_array_of_ids
+ # AR finders also accept arrays of IDs
+ # (this was broken in Rails before [6912])
+ assert_queries(1) do
+ entries = Developer.paginate((1..8).to_a, :per_page => 3, :page => 2, …
+ assert_equal (4..6).to_a, entries.map(&:id)
+ assert_equal 8, entries.total_entries
+ end
+ end
+ end
+
+ uses_mocha 'internals' do
+ def test_implicit_all_with_dynamic_finders
+ Topic.expects(:find_all_by_foo).returns([])
+ Topic.expects(:count).returns(0)
+ Topic.paginate_by_foo :page => 2
+ end
+
+ def test_guessing_the_total_count
+ Topic.expects(:find).returns(Array.new(2))
+ Topic.expects(:count).never
+
+ entries = Topic.paginate :page => 2, :per_page => 4
+ assert_equal 6, entries.total_entries
+ end
+
+ def test_guessing_that_there_are_no_records
+ Topic.expects(:find).returns([])
+ Topic.expects(:count).never
+
+ entries = Topic.paginate :page => 1, :per_page => 4
+ assert_equal 0, entries.total_entries
+ end
+
+ def test_extra_parameters_stay_untouched
+ Topic.expects(:find).with(:all, {:foo => 'bar', :limit => 4, :offset => …
+ Topic.expects(:count).with({:foo => 'bar'}).returns(1)
+
+ Topic.paginate :foo => 'bar', :page => 1, :per_page => 4
+ end
+
+ def test_count_skips_select
+ Developer.stubs(:find).returns([])
+ Developer.expects(:count).with({}).returns(0)
+ Developer.paginate :select => 'salary', :page => 2
+ end
+
+ def test_count_select_when_distinct
+ Developer.stubs(:find).returns([])
+ Developer.expects(:count).with(:select => 'DISTINCT salary').returns(0)
+ Developer.paginate :select => 'DISTINCT salary', :page => 2
+ end
+
+ def test_count_with_scoped_select_when_distinct
+ Developer.stubs(:find).returns([])
+ Developer.expects(:count).with(:select => 'DISTINCT users.id').returns(0)
+ Developer.distinct.paginate :page => 2
+ end
+
+ def test_should_use_scoped_finders_if_present
+ # scope-out compatibility
+ Topic.expects(:find_best).returns(Array.new(5))
+ Topic.expects(:with_best).returns(1)
+
+ Topic.paginate_best :page => 1, :per_page => 4
+ end
+
+ def test_paginate_by_sql
+ sql = "SELECT * FROM users WHERE type = 'Developer' ORDER BY id"
+ entries = Developer.paginate_by_sql(sql, :page => 2, :per_page => 3)
+ assert_equal 11, entries.total_entries
+ assert_equal [users(:dev_4), users(:dev_5), users(:dev_6)], entries
+ end
+
+ def test_paginate_by_sql_respects_total_entries_setting
+ sql = "SELECT * FROM users"
+ entries = Developer.paginate_by_sql(sql, :page => 1, :total_entries => 9…
+ assert_equal 999, entries.total_entries
+ end
+
+ def test_paginate_by_sql_strips_order_by_when_counting
+ Developer.expects(:find_by_sql).returns([])
+ Developer.expects(:count_by_sql).with("SELECT COUNT(*) FROM (sql\n ) AS …
+
+ Developer.paginate_by_sql "sql\n ORDER\nby foo, bar, `baz` ASC", :page =…
+ end
+
+ # TODO: counts are still wrong
+ def test_ability_to_use_with_custom_finders
+ # acts_as_taggable defines find_tagged_with(tag, options)
+ Topic.expects(:find_tagged_with).with('will_paginate', :offset => 5, :li…
+ Topic.expects(:count).with({}).returns(0)
+
+ Topic.paginate_tagged_with 'will_paginate', :page => 2, :per_page => 5
+ end
+
+ def test_array_argument_doesnt_eliminate_count
+ ids = (1..8).to_a
+ Developer.expects(:find_all_by_id).returns([])
+ Developer.expects(:count).returns(0)
+
+ Developer.paginate_by_id(ids, :per_page => 3, :page => 2, :order => 'id')
+ end
+
+ def test_paginating_finder_doesnt_mangle_options
+ Developer.expects(:find).returns([])
+ options = { :page => 1, :per_page => 2, :foo => 'bar' }
+ options_before = options.dup
+
+ Developer.paginate(options)
+ assert_equal options_before, options
+ end
+
+ def test_paginate_by_sql_doesnt_change_original_query
+ query = 'SQL QUERY'
+ original_query = query.dup
+ Developer.expects(:find_by_sql).returns([])
+
+ Developer.paginate_by_sql query, :page => 1
+ assert_equal original_query, query
+ end
+
+ def test_paginated_each
+ collection = stub('collection', :size => 5, :empty? => false, :per_page …
+ collection.expects(:each).times(2).returns(collection)
+ last_collection = stub('collection', :size => 4, :empty? => false, :per_…
+ last_collection.expects(:each).returns(last_collection)
+
+ params = { :order => 'id', :total_entries => 0 }
+
+ Developer.expects(:paginate).with(params.merge(:page => 2)).returns(coll…
+ Developer.expects(:paginate).with(params.merge(:page => 3)).returns(coll…
+ Developer.expects(:paginate).with(params.merge(:page => 4)).returns(last…
+
+ assert_equal 14, Developer.paginated_each(:page => '2') { }
+ end
+
+ def test_paginated_each_with_named_scope
+ assert_equal 2, Developer.poor.paginated_each(:per_page => 1) {
+ assert_equal 11, Developer.count
+ }
+ end
+
+ # detect ActiveRecord 2.1
+ if ActiveRecord::Base.private_methods.include_method?(:references_eager_lo…
+ def test_removes_irrelevant_includes_in_count
+ Developer.expects(:find).returns([1])
+ Developer.expects(:count).with({}).returns(0)
+
+ Developer.paginate :page => 1, :per_page => 1, :include => :projects
+ end
+
+ def test_doesnt_remove_referenced_includes_in_count
+ Developer.expects(:find).returns([1])
+ Developer.expects(:count).with({ :include => :projects, :conditions =>…
+
+ Developer.paginate :page => 1, :per_page => 1,
+ :include => :projects, :conditions => 'projects.id > 2'
+ end
+ end
+
+ def test_paginate_from
+ result = Developer.paginate(:from => 'users', :page => 1, :per_page => 1)
+ assert_equal 1, result.size
+ end
+
+ def test_hmt_with_include
+ # ticket #220
+ reply = projects(:active_record).replies.find(:first, :order => 'replies…
+ assert_equal replies(:decisive), reply
+
+ # ticket #223
+ Project.find(1, :include => :replies)
+
+ # I cannot reproduce any of the failures from those reports :(
+ end
+
+ def test_hmt_with_uniq
+ project = Project.find(1)
+ result = project.unique_replies.paginate :page => 1, :per_page => 1,
+ :order => 'replies.id'
+ assert_equal replies(:decisive), result.first
+ end
+ end
+end
diff --git a/web/vendor/plugins/will_paginate/test/fixtures/admin.rb b/web/vend…
@@ -0,0 +1,3 @@
+class Admin < User
+ has_many :companies, :finder_sql => 'SELECT * FROM companies'
+end
diff --git a/web/vendor/plugins/will_paginate/test/fixtures/developer.rb b/web/…
@@ -0,0 +1,14 @@
+class Developer < User
+ has_and_belongs_to_many :projects, :include => :topics, :order => 'projects.…
+
+ def self.with_poor_ones(&block)
+ with_scope :find => { :conditions => ['salary <= ?', 80000], :order => 'sa…
+ yield
+ end
+ end
+
+ named_scope :distinct, :select => 'DISTINCT `users`.*'
+ named_scope :poor, :conditions => ['salary <= ?', 80000], :order => 'salary'
+
+ def self.per_page() 10 end
+end
diff --git a/web/vendor/plugins/will_paginate/test/fixtures/developers_projects…
@@ -0,0 +1,13 @@
+david_action_controller:
+ developer_id: 1
+ project_id: 2
+ joined_on: 2004-10-10
+
+david_active_record:
+ developer_id: 1
+ project_id: 1
+ joined_on: 2004-10-10
+
+jamis_active_record:
+ developer_id: 2
+ project_id: 1
+\ No newline at end of file
diff --git a/web/vendor/plugins/will_paginate/test/fixtures/project.rb b/web/ve…
@@ -0,0 +1,17 @@
+class Project < ActiveRecord::Base
+ has_and_belongs_to_many :developers, :uniq => true
+
+ has_many :topics
+ # :finder_sql => 'SELECT * FROM topics WHERE (topics.project_id = #{id})',
+ # :counter_sql => 'SELECT COUNT(*) FROM topics WHERE (topics.project_id = …
+
+ has_many :replies, :through => :topics do
+ def find_recent(params = {})
+ with_scope :find => { :conditions => ['replies.created_at > ?', 15.minut…
+ find :all, params
+ end
+ end
+ end
+
+ has_many :unique_replies, :through => :topics, :source => :replies, :uniq =>…
+end
diff --git a/web/vendor/plugins/will_paginate/test/fixtures/projects.yml b/web/…
@@ -0,0 +1,6 @@
+active_record:
+ id: 1
+ name: Active Record
+action_controller:
+ id: 2
+ name: Active Controller
diff --git a/web/vendor/plugins/will_paginate/test/fixtures/replies.yml b/web/v…
@@ -0,0 +1,29 @@
+witty_retort:
+ id: 1
+ topic_id: 1
+ content: Birdman is better!
+ created_at: <%= 6.hours.ago.to_s(:db) %>
+
+another:
+ id: 2
+ topic_id: 2
+ content: Nuh uh!
+ created_at: <%= 1.hour.ago.to_s(:db) %>
+
+spam:
+ id: 3
+ topic_id: 1
+ content: Nice site!
+ created_at: <%= 1.hour.ago.to_s(:db) %>
+
+decisive:
+ id: 4
+ topic_id: 4
+ content: "I'm getting to the bottom of this"
+ created_at: <%= 30.minutes.ago.to_s(:db) %>
+
+brave:
+ id: 5
+ topic_id: 4
+ content: "AR doesn't scare me a bit"
+ created_at: <%= 10.minutes.ago.to_s(:db) %>
diff --git a/web/vendor/plugins/will_paginate/test/fixtures/reply.rb b/web/vend…
@@ -0,0 +1,7 @@
+class Reply < ActiveRecord::Base
+ belongs_to :topic, :include => [:replies]
+
+ named_scope :recent, :conditions => ['replies.created_at > ?', 15.minutes.ag…
+
+ validates_presence_of :content
+end
diff --git a/web/vendor/plugins/will_paginate/test/fixtures/schema.rb b/web/ven…
@@ -0,0 +1,38 @@
+ActiveRecord::Schema.define do
+
+ create_table "users", :force => true do |t|
+ t.column "name", :text
+ t.column "salary", :integer, :default => 70000
+ t.column "created_at", :datetime
+ t.column "updated_at", :datetime
+ t.column "type", :text
+ end
+
+ create_table "projects", :force => true do |t|
+ t.column "name", :text
+ end
+
+ create_table "developers_projects", :id => false, :force => true do |t|
+ t.column "developer_id", :integer, :null => false
+ t.column "project_id", :integer, :null => false
+ t.column "joined_on", :date
+ t.column "access_level", :integer, :default => 1
+ end
+
+ create_table "topics", :force => true do |t|
+ t.column "project_id", :integer
+ t.column "title", :string
+ t.column "subtitle", :string
+ t.column "content", :text
+ t.column "created_at", :datetime
+ t.column "updated_at", :datetime
+ end
+
+ create_table "replies", :force => true do |t|
+ t.column "content", :text
+ t.column "created_at", :datetime
+ t.column "updated_at", :datetime
+ t.column "topic_id", :integer
+ end
+
+end
diff --git a/web/vendor/plugins/will_paginate/test/fixtures/topic.rb b/web/vend…
@@ -0,0 +1,12 @@
+class Topic < ActiveRecord::Base
+ has_many :replies, :dependent => :destroy, :order => 'replies.created_at DES…
+ belongs_to :project
+
+ named_scope :mentions_activerecord, :conditions => ['topics.title LIKE ?', '…
+
+ named_scope :with_replies_starting_with, lambda { |text|
+ { :conditions => "replies.content LIKE '#{text}%' ", :include => :replies…
+ }
+
+ def self.paginate_by_definition_in_class; end
+end
diff --git a/web/vendor/plugins/will_paginate/test/fixtures/topics.yml b/web/ve…
@@ -0,0 +1,30 @@
+futurama:
+ id: 1
+ title: Isnt futurama awesome?
+ subtitle: It really is, isnt it.
+ content: I like futurama
+ created_at: <%= 1.day.ago.to_s(:db) %>
+ updated_at:
+
+harvey_birdman:
+ id: 2
+ title: Harvey Birdman is the king of all men
+ subtitle: yup
+ content: He really is
+ created_at: <%= 2.hours.ago.to_s(:db) %>
+ updated_at:
+
+rails:
+ id: 3
+ project_id: 1
+ title: Rails is nice
+ subtitle: It makes me happy
+ content: except when I have to hack internals to fix pagination. even then r…
+ created_at: <%= 20.minutes.ago.to_s(:db) %>
+
+ar:
+ id: 4
+ project_id: 1
+ title: ActiveRecord sometimes freaks me out
+ content: "I mean, what's the deal with eager loading?"
+ created_at: <%= 15.minutes.ago.to_s(:db) %>
diff --git a/web/vendor/plugins/will_paginate/test/fixtures/user.rb b/web/vendo…
@@ -0,0 +1,2 @@
+class User < ActiveRecord::Base
+end
diff --git a/web/vendor/plugins/will_paginate/test/fixtures/users.yml b/web/ven…
@@ -0,0 +1,35 @@
+david:
+ id: 1
+ name: David
+ salary: 80000
+ type: Developer
+
+jamis:
+ id: 2
+ name: Jamis
+ salary: 150000
+ type: Developer
+
+<% for digit in 3..10 %>
+dev_<%= digit %>:
+ id: <%= digit %>
+ name: fixture_<%= digit %>
+ salary: 100000
+ type: Developer
+<% end %>
+
+poor_jamis:
+ id: 11
+ name: Jamis
+ salary: 9000
+ type: Developer
+
+admin:
+ id: 12
+ name: admin
+ type: Admin
+
+goofy:
+ id: 13
+ name: Goofy
+ type: Admin
diff --git a/web/vendor/plugins/will_paginate/test/helper.rb b/web/vendor/plugi…
@@ -0,0 +1,37 @@
+require 'test/unit'
+require 'rubygems'
+
+# gem install redgreen for colored test output
+begin require 'redgreen'; rescue LoadError; end
+
+require 'boot' unless defined?(ActiveRecord)
+
+class Test::Unit::TestCase
+ protected
+ def assert_respond_to_all object, methods
+ methods.each do |method|
+ [method.to_s, method.to_sym].each { |m| assert_respond_to object, m }
+ end
+ end
+
+ def collect_deprecations
+ old_behavior = WillPaginate::Deprecation.behavior
+ deprecations = []
+ WillPaginate::Deprecation.behavior = Proc.new do |message, callstack|
+ deprecations << message
+ end
+ result = yield
+ [result, deprecations]
+ ensure
+ WillPaginate::Deprecation.behavior = old_behavior
+ end
+end
+
+# Wrap tests that use Mocha and skip if unavailable.
+def uses_mocha(test_name)
+ require 'mocha'
+rescue LoadError
+ $stderr.puts "Skipping #{test_name} tests. `gem install mocha` and try again…
+else
+ yield
+end
diff --git a/web/vendor/plugins/will_paginate/test/lib/activerecord_test_case.r…
@@ -0,0 +1,43 @@
+require 'lib/activerecord_test_connector'
+
+class ActiveRecordTestCase < Test::Unit::TestCase
+ if defined?(ActiveSupport::Testing::SetupAndTeardown)
+ include ActiveSupport::Testing::SetupAndTeardown
+ end
+
+ if defined?(ActiveRecord::TestFixtures)
+ include ActiveRecord::TestFixtures
+ end
+ # Set our fixture path
+ if ActiveRecordTestConnector.able_to_connect
+ self.fixture_path = File.join(File.dirname(__FILE__), '..', 'fixtures')
+ self.use_transactional_fixtures = true
+ end
+
+ def self.fixtures(*args)
+ super if ActiveRecordTestConnector.connected
+ end
+
+ def run(*args)
+ super if ActiveRecordTestConnector.connected
+ end
+
+ # Default so Test::Unit::TestCase doesn't complain
+ def test_truth
+ end
+
+ protected
+
+ def assert_queries(num = 1)
+ $query_count = 0
+ yield
+ ensure
+ assert_equal num, $query_count, "#{$query_count} instead of #{num} queri…
+ end
+
+ def assert_no_queries(&block)
+ assert_queries(0, &block)
+ end
+end
+
+ActiveRecordTestConnector.setup
diff --git a/web/vendor/plugins/will_paginate/test/lib/activerecord_test_connec…
@@ -0,0 +1,76 @@
+require 'active_record'
+require 'active_record/version'
+require 'active_record/fixtures'
+
+class ActiveRecordTestConnector
+ cattr_accessor :able_to_connect
+ cattr_accessor :connected
+
+ FIXTURES_PATH = File.join(File.dirname(__FILE__), '..', 'fixtures')
+
+ # Set our defaults
+ self.connected = false
+ self.able_to_connect = true
+
+ def self.setup
+ unless self.connected || !self.able_to_connect
+ setup_connection
+ load_schema
+ add_load_path FIXTURES_PATH
+ self.connected = true
+ end
+ rescue Exception => e # errors from ActiveRecord setup
+ $stderr.puts "\nSkipping ActiveRecord tests: #{e}\n\n"
+ self.able_to_connect = false
+ end
+
+ private
+
+ def self.add_load_path(path)
+ dep = defined?(ActiveSupport::Dependencies) ? ActiveSupport::Dependencies …
+ autoload_paths = dep.respond_to?(:autoload_paths) ? dep.autoload_paths : d…
+ autoload_paths.unshift path
+ end
+
+ def self.setup_connection
+ db = ENV['DB'].blank?? 'sqlite3' : ENV['DB']
+
+ configurations = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'd…
+ raise "no configuration for '#{db}'" unless configurations.key? db
+ configuration = configurations[db]
+
+ ActiveRecord::Base.logger = Logger.new(STDOUT) if $0 == 'irb'
+ puts "using #{configuration['adapter']} adapter" unless ENV['DB'].blank?
+
+ gem 'sqlite3-ruby' if 'sqlite3' == db
+
+ ActiveRecord::Base.establish_connection(configuration)
+ ActiveRecord::Base.configurations = { db => configuration }
+ prepare ActiveRecord::Base.connection
+
+ unless Object.const_defined?(:QUOTED_TYPE)
+ Object.send :const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quot…
+ end
+ end
+
+ def self.load_schema
+ ActiveRecord::Base.silence do
+ ActiveRecord::Migration.verbose = false
+ load File.join(FIXTURES_PATH, 'schema.rb')
+ end
+ end
+
+ def self.prepare(conn)
+ class << conn
+ IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@…
+
+ def execute_with_counting(sql, name = nil, &block)
+ $query_count ||= 0
+ $query_count += 1 unless IGNORED_SQL.any? { |r| sql =~ r }
+ execute_without_counting(sql, name, &block)
+ end
+
+ alias_method_chain :execute, :counting
+ end
+ end
+end
diff --git a/web/vendor/plugins/will_paginate/test/lib/load_fixtures.rb b/web/v…
@@ -0,0 +1,11 @@
+require 'boot'
+require 'lib/activerecord_test_connector'
+
+# setup the connection
+ActiveRecordTestConnector.setup
+
+# load all fixtures
+Fixtures.create_fixtures(ActiveRecordTestConnector::FIXTURES_PATH, ActiveRecor…
+
+require 'will_paginate'
+WillPaginate.enable_activerecord
diff --git a/web/vendor/plugins/will_paginate/test/lib/view_test_process.rb b/w…
@@ -0,0 +1,179 @@
+require 'will_paginate/core_ext'
+require 'action_controller'
+require 'action_controller/test_process'
+
+require 'will_paginate'
+WillPaginate.enable_actionpack
+
+ActionController::Routing::Routes.draw do |map|
+ map.connect 'dummy/page/:page', :controller => 'dummy'
+ map.connect 'dummy/dots/page.:page', :controller => 'dummy', :action => 'dot…
+ map.connect 'ibocorp/:page', :controller => 'ibocorp',
+ :requirements => { :page => /\d+/ },
+ :defaults => { :page => 1 }
+
+ map.connect ':controller/:action/:id'
+end
+
+ActionController::Base.perform_caching = false
+
+class WillPaginate::ViewTestCase < Test::Unit::TestCase
+ if defined?(ActionController::TestCase::Assertions)
+ include ActionController::TestCase::Assertions
+ end
+ if defined?(ActiveSupport::Testing::Deprecation)
+ include ActiveSupport::Testing::Deprecation
+ end
+
+ def setup
+ super
+ @controller = DummyController.new
+ @request = @controller.request
+ @html_result = nil
+ @template = '<%= will_paginate collection, options %>'
+
+ @view = ActionView::Base.new
+ @view.assigns['controller'] = @controller
+ @view.assigns['_request'] = @request
+ @view.assigns['_params'] = @request.params
+ end
+
+ def test_no_complain; end
+
+ protected
+
+ def paginate(collection = {}, options = {}, &block)
+ if collection.instance_of? Hash
+ page_options = { :page => 1, :total_entries => 11, :per_page => 4 }.me…
+ collection = [1].paginate(page_options)
+ end
+
+ locals = { :collection => collection, :options => options }
+
+ unless @view.respond_to? :render_template
+ # Rails 2.2
+ @html_result = ActionView::InlineTemplate.new(@template).render(@view,…
+ else
+ if defined? ActionView::InlineTemplate
+ # Rails 2.1
+ args = [ ActionView::InlineTemplate.new(@view, @template, locals) ]
+ else
+ # older Rails versions
+ args = [nil, @template, nil, locals]
+ end
+
+ @html_result = @view.render_template(*args)
+ end
+
+ @html_document = HTML::Document.new(@html_result, true, false)
+
+ if block_given?
+ classname = options[:class] || WillPaginate::ViewHelpers.pagination_op…
+ assert_select("div.#{classname}", 1, 'no main DIV', &block)
+ end
+ end
+
+ def response_from_page_or_rjs
+ @html_document.root
+ end
+
+ def validate_page_numbers expected, links, param_name = :page
+ param_pattern = /\W#{CGI.escape(param_name.to_s)}=([^&]*)/
+
+ assert_equal(expected, links.map { |e|
+ e['href'] =~ param_pattern
+ $1 ? $1.to_i : $1
+ })
+ end
+
+ def assert_links_match pattern, links = nil, numbers = nil
+ links ||= assert_select 'div.pagination a[href]' do |elements|
+ elements
+ end
+
+ pages = [] if numbers
+
+ links.each do |el|
+ assert_match pattern, el['href']
+ if numbers
+ el['href'] =~ pattern
+ pages << ($1.nil?? nil : $1.to_i)
+ end
+ end
+
+ assert_equal numbers, pages, "page numbers don't match" if numbers
+ end
+
+ def assert_no_links_match pattern
+ assert_select 'div.pagination a[href]' do |elements|
+ elements.each do |el|
+ assert_no_match pattern, el['href']
+ end
+ end
+ end
+end
+
+class DummyRequest
+ attr_accessor :symbolized_path_parameters
+
+ def initialize
+ @get = true
+ @params = {}
+ @symbolized_path_parameters = { :controller => 'foo', :action => 'bar' }
+ end
+
+ def get?
+ @get
+ end
+
+ def post
+ @get = false
+ end
+
+ def relative_url_root
+ ''
+ end
+
+ def params(more = nil)
+ @params.update(more) if more
+ @params
+ end
+end
+
+class DummyController
+ attr_reader :request
+ attr_accessor :controller_name
+
+ def initialize
+ @request = DummyRequest.new
+ @url = ActionController::UrlRewriter.new(@request, @request.params)
+ end
+
+ def params
+ @request.params
+ end
+
+ def url_for(params)
+ @url.rewrite(params)
+ end
+end
+
+module HTML
+ Node.class_eval do
+ def inner_text
+ children.map(&:inner_text).join('')
+ end
+ end
+
+ Text.class_eval do
+ def inner_text
+ self.to_s
+ end
+ end
+
+ Tag.class_eval do
+ def inner_text
+ childless?? '' : super
+ end
+ end
+end
diff --git a/web/vendor/plugins/will_paginate/test/tasks.rake b/web/vendor/plug…
@@ -0,0 +1,59 @@
+require 'rake/testtask'
+
+desc 'Test the will_paginate plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+ t.libs << 'test'
+end
+
+# I want to specify environment variables at call time
+class EnvTestTask < Rake::TestTask
+ attr_accessor :env
+
+ def ruby(*args)
+ env.each { |key, value| ENV[key] = value } if env
+ super
+ env.keys.each { |key| ENV.delete key } if env
+ end
+end
+
+for configuration in %w( sqlite3 mysql postgres )
+ EnvTestTask.new("test_#{configuration}") do |t|
+ t.pattern = 'test/finder_test.rb'
+ t.verbose = true
+ t.env = { 'DB' => configuration }
+ t.libs << 'test'
+ end
+end
+
+task :test_databases => %w(test_mysql test_sqlite3 test_postgres)
+
+desc %{Test everything on SQLite3, MySQL and PostgreSQL}
+task :test_full => %w(test test_mysql test_postgres)
+
+desc %{Test everything with Rails 2.1.x, 2.0.x & 1.2.x gems}
+task :test_all do
+ all = Rake::Task['test_full']
+ versions = %w(2.3.2 2.2.2 2.1.0 2.0.4 1.2.6)
+ versions.each do |version|
+ ENV['RAILS_VERSION'] = "~> #{version}"
+ all.invoke
+ reset_invoked unless version == versions.last
+ end
+end
+
+def reset_invoked
+ %w( test_full test test_mysql test_postgres ).each do |name|
+ Rake::Task[name].instance_variable_set '@already_invoked', false
+ end
+end
+
+task :rcov do
+ excludes = %w( lib/will_paginate/named_scope*
+ lib/will_paginate/core_ext.rb
+ lib/will_paginate.rb
+ rails* )
+
+ system %[rcov -Itest:lib test/*.rb -x #{excludes.join(',')}]
+end
diff --git a/web/vendor/plugins/will_paginate/test/view_test.rb b/web/vendor/pl…
@@ -0,0 +1,373 @@
+require 'helper'
+require 'lib/view_test_process'
+
+class AdditionalLinkAttributesRenderer < WillPaginate::LinkRenderer
+ def initialize(link_attributes = nil)
+ super()
+ @additional_link_attributes = link_attributes || { :default => 'true' }
+ end
+
+ def page_link(page, text, attributes = {})
+ @template.link_to text, url_for(page), attributes.merge(@additional_link_a…
+ end
+end
+
+class ViewTest < WillPaginate::ViewTestCase
+
+ ## basic pagination ##
+
+ def test_will_paginate
+ paginate do |pagination|
+ assert_select 'a[href]', 3 do |elements|
+ validate_page_numbers [2,3,2], elements
+ assert_select elements.last, ':last-child', "Next &raquo;"
+ end
+ assert_select 'span', 2
+ assert_select 'span.disabled:first-child', '&laquo; Previous'
+ assert_select 'span.current', '1'
+ assert_equal '&laquo; Previous 1 2 3 Next &raquo;', pagination.first.inn…
+ end
+ end
+
+ def test_no_pagination_when_page_count_is_one
+ paginate :per_page => 30
+ assert_equal '', @html_result
+ end
+
+ def test_will_paginate_with_options
+ paginate({ :page => 2 },
+ :class => 'will_paginate', :previous_label => 'Prev', :next_label…
+ assert_select 'a[href]', 4 do |elements|
+ validate_page_numbers [1,1,3,3], elements
+ # test rel attribute values:
+ assert_select elements[1], 'a', '1' do |link|
+ assert_equal 'prev start', link.first['rel']
+ end
+ assert_select elements.first, 'a', "Prev" do |link|
+ assert_equal 'prev start', link.first['rel']
+ end
+ assert_select elements.last, 'a', "Next" do |link|
+ assert_equal 'next', link.first['rel']
+ end
+ end
+ assert_select 'span.current', '2'
+ end
+ end
+
+ def test_will_paginate_using_renderer_class
+ paginate({}, :renderer => AdditionalLinkAttributesRenderer) do
+ assert_select 'a[default=true]', 3
+ end
+ end
+
+ def test_will_paginate_using_renderer_instance
+ renderer = WillPaginate::LinkRenderer.new
+ renderer.gap_marker = '<span class="my-gap">~~</span>'
+
+ paginate({ :per_page => 2 }, :inner_window => 0, :outer_window => 0, :rend…
+ assert_select 'span.my-gap', '~~'
+ end
+
+ renderer = AdditionalLinkAttributesRenderer.new(:title => 'rendered')
+ paginate({}, :renderer => renderer) do
+ assert_select 'a[title=rendered]', 3
+ end
+ end
+
+ def test_prev_next_links_have_classnames
+ paginate do |pagination|
+ assert_select 'span.disabled.prev_page:first-child'
+ assert_select 'a.next_page[href]:last-child'
+ end
+ end
+
+ def test_prev_label_deprecated
+ assert_deprecated ':previous_label' do
+ paginate({ :page => 2 }, :prev_label => 'Deprecated') do
+ assert_select 'a[href]:first-child', 'Deprecated'
+ end
+ end
+ end
+
+ def test_full_output
+ paginate
+ expected = <<-HTML
+ <div class="pagination"><span class="disabled prev_page">&laquo; Previou…
+ <span class="current">1</span>
+ <a href="/foo/bar?page=2" rel="next">2</a>
+ <a href="/foo/bar?page=3">3</a>
+ <a href="/foo/bar?page=2" class="next_page" rel="next">Next &raquo;</a><…
+ HTML
+ expected.strip!.gsub!(/\s{2,}/, ' ')
+
+ assert_dom_equal expected, @html_result
+ end
+
+ def test_escaping_of_urls
+ paginate({:page => 1, :per_page => 1, :total_entries => 2},
+ :page_links => false, :params => { :tag => '<br>' })
+
+ assert_select 'a[href]', 1 do |links|
+ query = links.first['href'].split('?', 2)[1]
+ assert_equal %w(page=2 tag=%3Cbr%3E), query.split('&amp;').sort
+ end
+ end
+
+ ## advanced options for pagination ##
+
+ def test_will_paginate_without_container
+ paginate({}, :container => false)
+ assert_select 'div.pagination', 0, 'main DIV present when it shouldn\'t'
+ assert_select 'a[href]', 3
+ end
+
+ def test_will_paginate_without_page_links
+ paginate({ :page => 2 }, :page_links => false) do
+ assert_select 'a[href]', 2 do |elements|
+ validate_page_numbers [1,3], elements
+ end
+ end
+ end
+
+ def test_will_paginate_windows
+ paginate({ :page => 6, :per_page => 1 }, :inner_window => 1) do |paginatio…
+ assert_select 'a[href]', 8 do |elements|
+ validate_page_numbers [5,1,2,5,7,10,11,7], elements
+ assert_select elements.first, 'a', '&laquo; Previous'
+ assert_select elements.last, 'a', 'Next &raquo;'
+ end
+ assert_select 'span.current', '6'
+ assert_equal '&laquo; Previous 1 2 &hellip; 5 6 7 &hellip; 10 11 Next &r…
+ end
+ end
+
+ def test_will_paginate_eliminates_small_gaps
+ paginate({ :page => 6, :per_page => 1 }, :inner_window => 2) do
+ assert_select 'a[href]', 12 do |elements|
+ validate_page_numbers [5,1,2,3,4,5,7,8,9,10,11,7], elements
+ end
+ end
+ end
+
+ def test_container_id
+ paginate do |div|
+ assert_nil div.first['id']
+ end
+
+ # magic ID
+ paginate({}, :id => true) do |div|
+ assert_equal 'fixnums_pagination', div.first['id']
+ end
+
+ # explicit ID
+ paginate({}, :id => 'custom_id') do |div|
+ assert_equal 'custom_id', div.first['id']
+ end
+ end
+
+ ## other helpers ##
+
+ def test_paginated_section
+ @template = <<-ERB
+ <% paginated_section collection, options do %>
+ <%= content_tag :div, '', :id => "developers" %>
+ <% end %>
+ ERB
+
+ paginate
+ assert_select 'div.pagination', 2
+ assert_select 'div.pagination + div#developers', 1
+ end
+
+ def test_page_entries_info
+ @template = '<%= page_entries_info collection %>'
+ array = ('a'..'z').to_a
+
+ paginate array.paginate(:page => 2, :per_page => 5)
+ assert_equal %{Displaying strings <b>6&nbsp;-&nbsp;10</b> of <b>26</b> in …
+ @html_result
+
+ paginate array.paginate(:page => 7, :per_page => 4)
+ assert_equal %{Displaying strings <b>25&nbsp;-&nbsp;26</b> of <b>26</b> in…
+ @html_result
+ end
+
+ uses_mocha 'class name' do
+ def test_page_entries_info_with_longer_class_name
+ @template = '<%= page_entries_info collection %>'
+ collection = ('a'..'z').to_a.paginate
+ collection.first.stubs(:class).returns(mock('class', :name => 'ProjectTy…
+
+ paginate collection
+ assert @html_result.index('project types'), "expected <#{@html_result.in…
+ end
+ end
+
+ def test_page_entries_info_with_single_page_collection
+ @template = '<%= page_entries_info collection %>'
+
+ paginate(('a'..'d').to_a.paginate(:page => 1, :per_page => 5))
+ assert_equal %{Displaying <b>all 4</b> strings}, @html_result
+
+ paginate(['a'].paginate(:page => 1, :per_page => 5))
+ assert_equal %{Displaying <b>1</b> string}, @html_result
+
+ paginate([].paginate(:page => 1, :per_page => 5))
+ assert_equal %{No entries found}, @html_result
+ end
+
+ def test_page_entries_info_with_custom_entry_name
+ @template = '<%= page_entries_info collection, :entry_name => "author" %>'
+
+ entries = (1..20).to_a
+
+ paginate(entries.paginate(:page => 1, :per_page => 5))
+ assert_equal %{Displaying authors <b>1&nbsp;-&nbsp;5</b> of <b>20</b> in t…
+
+ paginate(entries.paginate(:page => 1, :per_page => 20))
+ assert_equal %{Displaying <b>all 20</b> authors}, @html_result
+
+ paginate(['a'].paginate(:page => 1, :per_page => 5))
+ assert_equal %{Displaying <b>1</b> author}, @html_result
+
+ paginate([].paginate(:page => 1, :per_page => 5))
+ assert_equal %{No authors found}, @html_result
+ end
+
+ ## parameter handling in page links ##
+
+ def test_will_paginate_preserves_parameters_on_get
+ @request.params :foo => { :bar => 'baz' }
+ paginate
+ assert_links_match /foo%5Bbar%5D=baz/
+ end
+
+ def test_will_paginate_doesnt_preserve_parameters_on_post
+ @request.post
+ @request.params :foo => 'bar'
+ paginate
+ assert_no_links_match /foo=bar/
+ end
+
+ def test_adding_additional_parameters
+ paginate({}, :params => { :foo => 'bar' })
+ assert_links_match /foo=bar/
+ end
+
+ def test_adding_anchor_parameter
+ paginate({}, :params => { :anchor => 'anchor' })
+ assert_links_match /#anchor$/
+ end
+
+ def test_removing_arbitrary_parameters
+ @request.params :foo => 'bar'
+ paginate({}, :params => { :foo => nil })
+ assert_no_links_match /foo=bar/
+ end
+
+ def test_adding_additional_route_parameters
+ paginate({}, :params => { :controller => 'baz', :action => 'list' })
+ assert_links_match %r{\Wbaz/list\W}
+ end
+
+ def test_will_paginate_with_custom_page_param
+ paginate({ :page => 2 }, :param_name => :developers_page) do
+ assert_select 'a[href]', 4 do |elements|
+ validate_page_numbers [1,1,3,3], elements, :developers_page
+ end
+ end
+ end
+
+ def test_will_paginate_with_atmark_url
+ @request.symbolized_path_parameters[:action] = "@tag"
+ renderer = WillPaginate::LinkRenderer.new
+
+ paginate({ :page => 1 }, :renderer=>renderer)
+ assert_links_match %r[/foo/@tag\?page=\d]
+ end
+
+ def test_complex_custom_page_param
+ @request.params :developers => { :page => 2 }
+
+ paginate({ :page => 2 }, :param_name => 'developers[page]') do
+ assert_select 'a[href]', 4 do |links|
+ assert_links_match /\?developers%5Bpage%5D=\d+$/, links
+ validate_page_numbers [1,1,3,3], links, 'developers[page]'
+ end
+ end
+ end
+
+ def test_custom_routing_page_param
+ @request.symbolized_path_parameters.update :controller => 'dummy', :action…
+ paginate :per_page => 2 do
+ assert_select 'a[href]', 6 do |links|
+ assert_links_match %r{/page/(\d+)$}, links, [2, 3, 4, 5, 6, 2]
+ end
+ end
+ end
+
+ def test_custom_routing_page_param_with_dot_separator
+ @request.symbolized_path_parameters.update :controller => 'dummy', :action…
+ paginate :per_page => 2 do
+ assert_select 'a[href]', 6 do |links|
+ assert_links_match %r{/page\.(\d+)$}, links, [2, 3, 4, 5, 6, 2]
+ end
+ end
+ end
+
+ def test_custom_routing_with_first_page_hidden
+ @request.symbolized_path_parameters.update :controller => 'ibocorp', :acti…
+ paginate :page => 2, :per_page => 2 do
+ assert_select 'a[href]', 7 do |links|
+ assert_links_match %r{/ibocorp(?:/(\d+))?$}, links, [nil, nil, 3, 4, 5…
+ end
+ end
+ end
+
+ ## internal hardcore stuff ##
+
+ class LegacyCollection < WillPaginate::Collection
+ alias :page_count :total_pages
+ undef :total_pages
+ end
+
+ def test_deprecation_notices_with_page_count
+ collection = LegacyCollection.new(1, 1, 2)
+
+ assert_deprecated collection.class.name do
+ paginate collection
+ end
+ end
+
+ uses_mocha 'view internals' do
+ def test_collection_name_can_be_guessed
+ collection = mock
+ collection.expects(:total_pages).returns(1)
+
+ @template = '<%= will_paginate options %>'
+ @controller.controller_name = 'developers'
+ @view.assigns['developers'] = collection
+
+ paginate(nil)
+ end
+ end
+
+ def test_inferred_collection_name_raises_error_when_nil
+ @template = '<%= will_paginate options %>'
+ @controller.controller_name = 'developers'
+
+ e = assert_raise ArgumentError do
+ paginate(nil)
+ end
+ assert e.message.include?('@developers')
+ end
+
+ if ActionController::Base.respond_to? :rescue_responses
+ # only on Rails 2
+ def test_rescue_response_hook_presence
+ assert_equal :not_found,
+ ActionController::Base.rescue_responses['WillPaginate::InvalidPage']
+ end
+ end
+
+end
diff --git a/web/vendor/plugins/will_paginate/will_paginate.gemspec b/web/vendo…
@@ -0,0 +1,22 @@
+# encoding: utf-8
+require File.expand_path('../lib/will_paginate/version', __FILE__)
+
+Gem::Specification.new do |gem|
+ gem.name = 'will_paginate'
+ gem.version = WillPaginate::VERSION::STRING
+ gem.date = Time.now.strftime('%Y-%m-%d')
+
+ gem.summary = "Pagination for Rails"
+ gem.description = "The will_paginate library provides a simple, yet powerful…
+
+ gem.authors = ['Mislav Marohnić', 'PJ Hyett']
+ gem.email = '[email protected]'
+ gem.homepage = 'http://github.com/mislav/will_paginate/wikis'
+
+ gem.rubyforge_project = nil
+ gem.has_rdoc = true
+ gem.rdoc_options = ['--main', 'README.rdoc', '--charset=UTF-8']
+ gem.extra_rdoc_files = ['README.rdoc', 'LICENSE', 'CHANGELOG.rdoc']
+
+ gem.files = Dir['Rakefile', '{bin,lib,rails,test,spec}/**/*', 'README*', 'LI…
+end
You are viewing proxied material from jay.scot. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.