| 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 ©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,'&').replace(/</g,'<').replace(/>/g,'>'… | |
| + } | |
| + | |
| + function unescapeHTML() { | |
| + return this.stripTags().replace(/</g,'<').replace(/>/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 <em>empty</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">…</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">« Previous</span> <span… | |
| +- pagination_no_page_links = '<span class="disabled prev_page">« 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 - 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">« Previous</span> <span class="curr… | |
| + </div> | |
| + <h2>Digg.com</h2> | |
| + <div class='digg_pagination'> | |
| + <span class="disabled prev_page">« Previous</span> <span class="curr… | |
| + </div> | |
| + <h2>Digg-style, no page links</h2> | |
| + <div class='digg_pagination'> | |
| + <span class="disabled prev_page">« Previous</span> <a href="./?page… | |
| + </div> | |
| + <p>Code that renders this:</p> | |
| + <pre> | |
| + <code><%= will_paginate @posts, :page_links => false %></code> | |
| + </pre> | |
| + <h2>Digg-style, extra content</h2> | |
| + <div class='digg_pagination'> | |
| + <div class='page_info'> | |
| + Displaying entries <b>1 - 6</b> of <b>180</b> in total | |
| + </div> | |
| + <span class="disabled prev_page">« Previous</span> <span class="curr… | |
| + </div> | |
| + <p>Code that renders this:</p> | |
| + <pre> | |
| + <code><div class="digg_pagination"> | |
| + <div clas="page_info"> | |
| + <%= page_entries_info @posts %> | |
| + </div> | |
| + <%= will_paginate @posts, :container => false %> | |
| + </div></code> | |
| + </pre> | |
| + <h2>Apple.com store</h2> | |
| + <div class='apple_pagination'> | |
| + <span class="disabled prev_page">« Previous</span> <span class="curr… | |
| + </div> | |
| + <h2>Flickr.com</h2> | |
| + <div class='flickr_pagination'> | |
| + <span class="disabled prev_page">« 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 => '« Previous', | |
| + :next_label => 'Next »', | |
| + :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 - %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">…</span> | |
| + attr_accessor :gap_marker | |
| + | |
| + def initialize | |
| + @gap_marker = '<span class="gap">…</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!((?:\?|&)#{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 »" | |
| + end | |
| + assert_select 'span', 2 | |
| + assert_select 'span.disabled:first-child', '« Previous' | |
| + assert_select 'span.current', '1' | |
| + assert_equal '« Previous 1 2 3 Next »', 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">« 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 »</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('&').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', '« Previous' | |
| + assert_select elements.last, 'a', 'Next »' | |
| + end | |
| + assert_select 'span.current', '6' | |
| + assert_equal '« Previous 1 2 … 5 6 7 … 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 - 10</b> of <b>26</b> in … | |
| + @html_result | |
| + | |
| + paginate array.paginate(:page => 7, :per_page => 4) | |
| + assert_equal %{Displaying strings <b>25 - 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 - 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 |