Changeset 39

Show
Ignore:
Timestamp:
03/20/06 02:09:26 (7 years ago)
Author:
dema
Message:

HUGE CHANGESET! UPGRADE TO RAILS 1.1 Release Candidate

ATTENTION: SCRIPT FILES ARE NAMED WITHOUT THE ".rb" EXTENSION NOW.

Added a "r.cmd" batch, so you can type "r script\server" to start the server on Windows or run any other script.

  • Updated RJB (Ruby Java Bridge) - still not compliant with Ruby 1.8.4, so stick with 1.8.2 for now.
  • Decoupled HyperDE and SemanticRecord? from other Rails components, so now it's much easier to keep Rails version up-to-date.
Location:
trunk
Files:
516 added
13 removed
24 modified

Legend:

Unmodified
Added
Removed
  • trunk/README

    r5 r39  
    11== Welcome to Rails 
    22 
    3 Rails is a web-application and persistance framework that includes everything 
     3Rails is a web-application and persistence framework that includes everything 
    44needed to create database-backed web-applications according to the 
    55Model-View-Control pattern of separation. This pattern splits the view (also 
    66called the presentation) into "dumb" templates that are primarily responsible 
    7 for inserting pre-build data in between HTML tags. The model contains the 
     7for inserting pre-built data in between HTML tags. The model contains the 
    88"smart" domain objects (such as Account, Product, Person, Post) that holds all 
    99the business logic and knows how to persist themselves to a database. The 
     
    1111Product, Show Post) by manipulating the model and directing data to the view. 
    1212 
    13 In Rails, the model is handled by what's called a object-relational mapping 
     13In Rails, the model is handled by what's called an object-relational mapping 
    1414layer entitled Active Record. This layer allows you to present the data from 
    1515database rows as objects and embellish these data objects with business logic 
     
    1717link:files/vendor/rails/activerecord/README.html. 
    1818 
    19 The controller and view is handled by the Action Pack, which handles both 
     19The controller and view are handled by the Action Pack, which handles both 
    2020layers by its two parts: Action View and Action Controller. These two layers 
    2121are bundled in a single package due to their heavy interdependence. This is 
     
    2626 
    2727 
    28 == Requirements 
    29  
    30 * Database and driver (MySQL, PostgreSQL, or SQLite) 
    31 * Rake[http://rake.rubyforge.org] for running tests and the generating documentation 
    32  
    33 == Optionals 
    34  
    35 * Apache 1.3.x or 2.x or lighttpd 1.3.11+ (or any FastCGI-capable webserver with a 
    36   mod_rewrite-like module) 
    37 * FastCGI (or mod_ruby) for better performance on Apache 
    38  
    3928== Getting started 
    4029 
    41 1. Run the WEBrick servlet: <tt>ruby script/server</tt> 
    42    (run with --help for options) 
     301. Run the WEBrick servlet: <tt>ruby script/server</tt> (run with --help for options) 
     31   ...or if you have lighttpd installed: <tt>ruby script/lighttpd</tt> (it's faster) 
    43322. Go to http://localhost:3000/ and get "Congratulations, you've put Ruby on Rails!" 
    44333. Follow the guidelines on the "Congratulations, you've put Ruby on Rails!" screen 
     
    6150 
    6251NOTE: Be sure that CGIs can be executed in that directory as well. So ExecCGI 
    63 should be on and ".cgi" should respond. All requests from 127.0.0.1 goes 
     52should be on and ".cgi" should respond. All requests from 127.0.0.1 go 
    6453through CGI, so no Apache restart is necessary for changes. All other requests 
    65 goes through FCGI (or mod_ruby) that requires restart to show changes. 
     54go through FCGI (or mod_ruby), which requires a restart to show changes. 
    6655 
    67  
    68 == Example for lighttpd conf (with FastCGI) 
    69  
    70   server.port = 8080 
    71   server.bind = "127.0.0.1" 
    72   # server.event-handler = "freebsd-kqueue" # needed on OS X 
    73    
    74   server.modules = ( "mod_rewrite", "mod_fastcgi" ) 
    75    
    76   url.rewrite = ( "^/$" => "index.html", "^([^.]+)$" => "$1.html" ) 
    77   server.error-handler-404 = "/dispatch.fcgi" 
    78    
    79   server.document-root = "/path/application/public" 
    80   server.errorlog      = "/path/application/log/server.log" 
    81    
    82   fastcgi.server = ( ".fcgi" => 
    83     ( "localhost" => 
    84         ( 
    85           "min-procs" => 1,  
    86           "max-procs" => 5, 
    87           "socket"   => "/tmp/application.fcgi.socket", 
    88           "bin-path" => "/path/application/public/dispatch.fcgi", 
    89           "bin-environment" => ( "RAILS_ENV" => "development" ) 
    90         ) 
    91     ) 
    92   ) 
    93    
    9456 
    9557== Debugging Rails 
     
    140102Here you'll have all parts of the application configured, just like it is when the 
    141103application is running. You can inspect domain models, change values, and save to the 
    142 database. Start the script without arguments will launch it in the development environment. 
     104database. Starting the script without arguments will launch it in the development environment. 
    143105Passing an argument will specify a different environment, like <tt>console production</tt>. 
    144106 
     
    156118app/models 
    157119  Holds models that should be named like post.rb. 
    158   Most models will descent from ActiveRecord::Base. 
     120  Most models will descend from ActiveRecord::Base. 
    159121   
    160122app/views 
    161123  Holds the template files for the view that should be named like 
    162   weblog/index.rhtml for the WeblogController#index action. All views uses eRuby 
     124  weblog/index.rhtml for the WeblogController#index action. All views use eRuby 
    163125  syntax. This directory can also be used to keep stylesheets, images, and so on 
    164126  that can be symlinked to public. 
     
    171133 
    172134components 
    173   Self-contained mini-applications that can bundle controllers, models, and views together. 
     135  Self-contained mini-applications that can bundle together controllers, models, and views. 
    174136 
    175137lib 
    176138  Application specific libraries. Basically, any kind of custom code that doesn't 
    177   belong controllers, models, or helpers. This directory is in the load path. 
     139  belong under controllers, models, or helpers. This directory is in the load path. 
    178140     
    179141public 
    180   The directory available for the web server. Contains sub-directories for images, stylesheets, 
     142  The directory available for the web server. Contains subdirectories for images, stylesheets, 
    181143  and javascripts. Also contains the dispatchers and the default HTML files. 
    182144 
     
    188150 
    189151vendor 
    190   External libraries that the application depend on. This directory is in the load path. 
     152  External libraries that the application depends on. Also includes the plugins subdirectory. 
     153  This directory is in the load path. 
  • trunk/Rakefile

    r5 r39  
     1# Add your own tasks in files placed in lib/tasks ending in .rake, 
     2# for example lib/tasks/switchtower.rake, and they will automatically be available to Rake. 
     3 
     4require(File.join(File.dirname(__FILE__), 'config', 'boot')) 
     5 
    16require 'rake' 
    27require 'rake/testtask' 
    38require 'rake/rdoctask' 
    49 
    5 $VERBOSE = nil 
    6 TEST_CHANGES_SINCE = Time.now - 600 
    7  
    8 desc "Run all the tests on a fresh test database" 
    9 task :default => [ :test_units, :test_functional ] 
    10  
    11  
    12 desc 'Require application environment.' 
    13 task :environment do 
    14   unless defined? RAILS_ROOT 
    15     require File.dirname(__FILE__) + '/config/environment' 
    16   end 
    17 end 
    18  
    19 desc "Generate API documentation, show coding stats" 
    20 task :doc => [ :appdoc, :stats ] 
    21  
    22  
    23 # Look up tests for recently modified sources. 
    24 def recent_tests(source_pattern, test_path, touched_since = 10.minutes.ago) 
    25   FileList[source_pattern].map do |path| 
    26     if File.mtime(path) > touched_since 
    27       test = "#{test_path}/#{File.basename(path, '.rb')}_test.rb" 
    28       test if File.exists?(test) 
    29     end 
    30   end.compact 
    31 end 
    32  
    33 desc 'Test recent changes.' 
    34 Rake::TestTask.new(:recent => [ :clone_structure_to_test ]) do |t| 
    35   since = TEST_CHANGES_SINCE 
    36   touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) > since } + 
    37     recent_tests('app/models/*.rb', 'test/unit', since) + 
    38     recent_tests('app/controllers/*.rb', 'test/functional', since) 
    39  
    40   t.libs << 'test' 
    41   t.verbose = true 
    42   t.test_files = touched.uniq 
    43 end 
    44 task :test_recent => [ :clone_structure_to_test ] 
    45  
    46 desc "Run the unit tests in test/unit" 
    47 Rake::TestTask.new("test_units") { |t| 
    48   t.libs << "test" 
    49   t.pattern = 'test/unit/**/*_test.rb' 
    50   t.verbose = true 
    51 } 
    52 task :test_units => [ :clone_structure_to_test ] 
    53  
    54 desc "Run the functional tests in test/functional" 
    55 Rake::TestTask.new("test_functional") { |t| 
    56   t.libs << "test" 
    57   t.pattern = 'test/functional/**/*_test.rb' 
    58   t.verbose = true 
    59 } 
    60 task :test_functional => [ :clone_structure_to_test ] 
    61  
    62 desc "Generate documentation for the application" 
    63 Rake::RDocTask.new("appdoc") { |rdoc| 
    64   rdoc.rdoc_dir = 'doc/app' 
    65   rdoc.title    = "Rails Application Documentation" 
    66   rdoc.options << '--line-numbers --inline-source' 
    67   rdoc.rdoc_files.include('doc/README_FOR_APP') 
    68   rdoc.rdoc_files.include('app/**/*.rb') 
    69 } 
    70  
    71 desc "Generate documentation for the Rails framework" 
    72 Rake::RDocTask.new("apidoc") { |rdoc| 
    73   rdoc.rdoc_dir = 'doc/api' 
    74   rdoc.template = "#{ENV['template']}.rb" if ENV['template'] 
    75   rdoc.title    = "Rails Framework Documentation" 
    76   rdoc.options << '--line-numbers --inline-source' 
    77   rdoc.rdoc_files.include('README') 
    78   rdoc.rdoc_files.include('CHANGELOG') 
    79   rdoc.rdoc_files.include('vendor/rails/railties/CHANGELOG') 
    80   rdoc.rdoc_files.include('vendor/rails/railties/MIT-LICENSE') 
    81   rdoc.rdoc_files.include('vendor/rails/activerecord/README') 
    82   rdoc.rdoc_files.include('vendor/rails/activerecord/CHANGELOG') 
    83   rdoc.rdoc_files.include('vendor/rails/activerecord/lib/active_record/**/*.rb') 
    84   rdoc.rdoc_files.exclude('vendor/rails/activerecord/lib/active_record/vendor/*') 
    85   rdoc.rdoc_files.include('vendor/rails/actionpack/README') 
    86   rdoc.rdoc_files.include('vendor/rails/actionpack/CHANGELOG') 
    87   rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_controller/**/*.rb') 
    88   rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_view/**/*.rb') 
    89   rdoc.rdoc_files.include('vendor/rails/actionmailer/README') 
    90   rdoc.rdoc_files.include('vendor/rails/actionmailer/CHANGELOG') 
    91   rdoc.rdoc_files.include('vendor/rails/actionmailer/lib/action_mailer/base.rb') 
    92   rdoc.rdoc_files.include('vendor/rails/actionwebservice/README') 
    93   rdoc.rdoc_files.include('vendor/rails/actionwebservice/CHANGELOG') 
    94   rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service.rb') 
    95   rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/*.rb') 
    96   rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/api/*.rb') 
    97   rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/client/*.rb') 
    98   rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/container/*.rb') 
    99   rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/dispatcher/*.rb') 
    100   rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/protocol/*.rb') 
    101   rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/support/*.rb') 
    102   rdoc.rdoc_files.include('vendor/rails/activesupport/README') 
    103   rdoc.rdoc_files.include('vendor/rails/activesupport/CHANGELOG') 
    104   rdoc.rdoc_files.include('vendor/rails/activesupport/lib/active_support/**/*.rb') 
    105 } 
    106  
    107 desc "Report code statistics (KLOCs, etc) from the application" 
    108 task :stats => [ :environment ] do 
    109   require 'code_statistics' 
    110   CodeStatistics.new( 
    111     ["Helpers", "app/helpers"],  
    112     ["Controllers", "app/controllers"],  
    113     ["APIs", "app/apis"], 
    114     ["Components", "components"], 
    115     ["Functionals", "test/functional"], 
    116     ["Models", "app/models"], 
    117     ["Units", "test/unit"] 
    118   ).to_s 
    119 end 
    120  
    121 desc "Recreate the test databases from the development structure" 
    122 task :clone_structure_to_test => [ :db_structure_dump, :purge_test_database ] do 
    123   abcs = ActiveRecord::Base.configurations 
    124   case abcs["test"]["adapter"] 
    125     when  "mysql" 
    126       ActiveRecord::Base.establish_connection(:test) 
    127       ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0') 
    128       IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table| 
    129         ActiveRecord::Base.connection.execute(table) 
    130       end 
    131     when "postgresql" 
    132       ENV['PGHOST']     = abcs["test"]["host"] if abcs["test"]["host"] 
    133       ENV['PGPORT']     = abcs["test"]["port"].to_s if abcs["test"]["port"] 
    134       ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] 
    135       `psql -U "#{abcs["test"]["username"]}" -f db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}` 
    136     when "sqlite", "sqlite3" 
    137       `#{abcs[RAILS_ENV]["adapter"]} #{abcs["test"]["dbfile"]} < db/#{RAILS_ENV}_structure.sql` 
    138     when "sqlserver" 
    139       `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` 
    140     else  
    141       raise "Unknown database adapter '#{abcs["test"]["adapter"]}'" 
    142   end 
    143 end 
    144  
    145 desc "Dump the database structure to a SQL file" 
    146 task :db_structure_dump => :environment do 
    147   abcs = ActiveRecord::Base.configurations 
    148   case abcs[RAILS_ENV]["adapter"]  
    149     when "mysql" 
    150       ActiveRecord::Base.establish_connection(abcs[RAILS_ENV]) 
    151       File.open("db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump } 
    152     when "postgresql" 
    153       ENV['PGHOST']     = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"] 
    154       ENV['PGPORT']     = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"] 
    155       ENV['PGPASSWORD'] = abcs[RAILS_ENV]["password"].to_s if abcs[RAILS_ENV]["password"] 
    156       `pg_dump -U "#{abcs[RAILS_ENV]["username"]}" -s -x -O -f db/#{RAILS_ENV}_structure.sql #{abcs[RAILS_ENV]["database"]}` 
    157     when "sqlite", "sqlite3" 
    158       `#{abcs[RAILS_ENV]["adapter"]} #{abcs[RAILS_ENV]["dbfile"]} .schema > db/#{RAILS_ENV}_structure.sql` 
    159     when "sqlserver" 
    160       `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /f db\\#{RAILS_ENV}_structure.sql /q /A /r` 
    161       `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /F db\ /q /A /r` 
    162     else  
    163       raise "Unknown database adapter '#{abcs["test"]["adapter"]}'" 
    164   end 
    165 end 
    166  
    167 desc "Empty the test database" 
    168 task :purge_test_database => :environment do 
    169   abcs = ActiveRecord::Base.configurations 
    170   case abcs["test"]["adapter"] 
    171     when "mysql" 
    172       ActiveRecord::Base.establish_connection(:test) 
    173       ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"]) 
    174     when "postgresql" 
    175       ENV['PGHOST']     = abcs["test"]["host"] if abcs["test"]["host"] 
    176       ENV['PGPORT']     = abcs["test"]["port"].to_s if abcs["test"]["port"] 
    177       ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] 
    178       `dropdb -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}` 
    179       `createdb -T template0 -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}` 
    180     when "sqlite","sqlite3" 
    181       File.delete(abcs["test"]["dbfile"]) if File.exist?(abcs["test"]["dbfile"]) 
    182     when "sqlserver" 
    183       dropfkscript = "#{abcs["test"]["host"]}.#{abcs["test"]["database"]}.DP1".gsub(/\\/,'-') 
    184       `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{dropfkscript}` 
    185       `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` 
    186     else  
    187       raise "Unknown database adapter '#{abcs["test"]["adapter"]}'" 
    188   end 
    189 end 
    190  
    191 desc "Clears all *.log files in log/" 
    192 task :clear_logs => :environment do 
    193   FileList["log/*.log"].each do |log_file| 
    194     f = File.open(log_file, "w") 
    195     f.close 
    196   end 
    197 end 
    198  
    199 desc "Migrate the database according to the migrate scripts in db/migrate (only supported on PG/MySQL). A specific version can be targetted with VERSION=x" 
    200 task :migrate => :environment do 
    201   ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/db/migrate/', ENV["VERSION"] ? ENV["VERSION"].to_i : nil) 
    202 end 
     10require 'tasks/rails' 
  • trunk/app/controllers/navigation_controller.rb

    r16 r39  
    2222      redirect_to :action => action_for_landmark(lm),  
    2323                  :id => lm.target_id,  
    24                   :params => { "params" => lm.params_values }  
     24                  :params => { :p => lm.params_values }  
    2525    else 
    2626      redirect_to :controller => "nav_class"              
     
    3131    id = @params["id"] 
    3232    id ? @index = Index.find(id) : @index = Index.find_first 
    33     params = @params["params"]  
     33    params = @params[:p]  
    3434    @index.entries(params ? params.split(",") : nil) 
    3535    @page_title = "#{@index.name}" 
     
    4242    id ? @context = Context.find(id) : @context = Context.find_first 
    4343    @context.position = position.to_i 
    44     params = @params["params"] 
     44    params = @params[:p] 
    4545    @context.nodes(params ? params.split(",") : nil) 
    4646     
     
    131131   
    132132  def set_return_to_current 
    133     self.return_to = url_for :params => { "params" => @params["params"]}      
     133    self.return_to = url_for :params => { :p => @params[:p]}      
    134134  end 
    135135 
     
    149149    bc_cookie = @cookies["breadcrumb"] 
    150150    @breadcrumb = Breadcrumb.new(bc_cookie) 
    151     @breadcrumb.add(action_name, @params["id"], @params["params"], @page_title) 
     151    @breadcrumb.add(action_name, @params["id"], @params[:p], @page_title) 
    152152  end 
    153153   
  • trunk/app/helpers/hyperde/breadcrumb.rb

    r1 r39  
    1818                  @label = view.truncate(bc["title"]) 
    1919                  @url = view.url_for(:action => bc["action"], :id => bc["id"],  
    20                                                                            :params => params_for_anchor(bc["params"])) 
     20                                                                           :params => params_for_anchor(bc[:p])) 
    2121                  @ahref = view.link_to(@label, :action => bc["action"], :id => bc["id"],  
    22                                                                                    :params => params_for_anchor(bc["params"])) 
     22                                                                                   :params => params_for_anchor(bc[:p])) 
    2323          end 
    2424  end 
     
    3737  def add(action, id, params, title) 
    3838    last = @entries.last     
    39     new = { "action" => action, "id" => id, "params" => params, "title" => title } 
     39    new = { "action" => action, "id" => id, :p => params, "title" => title } 
    4040    if (strip(new) == strip(last)) 
    4141      @entries.pop  
     
    5858  def serialize 
    5959    values = [] 
    60     @entries.each { |e| values << [e["action"], e["id"], e["params"], e["title"]].join('&') } 
     60    @entries.each { |e| values << [e["action"], e["id"], e[:p], e["title"]].join('&') } 
    6161    values 
    6262  end 
     
    6666                  serialized_bc.each do |c| 
    6767                          data = c.split("&") 
    68                           hash = { "action" => data[0], "id" => data[1], "params" => data[2] && data[2].length > 0 ? data[2] : nil, "title" => data[3] } 
     68                          hash = { "action" => data[0], "id" => data[1], :p => data[2] && data[2].length > 0 ? data[2] : nil, "title" => data[3] } 
    6969                          @entries << hash 
    7070                        end 
  • trunk/app/helpers/hyperde/context.rb

    r1 r39  
    121121            @url = view.url_for(:controller => "navigation", :action => "context", 
    122122                           :id => "#{Context.full_id(context, @position)}", 
    123                            :params => params_for_anchor(view.params["params"])) 
     123                           :params => params_for_anchor(view.params[:p])) 
    124124               
    125125            @ahref = view.link_to(@label, :controller => "navigation", :action => "context", 
    126126                           :id => "#{Context.full_id(context, @position)}", 
    127                            :params => params_for_anchor(view.params["params"])) 
     127                           :params => params_for_anchor(view.params[:p])) 
    128128                end 
    129129 
  • trunk/app/helpers/hyperde/node.rb

    r19 r39  
    8585      options[:url] ||= { :action => "context", :op => op_name,  
    8686                          :view => options[:view],  
    87                           :params => params_for_anchor(view.params["params"]) } 
     87                          :params => params_for_anchor(view.params[:p]) } 
    8888      options[:with] ||= "Form.serialize(document.forms[0])" 
    8989      options[:complete] ||= "window.location.reload()" unless options[:update] 
  • trunk/app/helpers/hyperde_helper.rb

    r1 r39  
    151151                  ret = [] 
    152152                  params.each { |p| ret << p if p } if params 
    153                   { "params" => ret.length == 0 ? nil : ret.join(',') } 
     153                  { :p => ret.length == 0 ? nil : ret.join(',') } 
    154154          end 
    155155 
  • trunk/app/helpers/navigation_helper.rb

    r1 r39  
    55    for op_name, op in node.nav_operations 
    66      buffer << "<span class=operation>&nbsp;[" 
    7       buffer << link_to(op_name, :id => @params["id"], :params => { "params" => @params["params"], "operation" => op_name }) 
     7      buffer << link_to(op_name, :id => @params["id"], :params => { :p => @params[:p], "operation" => op_name }) 
    88      buffer << "]&nbsp;</span>" 
    99    end 
     
    9393          ret = [] 
    9494          params.each { |p| ret << p if p } if params 
    95           { "params" => ret.length == 0 ? nil : ret.join(',') } 
     95          { :p => ret.length == 0 ? nil : ret.join(',') } 
    9696        end 
    9797         
     
    162162        def render_context_index(context) 
    163163          buffer = "" 
    164     buffer << render_index(context.index, @params["params"], context.position, 9, 20) 
     164    buffer << render_index(context.index, @params[:p], context.position, 9, 20) 
    165165                buffer 
    166166        end 
     
    188188                              :controller => "navigation", :action => "context",  
    189189                              :id => "#{Context.full_id(context,context.position-1)}",  
    190                               :params => params_for_anchor(@params["params"]) ) 
     190                              :params => params_for_anchor(@params[:p]) ) 
    191191                else 
    192192                  buffer << "&nbsp;" 
     
    199199                buffer = "<span id=next_link>" 
    200200          if !context.on_last? 
    201                 buffer << link_to("#{truncate(context.next.label)} >>", :controller => "navigation", :action => "context", :id => "#{Context.full_id(context,context.position+1)}", :params => params_for_anchor(@params["params"]) ) 
     201                buffer << link_to("#{truncate(context.next.label)} >>", :controller => "navigation", :action => "context", :id => "#{Context.full_id(context,context.position+1)}", :params => params_for_anchor(@params[:p]) ) 
    202202    else 
    203203           buffer << "&nbsp;" 
     
    228228            buffer << separator if separator 
    229229            buffer << "<span class=breadcrumb_item>" 
    230             buffer << link_to(truncate(bc["title"]), :action => bc["action"], :id => bc["id"], :params => params_for_anchor(bc["params"])) 
     230            buffer << link_to(truncate(bc["title"]), :action => bc["action"], :id => bc["id"], :params => params_for_anchor(bc[:p])) 
    231231            buffer << "</span>" 
    232232                end      
     
    259259                                                          :action => @controller.action_name, \ 
    260260                                                          :id => @params["id"], \ 
    261                                                           :params => { "params" => @params["params"] } ) } ) 
     261                                                          :params => { :p => @params[:p] } ) } ) 
    262262            buffer << "]</span>" 
    263263                end 
  • trunk/app/models/node.rb

    r28 r39  
    156156  end 
    157157 
    158   alias read_std_attribute read_attribute 
    159  
    160158  def read_attribute(attr_name) 
    161     val = read_std_attribute(attr_name) 
     159    val = super(attr_name) 
    162160    if val.nil?  
    163161      attr_def = nav_attributes[attr_name] 
  • trunk/config/environment.rb

    r28 r39  
     1# Be sure to restart your web server when you modify this file. 
     2 
     3# Uncomment below to force Rails into production mode when  
     4# you don't control web/app server and can't set it the proper way 
     5# ENV['RAILS_ENV'] ||= 'production' 
     6 
     7# Bootstrap the Rails environment, frameworks, and default configuration 
     8require File.join(File.dirname(__FILE__), 'boot') 
     9 
     10Rails::Initializer.run do |config| 
     11  # Settings in config/environments/* take precedence those specified here 
     12   
     13  # Skip frameworks you're not going to use 
     14  config.frameworks -= [ :action_web_service, :action_mailer, :active_record ] 
     15 
     16  # Add additional load paths for your own custom dirs 
     17  config.load_paths += %W( #{RAILS_ROOT}/vendor/semanticrecord/lib ) 
     18 
     19  # Force all environments to use the same logger level  
     20  # (by default production uses :info, the others :debug) 
     21  # config.log_level = :debug 
     22 
     23  # Use the database for sessions instead of the file system 
     24  # (create the session table with 'rake create_sessions_table') 
     25  # config.action_controller.session_store = :active_record_store 
     26 
     27  # Enable page/fragment caching by setting a file-based store 
     28  # (remember to create the caching directory and make it readable to the application) 
     29  # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache" 
     30 
     31  # Activate observers that should always be running 
     32  # config.active_record.observers = :cacher, :garbage_collector 
     33 
     34  # Make Active Record use UTC-base instead of local time 
     35  # config.active_record.default_timezone = :utc 
     36   
     37  # Use Active Record's schema dumper instead of SQL when creating the test database 
     38  # (enables use of different database adapters for development and test environments) 
     39  # config.active_record.schema_format = :ruby 
     40 
     41  # See Rails::Configuration for more options 
     42end 
     43 
     44# Add new inflection rules using the following format  
     45# (all these examples are active by default): 
     46# Inflector.inflections do |inflect| 
     47#   inflect.plural /^(ox)$/i, '\1en' 
     48#   inflect.singular /^(ox)en/i, '\1' 
     49#   inflect.irregular 'person', 'people' 
     50#   inflect.uncountable %w( fish sheep ) 
     51# end 
     52 
     53# Include your application configuration below 
    154$KCODE = 'u' 
    255require 'jcode' 
    3  
    4 # Load the Rails framework and configure your application. 
    5 # You can include your own configuration at the end of this file. 
    6 # 
    7 # Be sure to restart your webserver when you modify this file. 
    8  
    9 # The path to the root directory of your application. 
    10 RAILS_ROOT = File.join(File.dirname(__FILE__), '..') 
    11  
    12 # The environment your application is currently running.  Don't set 
    13 # this here; put it in your webserver's configuration as the RAILS_ENV 
    14 # environment variable instead. 
    15 # 
    16 # See config/environments/*.rb for environment-specific configuration. 
    17 RAILS_ENV  = ENV['RAILS_ENV'] || 'development' 
    18  
    19  
    20 # Load the Rails framework.  Mock classes for testing come first. 
    21 ADDITIONAL_LOAD_PATHS = ["#{RAILS_ROOT}/test/mocks/#{RAILS_ENV}"] 
    22  
    23 # Then model subdirectories. 
    24 ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/app/models/[_a-z]*"]) 
    25 ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/components/[_a-z]*"]) 
    26  
    27 # Followed by the standard includes. 
    28 ADDITIONAL_LOAD_PATHS.concat %w( 
    29   app  
    30   app/models  
    31   app/controllers  
    32   app/helpers  
    33   app/apis  
    34   components  
    35   config  
    36   lib  
    37   vendor  
    38   vendor/railties 
    39   vendor/railties/lib 
    40   vendor/actionpack/lib 
    41   vendor/activesupport/lib 
    42   vendor/semanticrecord/lib 
    43 ).map { |dir| "#{RAILS_ROOT}/#{dir}" }.select { |dir| File.directory?(dir) } 
    44  
    45 # Prepend to $LOAD_PATH 
    46 ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } 
    47  
    48 require 'active_support' 
    4956require 'semantic_record' 
    50 require 'action_controller' 
    51  
    52 # Environment-specific configuration. 
    53 require_dependency "environments/#{RAILS_ENV}" 
    54 SemanticRecord::Base.configurations = File.open("#{RAILS_ROOT}/config/database.yml") { |f| YAML::load(f) } 
    55  
    56 # Configure defaults if the included environment did not. 
    57 begin 
    58   RAILS_DEFAULT_LOGGER = Logger.new("#{RAILS_ROOT}/log/#{RAILS_ENV}.log") 
    59   RAILS_DEFAULT_LOGGER.level = (RAILS_ENV == 'production' ? Logger::INFO : Logger::DEBUG) 
    60 rescue StandardError 
    61   RAILS_DEFAULT_LOGGER = Logger.new(STDERR) 
    62   RAILS_DEFAULT_LOGGER.level = Logger::WARN 
    63   RAILS_DEFAULT_LOGGER.warn( 
    64     "Rails Error: Unable to access log file. Please ensure that log/#{RAILS_ENV}.log exists and is chmod 0666. " + 
    65     "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed." 
    66   ) 
    67 end 
    68  
    69 [SemanticRecord, ActionController].each { |mod| mod::Base.logger ||= RAILS_DEFAULT_LOGGER } 
    70 [ActionController].each { |mod| mod::Base.template_root ||= "#{RAILS_ROOT}/app/views/" } 
    71  
    72 # Set up routes. 
    73 ActionController::Routing::Routes.reload 
    74  
    75 Controllers = Dependencies::LoadingModule.root( 
    76   File.join(RAILS_ROOT, 'app', 'controllers'), 
    77   File.join(RAILS_ROOT, 'components') 
    78 ) 
    79  
    80 # Include your app's configuration here: 
    8157require 'query_builder' 
    8258Dir["#{RAILS_ROOT}/app/models/**.rb"].each { |model| require File.basename(model, ".rb") } 
     59SemanticRecord::Base.logger = RAILS_DEFAULT_LOGGER 
     60SemanticRecord::Base.configurations = File.open("#{RAILS_ROOT}/config/database.yml") { |f| YAML::load(f) } 
    8361SemanticRecord::Base.save_schema 
    8462NavModelGenerator.new.generate 
  • trunk/config/environments/development.rb

    r5 r39  
     1# Settings specified here will take precedence over those in config/environment.rb 
     2 
    13# In the development environment your application's code is reloaded on 
    24# every request.  This slows down response time but is perfect for development 
    35# since you don't have to restart the webserver when you make code changes. 
     6config.cache_classes     = false 
    47 
    58# Log error messages when you accidentally call methods on nil. 
    6 require 'active_support/whiny_nil' 
     9config.whiny_nils        = true 
    710 
    8 # Reload code; show full error reports; disable caching. 
    9 Dependencies.mechanism                             = :require 
    10 ActionController::Base.consider_all_requests_local = true 
    11 ActionController::Base.perform_caching             = false 
     11# Enable the breakpoint server that script/breakpointer connects to 
     12config.breakpoint_server = true 
    1213 
    13 # The breakpoint server port that script/breakpointer connects to. 
    14 BREAKPOINT_SERVER_PORT = 42531 
     14# Show full error reports and disable caching 
     15config.action_controller.consider_all_requests_local = true 
     16config.action_controller.perform_caching             = false 
     17 
     18# Don't care if the mailer can't send 
     19config.action_mailer.raise_delivery_errors = false 
  • trunk/config/environments/production.rb

    r5 r39  
     1# Settings specified here will take precedence over those in config/environment.rb 
     2 
    13# The production environment is meant for finished, "live" apps. 
    2 # Code is not reloaded between requests, full error reports are disabled, 
    3 # and caching is turned on. 
     4# Code is not reloaded between requests 
     5config.cache_classes = true 
    46 
    5 # Don't reload code; don't show full error reports; enable caching. 
    6 Dependencies.mechanism                             = :require 
    7 ActionController::Base.consider_all_requests_local = false 
    8 ActionController::Base.perform_caching             = true 
     7# Use a different logger for distributed setups 
     8# config.logger        = SyslogLogger.new 
     9 
     10 
     11# Full error reports are disabled and caching is turned on 
     12config.action_controller.consider_all_requests_local = false 
     13config.action_controller.perform_caching             = true 
     14 
     15# Enable serving of images, stylesheets, and javascripts from an asset server 
     16# config.action_controller.asset_host                  = "http://assets.example.com" 
     17 
     18# Disable delivery errors if you bad email addresses should just be ignored 
     19# config.action_mailer.raise_delivery_errors = false 
  • trunk/config/environments/test.rb

    r5 r39  
     1# Settings specified here will take precedence over those in config/environment.rb 
     2 
    13# The test environment is used exclusively to run your application's 
    24# test suite.  You never need to work with it otherwise.  Remember that 
    35# your test database is "scratch space" for the test suite and is wiped 
    46# and recreated between test runs.  Don't rely on the data there! 
     7config.cache_classes = true 
    58 
    69# Log error messages when you accidentally call methods on nil. 
    7 require 'active_support/whiny_nil' 
     10config.whiny_nils    = true 
    811 
    9 # Don't reload code; show full error reports; disable caching. 
    10 Dependencies.mechanism                             = :require 
    11 ActionController::Base.consider_all_requests_local = true 
    12 ActionController::Base.perform_caching             = false 
     12# Show full error reports and disable caching 
     13config.action_controller.consider_all_requests_local = true 
     14config.action_controller.perform_caching             = false 
    1315 
    1416# Tell ActionMailer not to deliver emails to the real world. 
    1517# The :test delivery method accumulates sent emails in the 
    1618# ActionMailer::Base.deliveries array. 
    17 ActionMailer::Base.delivery_method                = :test 
     19config.action_mailer.delivery_method = :test 
  • trunk/config/routes.rb

    r5 r39  
    1111  map.connect '', :controller => "navigation" 
    1212 
     13  # Allow downloading Web Service WSDL as a file with an extension 
     14  # instead of a file named 'wsdl' 
     15  map.connect ':controller/service.wsdl', :action => 'wsdl' 
    1316 
    1417  # Install the default route as the lowest priority. 
  • trunk/lib/model_loader.rb

    r9 r39  
    100100      ctx["params"] ||= [] 
    101101      define_parameters(ctx["params"]) do |p_name, p_class| 
     102        @stdout.puts "Context Parameter: #{p_name}" 
    102103        context.parameters.create("name" => p_name, "nav_class" => p_class) 
    103104      end 
     
    121122      idx["params"] ||= [] 
    122123      define_parameters(idx["params"]) do |p_name, p_class| 
     124        @stdout.puts "Index Parameter: #{p_name}" 
    123125        index.parameters.create("name" => p_name, "nav_class" => p_class) 
    124126      end 
  • trunk/public/.htaccess

    r5 r39  
    1919#   RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] 
    2020RewriteEngine On 
     21 
     22# If your Rails application is accessed via an Alias directive, 
     23# then you MUST also set the RewriteBase in this htaccess file. 
     24# 
     25# Example: 
     26#   Alias /myrailsapp /path/to/myrailsapp/public 
     27#   RewriteBase /myrailsapp 
     28 
    2129RewriteRule ^$ index.html [QSA] 
    2230RewriteRule ^([^.]+)$ $1.html [QSA] 
  • trunk/public/javascripts/controls.js

    r5 r39  
    11// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 
    22//           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) 
     3//           (c) 2005 Jon Tirsen (http://www.tirsen.com) 
     4// Contributors: 
     5//  Richard Livsey 
     6//  Rahul Bhargava 
     7//  Rob Wills 
    38//  
    4 // Permission is hereby granted, free of charge, to any person obtaining 
    5 // a copy of this software and associated documentation files (the 
    6 // "Software"), to deal in the Software without restriction, including 
    7 // without limitation the rights to use, copy, modify, merge, publish, 
    8 // distribute, sublicense, and/or sell copies of the Software, and to 
    9 // permit persons to whom the Software is furnished to do so, subject to 
    10 // the following conditions: 
    11 //  
    12 // The above copyright notice and this permission notice shall be 
    13 // included in all copies or substantial portions of the Software. 
    14 //  
    15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
    16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
    17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
    18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
    19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
    20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
    21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
    22  
    23 Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { 
    24   var children = $(element).childNodes; 
    25   var text     = ""; 
    26   var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i"); 
    27    
    28   for (var i = 0; i < children.length; i++) { 
    29     if(children[i].nodeType==3) { 
    30       text+=children[i].nodeValue; 
    31     } else { 
    32       if((!children[i].className.match(classtest)) && children[i].hasChildNodes()) 
    33         text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass); 
    34     } 
    35   } 
    36    
    37   return text; 
    38 } 
     9// See scriptaculous.js for full license. 
    3910 
    4011// Autocompleter.Base handles all the autocompletion functionality  
     
    4718// the text inside the monitored textbox changes. This method  
    4819// should get the text for which to provide autocompletion by 
    49 // invoking this.getEntry(), NOT by directly accessing 
     20// invoking this.getToken(), NOT by directly accessing 
    5021// this.element.value. This is to allow incremental tokenized 
    5122// autocompletion. Specific auto-completion logic (AJAX, etc) 
     
    5829// will incrementally autocomplete with a comma as the token. 
    5930// Additionally, ',' in the above example can be replaced with 
    60 // a token array, e.g. { tokens: new Array (',', '\n') } which 
     31// a token array, e.g. { tokens: [',', '\n'] } which 
    6132// enables autocompletion on multiple tokens. This is most  
    6233// useful when one of the tokens is \n (a newline), as it  
     
    6637Autocompleter.Base = function() {}; 
    6738Autocompleter.Base.prototype = { 
    68   base_initialize: function(element, update, options) { 
     39  baseInitialize: function(element, update, options) { 
    6940    this.element     = $(element);  
    7041    this.update      = $(update);   
    71     this.has_focus   = false;  
     42    this.hasFocus    = false;  
    7243    this.changed     = false;  
    7344    this.active      = false;  
    7445    this.index       = 0;      
    75     this.entry_count = 0; 
     46    this.entryCount = 0; 
    7647 
    7748    if (this.setOptions) 
    7849      this.setOptions(options); 
    7950    else 
    80       this.options = {} 
    81       
    82     this.options.tokens       = this.options.tokens || new Array(); 
     51      this.options = options || {}; 
     52 
     53    this.options.paramName    = this.options.paramName || this.element.name; 
     54    this.options.tokens       = this.options.tokens || []; 
    8355    this.options.frequency    = this.options.frequency || 0.4; 
    84     this.options.min_chars    = this.options.min_chars || 1; 
     56    this.options.minChars     = this.options.minChars || 1; 
    8557    this.options.onShow       = this.options.onShow ||  
    8658    function(element, update){  
    8759      if(!update.style.position || update.style.position=='absolute') { 
    8860        update.style.position = 'absolute'; 
    89           var offsets = Position.cumulativeOffset(element); 
    90           update.style.left = offsets[0] + 'px'; 
    91           update.style.top  = (offsets[1] + element.offsetHeight) + 'px'; 
    92           update.style.width = element.offsetWidth + 'px'; 
     61        Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight}); 
    9362      } 
    94       new Effect.Appear(update,{duration:0.15}); 
     63      Effect.Appear(update,{duration:0.15}); 
    9564    }; 
    9665    this.options.onHide = this.options.onHide ||  
    9766    function(element, update){ new Effect.Fade(update,{duration:0.15}) }; 
    98      
    99     if(this.options.indicator) 
    100       this.indicator = $(this.options.indicator); 
    10167 
    10268    if (typeof(this.options.tokens) == 'string')  
    10369      this.options.tokens = new Array(this.options.tokens); 
    104         
     70 
    10571    this.observer = null; 
    10672     
     73    this.element.setAttribute('autocomplete','off'); 
     74 
    10775    Element.hide(this.update); 
    108      
     76 
    10977    Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); 
    11078    Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); 
     
    11280 
    11381  show: function() { 
    114     if(this.update.style.display=='none') this.options.onShow(this.element, this.update); 
    115     if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') { 
     82    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); 
     83    if(!this.iefix &&  
     84      (navigator.appVersion.indexOf('MSIE')>0) && 
     85      (navigator.userAgent.indexOf('Opera')<0) && 
     86      (Element.getStyle(this.update, 'position')=='absolute')) { 
    11687      new Insertion.After(this.update,  
    11788       '<iframe id="' + this.update.id + '_iefix" '+ 
    118        'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + 
     89       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + 
    11990       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); 
    12091      this.iefix = $(this.update.id+'_iefix'); 
    12192    } 
    122     if(this.iefix) { 
    123       Position.clone(this.update, this.iefix); 
    124       this.iefix.style.zIndex = 1; 
    125       this.update.style.zIndex = 2; 
    126       Element.show(this.iefix); 
    127     } 
    128   }, 
    129    
     93    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); 
     94  }, 
     95   
     96  fixIEOverlapping: function() { 
     97    Position.clone(this.update, this.iefix); 
     98    this.iefix.style.zIndex = 1; 
     99    this.update.style.zIndex = 2; 
     100    Element.show(this.iefix); 
     101  }, 
     102 
    130103  hide: function() { 
    131     if(this.update.style.display=='') this.options.onHide(this.element, this.update); 
     104    this.stopIndicator(); 
     105    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); 
    132106    if(this.iefix) Element.hide(this.iefix); 
    133107  }, 
    134    
     108 
    135109  startIndicator: function() { 
    136     if(this.indicator) Element.show(this.indicator); 
    137   }, 
    138    
     110    if(this.options.indicator) Element.show(this.options.indicator); 
     111  }, 
     112 
    139113  stopIndicator: function() { 
    140     if(this.indicator) Element.hide(this.indicator); 
     114    if(this.options.indicator) Element.hide(this.options.indicator); 
    141115  }, 
    142116 
     
    146120       case Event.KEY_TAB: 
    147121       case Event.KEY_RETURN: 
    148          this.select_entry(); 
     122         this.selectEntry(); 
    149123         Event.stop(event); 
    150124       case Event.KEY_ESC: 
    151125         this.hide(); 
    152126         this.active = false; 
     127         Event.stop(event); 
    153128         return; 
    154129       case Event.KEY_LEFT: 
     
    156131         return; 
    157132       case Event.KEY_UP: 
    158          this.mark_previous(); 
     133         this.markPrevious(); 
    159134         this.render(); 
    160135         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); 
    161136         return; 
    162137       case Event.KEY_DOWN: 
    163          this.mark_next(); 
     138         this.markNext(); 
    164139         this.render(); 
    165140         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); 
     
    169144      if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)  
    170145        return; 
    171      
     146 
    172147    this.changed = true; 
    173     this.has_focus = true; 
    174      
     148    this.hasFocus = true; 
     149 
    175150    if(this.observer) clearTimeout(this.observer); 
    176151      this.observer =  
    177152        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); 
    178153  }, 
    179    
     154 
    180155  onHover: function(event) { 
    181156    var element = Event.findElement(event, 'LI'); 
     
    191166    var element = Event.findElement(event, 'LI'); 
    192167    this.index = element.autocompleteIndex; 
    193     this.select_entry(); 
    194     Event.stop(event); 
     168    this.selectEntry(); 
     169    this.hide(); 
    195170  }, 
    196171   
     
    198173    // needed to make click events working 
    199174    setTimeout(this.hide.bind(this), 250); 
    200     this.has_focus = false; 
     175    this.hasFocus = false; 
    201176    this.active = false;      
    202177  },  
    203178   
    204179  render: function() { 
    205     if(this.entry_count > 0) { 
    206       for (var i = 0; i < this.entry_count; i++) 
     180    if(this.entryCount > 0) { 
     181      for (var i = 0; i < this.entryCount; i++) 
    207182        this.index==i ?  
    208           Element.addClassName(this.get_entry(i),"selected") :  
    209           Element.removeClassName(this.get_entry(i),"selected"); 
     183          Element.addClassName(this.getEntry(i),"selected") :  
     184          Element.removeClassName(this.getEntry(i),"selected"); 
    210185         
    211       if(this.has_focus) {  
    212         if(this.get_current_entry().scrollIntoView)  
    213           this.get_current_entry().scrollIntoView(false); 
    214          
     186      if(this.hasFocus) {  
    215187        this.show(); 
    216188        this.active = true; 
    217189      } 
    218     } else this.hide(); 
    219   }, 
    220    
    221   mark_previous: function() { 
     190    } else { 
     191      this.active = false; 
     192      this.hide(); 
     193    } 
     194  }, 
     195   
     196  markPrevious: function() { 
    222197    if(this.index > 0) this.index-- 
    223       else this.index = this.entry_count-1; 
    224   }, 
    225    
    226   mark_next: function() { 
    227     if(this.index < this.entry_count-1) this.index++ 
     198      else this.index = this.entryCount-1; 
     199  }, 
     200   
     201  markNext: function() { 
     202    if(this.index < this.entryCount-1) this.index++ 
    228203      else this.index = 0; 
    229204  }, 
    230205   
    231   get_entry: function(index) { 
     206  getEntry: function(index) { 
    232207    return this.update.firstChild.childNodes[index]; 
    233208  }, 
    234209   
    235   get_current_entry: function() { 
    236     return this.get_entry(this.index); 
    237   }, 
    238    
    239   select_entry: function() { 
     210  getCurrentEntry: function() { 
     211    return this.getEntry(this.index); 
     212  }, 
     213   
     214  selectEntry: function() { 
    240215    this.active = false; 
    241     value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML(); 
    242     this.updateElement(value); 
    243     this.element.focus(); 
    244   }, 
    245  
    246   updateElement: function(value) { 
    247     var last_token_pos = this.findLastToken(); 
    248     if (last_token_pos != -1) { 
    249       var new_value = this.element.value.substr(0, last_token_pos + 1); 
    250       var whitespace = this.element.value.substr(last_token_pos + 1).match(/^\s+/); 
     216    this.updateElement(this.getCurrentEntry()); 
     217  }, 
     218 
     219  updateElement: function(selectedElement) { 
     220    if (this.options.updateElement) { 
     221      this.options.updateElement(selectedElement); 
     222      return; 
     223    } 
     224 
     225    var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); 
     226    var lastTokenPos = this.findLastToken(); 
     227    if (lastTokenPos != -1) { 
     228      var newValue = this.element.value.substr(0, lastTokenPos + 1); 
     229      var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); 
    251230      if (whitespace) 
    252         new_value += whitespace[0]; 
    253       this.element.value = new_value + value; 
     231        newValue += whitespace[0]; 
     232      this.element.value = newValue + value; 
    254233    } else { 
    255234      this.element.value = value; 
    256     }  
    257   }, 
    258    
     235    } 
     236    this.element.focus(); 
     237     
     238    if (this.options.afterUpdateElement) 
     239      this.options.afterUpdateElement(this.element, selectedElement); 
     240  }, 
     241 
    259242  updateChoices: function(choices) { 
    260     if(!this.changed && this.has_focus) { 
     243    if(!this.changed && this.hasFocus) { 
    261244      this.update.innerHTML = choices; 
    262245      Element.cleanWhitespace(this.update); 
     
    264247 
    265248      if(this.update.firstChild && this.update.firstChild.childNodes) { 
    266         this.entry_count =  
     249        this.entryCount =  
    267250          this.update.firstChild.childNodes.length; 
    268         for (var i = 0; i < this.entry_count; i++) { 
    269           entry = this.get_entry(i); 
     251        for (var i = 0; i < this.entryCount; i++) { 
     252          var entry = this.getEntry(i); 
    270253          entry.autocompleteIndex = i; 
    271254          this.addObservers(entry); 
    272255        } 
    273256      } else {  
    274         this.entry_count = 0; 
     257        this.entryCount = 0; 
    275258      } 
    276        
     259 
    277260      this.stopIndicator(); 
    278        
     261 
    279262      this.index = 0; 
    280263      this.render(); 
     
    289272  onObserverEvent: function() { 
    290273    this.changed = false;    
    291     if(this.getEntry().length>=this.options.min_chars) { 
     274    if(this.getToken().length>=this.options.minChars) { 
    292275      this.startIndicator(); 
    293276      this.getUpdatedChoices(); 
     
    298281  }, 
    299282 
    300   getEntry: function() { 
    301     var token_pos = this.findLastToken(); 
    302     if (token_pos != -1) 
    303       var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,''); 
     283  getToken: function() { 
     284    var tokenPos = this.findLastToken(); 
     285    if (tokenPos != -1) 
     286      var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); 
    304287    else 
    305288      var ret = this.element.value; 
    306      
     289 
    307290    return /\n/.test(ret) ? '' : ret; 
    308291  }, 
    309292 
    310293  findLastToken: function() { 
    311     var last_token_pos = -1; 
     294    var lastTokenPos = -1; 
    312295 
    313296    for (var i=0; i<this.options.tokens.length; i++) { 
    314       var this_token_pos = this.element.value.lastIndexOf(this.options.tokens[i]); 
    315       if (this_token_pos > last_token_pos) 
    316         last_token_pos = this_token_pos; 
    317     } 
    318     return last_token_pos; 
     297      var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]); 
     298      if (thisTokenPos > lastTokenPos) 
     299        lastTokenPos = thisTokenPos; 
     300    } 
     301    return lastTokenPos; 
    319302  } 
    320303} 
    321304 
    322305Ajax.Autocompleter = Class.create(); 
    323 Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(),  
    324 Object.extend(new Ajax.Base(), { 
     306Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { 
    325307  initialize: function(element, update, url, options) { 
    326           this.base_initialize(element, update, options); 
     308          this.baseInitialize(element, update, options); 
    327309    this.options.asynchronous  = true; 
    328     this.options.onComplete    = this.onComplete.bind(this) 
    329     this.options.method        = 'post'; 
     310    this.options.onComplete    = this.onComplete.bind(this); 
    330311    this.options.defaultParams = this.options.parameters || null; 
    331312    this.url                   = url; 
    332313  }, 
    333    
     314 
    334315  getUpdatedChoices: function() { 
    335     entry = encodeURIComponent(this.element.name) + '=' +  
    336       encodeURIComponent(this.getEntry()); 
    337        
     316    entry = encodeURIComponent(this.options.paramName) + '=' +  
     317      encodeURIComponent(this.getToken()); 
     318 
    338319    this.options.parameters = this.options.callback ? 
    339320      this.options.callback(this.element, entry) : entry; 
    340          
     321 
    341322    if(this.options.defaultParams)  
    342323      this.options.parameters += '&' + this.options.defaultParams; 
    343      
     324 
    344325    new Ajax.Request(this.url, this.options); 
    345326  }, 
    346    
     327 
    347328  onComplete: function(request) { 
    348329    this.updateChoices(request.responseText); 
    349330  } 
    350331 
    351 })); 
     332}); 
    352333 
    353334// The local array autocompleter. Used when you'd prefer to 
     
    363344// - choices - How many autocompletion choices to offer 
    364345// 
    365 // - partial_search - If false, the autocompleter will match entered 
     346// - partialSearch - If false, the autocompleter will match entered 
    366347//                    text only at the beginning of strings in the  
    367348//                    autocomplete array. Defaults to true, which will 
     
    369350//                    strings in the autocomplete array. If you want to 
    370351//                    search anywhere in the string, additionally set 
    371 //                    the option full_search to true (default: off). 
    372 // 
    373 // - full_search - Search anywhere in autocomplete array strings. 
    374 // 
    375 // - partial_chars - How many characters to enter before triggering 
    376 //                   a partial match (unlike min_chars, which defines 
     352//                    the option fullSearch to true (default: off). 
     353// 
     354// - fullSsearch - Search anywhere in autocomplete array strings. 
     355// 
     356// - partialChars - How many characters to enter before triggering 
     357//                   a partial match (unlike minChars, which defines 
    377358//                   how many characters are required to do any match 
    378359//                   at all). Defaults to 2. 
    379360// 
    380 // - ignore_case - Whether to ignore case when autocompleting. 
     361// - ignoreCase - Whether to ignore case when autocompleting. 
    381362//                 Defaults to true. 
    382363// 
     
    389370Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { 
    390371  initialize: function(element, update, array, options) { 
    391     this.base_initialize(element, update, options); 
     372    this.baseInitialize(element, update, options); 
    392373    this.options.array = array; 
    393374  }, 
     
    400381    this.options = Object.extend({ 
    401382      choices: 10, 
    402       partial_search: true, 
    403       partial_chars: 2, 
    404       ignore_case: true, 
    405       full_search: false, 
     383      partialSearch: true, 
     384      partialChars: 2, 
     385      ignoreCase: true, 
     386      fullSearch: false, 
    406387      selector: function(instance) { 
    407         var ret       = new Array(); // Beginning matches 
    408         var partial   = new Array(); // Inside matches 
    409         var entry     = instance.getEntry(); 
     388        var ret       = []; // Beginning matches 
     389        var partial   = []; // Inside matches 
     390        var entry     = instance.getToken(); 
    410391        var count     = 0; 
    411          
     392 
    412393        for (var i = 0; i < instance.options.array.length &&   
    413             ret.length < instance.options.choices ; i++) {  
     394          ret.length < instance.options.choices ; i++) {  
     395 
    414396          var elem = instance.options.array[i]; 
    415           var found_pos = instance.options.ignore_case ?  
     397          var foundPos = instance.options.ignoreCase ?  
    416398            elem.toLowerCase().indexOf(entry.toLowerCase()) :  
    417399            elem.indexOf(entry); 
    418400 
    419           while (found_pos != -1) { 
    420             if (found_pos == 0 && elem.length != entry.length) {  
     401          while (foundPos != -1) { 
     402            if (foundPos == 0 && elem.length != entry.length) {  
    421403              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +  
    422404                elem.substr(entry.length) + "</li>"); 
    423405              break; 
    424             } else if (entry.length >= instance.options.partial_chars &&  
    425               instance.options.partial_search && found_pos != -1) { 
    426               if (instance.options.full_search || /\s/.test(elem.substr(found_pos-1,1))) { 
    427                 partial.push("<li>" + elem.substr(0, found_pos) + "<strong>" + 
    428                   elem.substr(found_pos, entry.length) + "</strong>" + elem.substr( 
    429                   found_pos + entry.length) + "</li>"); 
     406            } else if (entry.length >= instance.options.partialChars &&  
     407              instance.options.partialSearch && foundPos != -1) { 
     408              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { 
     409                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" + 
     410                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr( 
     411                  foundPos + entry.length) + "</li>"); 
    430412                break; 
    431413              } 
    432414            } 
    433415 
    434             found_pos = instance.options.ignore_case ?  
    435               elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) :  
    436               elem.indexOf(entry, found_pos + 1); 
     416            foundPos = instance.options.ignoreCase ?  
     417              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :  
     418              elem.indexOf(entry, foundPos + 1); 
    437419 
    438420          } 
     
    445427  } 
    446428}); 
     429 
     430// AJAX in-place editor 
     431// 
     432// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor 
     433 
     434// Use this if you notice weird scrolling problems on some browsers, 
     435// the DOM might be a bit confused when this gets called so do this 
     436// waits 1 ms (with setTimeout) until it does the activation 
     437Field.scrollFreeActivate = function(field) { 
     438  setTimeout(function() { 
     439    Field.activate(field); 
     440  }, 1); 
     441} 
     442 
     443Ajax.InPlaceEditor = Class.create(); 
     444Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; 
     445Ajax.InPlaceEditor.prototype = { 
     446  initialize: function(element, url, options) { 
     447    this.url = url; 
     448    this.element = $(element); 
     449 
     450    this.options = Object.extend({ 
     451      okText: "ok", 
     452      cancelText: "cancel", 
     453      savingText: "Saving...", 
     454      clickToEditText: "Click to edit", 
     455      okText: "ok", 
     456      rows: 1, 
     457      onComplete: function(transport, element) { 
     458        new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); 
     459      }, 
     460      onFailure: function(transport) { 
     461        alert("Error communicating with the server: " + transport.responseText.stripTags()); 
     462      }, 
     463      callback: function(form) { 
     464        return Form.serialize(form); 
     465      }, 
     466      handleLineBreaks: true, 
     467      loadingText: 'Loading...', 
     468      savingClassName: 'inplaceeditor-saving', 
     469      loadingClassName: 'inplaceeditor-loading', 
     470      formClassName: 'inplaceeditor-form', 
     471      highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, 
     472      highlightendcolor: "#FFFFFF", 
     473      externalControl:  null, 
     474      ajaxOptions: {} 
     475    }, options || {}); 
     476 
     477    if(!this.options.formId && this.element.id) { 
     478      this.options.formId = this.element.id + "-inplaceeditor"; 
     479      if ($(this.options.formId)) { 
     480        // there's already a form with that name, don't specify an id 
     481        this.options.formId = null; 
     482      } 
     483    } 
     484     
     485    if (this.options.externalControl) { 
     486      this.options.externalControl = $(this.options.externalControl); 
     487    } 
     488     
     489    this.originalBackground = Element.getStyle(this.element, 'background-color'); 
     490    if (!this.originalBackground) { 
     491      this.originalBackground = "transparent"; 
     492    } 
     493     
     494    this.element.title = this.options.clickToEditText; 
     495     
     496    this.onclickListener = this.enterEditMode.bindAsEventListener(this); 
     497    this.mouseoverListener = this.enterHover.bindAsEventListener(this); 
     498    this.mouseoutListener = this.leaveHover.bindAsEventListener(this); 
     499    Event.observe(this.element, 'click', this.onclickListener); 
     500    Event.observe(this.element, 'mouseover', this.mouseoverListener); 
     501    Event.observe(this.element, 'mouseout', this.mouseoutListener); 
     502    if (this.options.externalControl) { 
     503      Event.observe(this.options.externalControl, 'click', this.onclickListener); 
     504      Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); 
     505      Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); 
     506    } 
     507  }, 
     508  enterEditMode: function(evt) { 
     509    if (this.saving) return; 
     510    if (this.editing) return; 
     511    this.editing = true; 
     512    this.onEnterEditMode(); 
     513    if (this.options.externalControl) { 
     514      Element.hide(this.options.externalControl); 
     515    } 
     516    Element.hide(this.element); 
     517    this.createForm(); 
     518    this.element.parentNode.insertBefore(this.form, this.element); 
     519    Field.scrollFreeActivate(this.editField); 
     520    // stop the event to avoid a page refresh in Safari 
     521    if (evt) { 
     522      Event.stop(evt); 
     523    } 
     524    return false; 
     525  }, 
     526  createForm: function() { 
     527    this.form = document.createElement("form"); 
     528    this.form.id = this.options.formId; 
     529    Element.addClassName(this.form, this.options.formClassName) 
     530    this.form.onsubmit = this.onSubmit.bind(this); 
     531 
     532    this.createEditField(); 
     533 
     534    if (this.options.textarea) { 
     535      var br = document.createElement("br"); 
     536      this.form.appendChild(br); 
     537    } 
     538 
     539    okButton = document.createElement("input"); 
     540    okButton.type = "submit"; 
     541    okButton.value = this.options.okText; 
     542    this.form.appendChild(okButton); 
     543 
     544    cancelLink = document.createElement("a"); 
     545    cancelLink.href = "#"; 
     546    cancelLink.appendChild(document.createTextNode(this.options.cancelText)); 
     547    cancelLink.onclick = this.onclickCancel.bind(this); 
     548    this.form.appendChild(cancelLink); 
     549  }, 
     550  hasHTMLLineBreaks: function(string) { 
     551    if (!this.options.handleLineBreaks) return false; 
     552    return string.match(/<br/i) || string.match(/<p>/i); 
     553  }, 
     554  convertHTMLLineBreaks: function(string) { 
     555    return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, ""); 
     556  }, 
     557  createEditField: function() { 
     558    var text; 
     559    if(this.options.loadTextURL) { 
     560      text = this.options.loadingText; 
     561    } else { 
     562      text = this.getText(); 
     563    } 
     564     
     565    if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { 
     566      this.options.textarea = false; 
     567      var textField = document.createElement("input"); 
     568      textField.type = "text"; 
     569      textField.name = "value"; 
     570      textField.value = text; 
     571      textField.style.backgroundColor = this.options.highlightcolor; 
     572      var size = this.options.size || this.options.cols || 0; 
     573      if (size != 0) textField.size = size; 
     574      this.editField = textField; 
     575    } else { 
     576      this.options.textarea = true; 
     577      var textArea = document.createElement("textarea"); 
     578      textArea.name = "value"; 
     579      textArea.value = this.convertHTMLLineBreaks(text); 
     580      textArea.rows = this.options.rows; 
     581      textArea.cols = this.options.cols || 40; 
     582      this.editField = textArea; 
     583    } 
     584     
     585    if(this.options.loadTextURL) { 
     586      this.loadExternalText(); 
     587    } 
     588    this.form.appendChild(this.editField); 
     589  }, 
     590  getText: function() { 
     591    return this.element.innerHTML; 
     592  }, 
     593  loadExternalText: function() { 
     594    Element.addClassName(this.form, this.options.loadingClassName); 
     595    this.editField.disabled = true; 
     596    new Ajax.Request( 
     597      this.options.loadTextURL, 
     598      Object.extend({ 
     599        asynchronous: true, 
     600        onComplete: this.onLoadedExternalText.bind(this) 
     601      }, this.options.ajaxOptions) 
     602    ); 
     603  }, 
     604  onLoadedExternalText: function(transport) { 
     605    Element.removeClassName(this.form, this.options.loadingClassName); 
     606    this.editField.disabled = false; 
     607    this.editField.value = transport.responseText.stripTags(); 
     608  }, 
     609  onclickCancel: function() { 
     610    this.onComplete(); 
     611    this.leaveEditMode(); 
     612    return false; 
     613  }, 
     614  onFailure: function(transport) { 
     615    this.options.onFailure(transport); 
     616    if (this.oldInnerHTML) { 
     617      this.element.innerHTML = this.oldInnerHTML; 
     618      this.oldInnerHTML = null; 
     619    } 
     620    return false; 
     621  }, 
     622  onSubmit: function() { 
     623    // onLoading resets these so we need to save them away for the Ajax call 
     624    var form = this.form; 
     625    var value = this.editField.value; 
     626     
     627    // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... 
     628    // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... 
     629    // to be displayed indefinitely 
     630    this.onLoading(); 
     631     
     632    new Ajax.Updater( 
     633      {  
     634        success: this.element, 
     635         // don't update on failure (this could be an option) 
     636        failure: null 
     637      }, 
     638      this.url, 
     639      Object.extend({ 
     640        parameters: this.options.callback(form, value), 
     641        onComplete: this.onComplete.bind(this), 
     642        onFailure: this.onFailure.bind(this) 
     643      }, this.options.ajaxOptions) 
     644    ); 
     645    // stop the event to avoid a page refresh in Safari 
     646    if (arguments.length > 1) { 
     647      Event.stop(arguments[0]); 
     648    } 
     649    return false; 
     650  }, 
     651  onLoading: function() { 
     652    this.saving = true; 
     653    this.removeForm(); 
     654    this.leaveHover(); 
     655    this.showSaving(); 
     656  }, 
     657  showSaving: function() { 
     658    this.oldInnerHTML = this.element.innerHTML; 
     659    this.element.innerHTML = this.options.savingText; 
     660    Element.addClassName(this.element, this.options.savingClassName); 
     661    this.element.style.backgroundColor = this.originalBackground; 
     662    Element.show(this.element); 
     663  }, 
     664  removeForm: function() { 
     665    if(this.form) { 
     666      if (this.form.parentNode) Element.remove(this.form); 
     667      this.form = null; 
     668    } 
     669  }, 
     670  enterHover: function() { 
     671    if (this.saving) return; 
     672    this.element.style.backgroundColor = this.options.highlightcolor; 
     673    if (this.effect) { 
     674      this.effect.cancel(); 
     675    } 
     676    Element.addClassName(this.element, this.options.hoverClassName) 
     677  }, 
     678  leaveHover: function() { 
     679    if (this.options.backgroundColor) { 
     680      this.element.style.backgroundColor = this.oldBackground; 
     681    } 
     682    Element.removeClassName(this.element, this.options.hoverClassName) 
     683    if (this.saving) return; 
     684    this.effect = new Effect.Highlight(this.element, { 
     685      startcolor: this.options.highlightcolor, 
     686      endcolor: this.options.highlightendcolor, 
     687      restorecolor: this.originalBackground 
     688    }); 
     689  }, 
     690  leaveEditMode: function() { 
     691    Element.removeClassName(this.element, this.options.savingClassName); 
     692    this.removeForm(); 
     693    this.leaveHover(); 
     694    this.element.style.backgroundColor = this.originalBackground; 
     695    Element.show(this.element); 
     696    if (this.options.externalControl) { 
     697      Element.show(this.options.externalControl); 
     698    } 
     699    this.editing = false; 
     700    this.saving = false; 
     701    this.oldInnerHTML = null; 
     702    this.onLeaveEditMode(); 
     703  }, 
     704  onComplete: function(transport) { 
     705    this.leaveEditMode(); 
     706    this.options.onComplete.bind(this)(transport, this.element); 
     707  }, 
     708  onEnterEditMode: function() {}, 
     709  onLeaveEditMode: function() {}, 
     710  dispose: function() { 
     711    if (this.oldInnerHTML) { 
     712      this.element.innerHTML = this.oldInnerHTML; 
     713    } 
     714    this.leaveEditMode(); 
     715    Event.stopObserving(this.element, 'click', this.onclickListener); 
     716    Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); 
     717    Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); 
     718    if (this.options.externalControl) { 
     719      Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); 
     720      Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); 
     721      Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); 
     722    } 
     723  } 
     724}; 
     725 
     726// Delayed observer, like Form.Element.Observer,  
     727// but waits for delay after last key input 
     728// Ideal for live-search fields 
     729 
     730Form.Element.DelayedObserver = Class.create(); 
     731Form.Element.DelayedObserver.prototype = { 
     732  initialize: function(element, delay, callback) { 
     733    this.delay     = delay || 0.5; 
     734    this.element   = $(element); 
     735    this.callback  = callback; 
     736    this.timer     = null; 
     737    this.lastValue = $F(this.element);  
     738    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); 
     739  }, 
     740  delayedListener: function(event) { 
     741    if(this.lastValue == $F(this.element)) return; 
     742    if(this.timer) clearTimeout(this.timer); 
     743    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); 
     744    this.lastValue = $F(this.element); 
     745  }, 
     746  onTimerEvent: function() { 
     747    this.timer = null; 
     748    this.callback(this.element, $F(this.element)); 
     749  } 
     750}; 
  • trunk/public/javascripts/dragdrop.js

    r5 r39  
    11// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 
    22//  
    3 // Element.Class part Copyright (c) 2005 by Rick Olson 
    4 //  
    5 // Permission is hereby granted, free of charge, to any person obtaining 
    6 // a copy of this software and associated documentation files (the 
    7 // "Software"), to deal in the Software without restriction, including 
    8 // without limitation the rights to use, copy, modify, merge, publish, 
    9 // distribute, sublicense, and/or sell copies of the Software, and to 
    10 // permit persons to whom the Software is furnished to do so, subject to 
    11 // the following conditions: 
    12 //  
    13 // The above copyright notice and this permission notice shall be 
    14 // included in all copies or substantial portions of the Software. 
    15 //  
    16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
    17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
    18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
    19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
    20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
    21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
    22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
    23  
    24 Element.Class = { 
    25     // Element.toggleClass(element, className) toggles the class being on/off 
    26     // Element.toggleClass(element, className1, className2) toggles between both classes, 
    27     //   defaulting to className1 if neither exist 
    28     toggle: function(element, className) { 
    29       if(Element.Class.has(element, className)) { 
    30         Element.Class.remove(element, className); 
    31         if(arguments.length == 3) Element.Class.add(element, arguments[2]); 
    32       } else { 
    33         Element.Class.add(element, className); 
    34         if(arguments.length == 3) Element.Class.remove(element, arguments[2]); 
    35       } 
    36     }, 
    37  
    38     // gets space-delimited classnames of an element as an array 
    39     get: function(element) { 
    40       element = $(element); 
    41       return element.className.split(' '); 
    42     }, 
    43  
    44     // functions adapted from original functions by Gavin Kistner 
    45     remove: function(element) { 
    46       element = $(element); 
    47       var regEx; 
    48       for(var i = 1; i < arguments.length; i++) { 
    49         regEx = new RegExp("^" + arguments[i] + "\\b\\s*|\\s*\\b" + arguments[i] + "\\b", 'g'); 
    50         element.className = element.className.replace(regEx, '') 
    51       } 
    52     }, 
    53  
    54     add: function(element) { 
    55       element = $(element); 
    56       for(var i = 1; i < arguments.length; i++) { 
    57         Element.Class.remove(element, arguments[i]); 
    58         element.className += (element.className.length > 0 ? ' ' : '') + arguments[i]; 
    59       } 
    60     }, 
    61  
    62     // returns true if all given classes exist in said element 
    63     has: function(element) { 
    64       element = $(element); 
    65       if(!element || !element.className) return false; 
    66       var regEx; 
    67       for(var i = 1; i < arguments.length; i++) { 
    68         regEx = new RegExp("\\b" + arguments[i] + "\\b"); 
    69         if(!regEx.test(element.className)) return false; 
    70       } 
    71       return true; 
    72     }, 
    73      
    74     // expects arrays of strings and/or strings as optional paramters 
    75     // Element.Class.has_any(element, ['classA','classB','classC'], 'classD') 
    76     has_any: function(element) { 
    77       element = $(element); 
    78       if(!element || !element.className) return false; 
    79       var regEx; 
    80       for(var i = 1; i < arguments.length; i++) { 
    81         if((typeof arguments[i] == 'object') &&  
    82           (arguments[i].constructor == Array)) { 
    83           for(var j = 0; j < arguments[i].length; j++) { 
    84             regEx = new RegExp("\\b" + arguments[i][j] + "\\b"); 
    85             if(regEx.test(element.className)) return true; 
    86           } 
    87         } else { 
    88           regEx = new RegExp("\\b" + arguments[i] + "\\b"); 
    89           if(regEx.test(element.className)) return true; 
    90         } 
    91       } 
    92       return false; 
    93     }, 
    94      
    95     childrenWith: function(element, className) { 
    96       var children = $(element).getElementsByTagName('*'); 
    97       var elements = new Array(); 
    98        
    99       for (var i = 0; i < children.length; i++) { 
    100         if (Element.Class.has(children[i], className)) { 
    101           elements.push(children[i]); 
    102           break; 
    103         } 
    104       } 
    105        
    106       return elements; 
    107     } 
    108 } 
     3// See scriptaculous.js for full license. 
    1094 
    1105/*--------------------------------------------------------------------------*/ 
    1116 
    1127var Droppables = { 
    113   drops: false, 
    114    
     8  drops: [], 
     9 
    11510  remove: function(element) { 
    116     for(var i = 0; i < this.drops.length; i++) 
    117       if(this.drops[i].element == element) 
    118         this.drops.splice(i,1); 
    119   }, 
    120    
     11    this.drops = this.drops.reject(function(d) { return d.element==$(element) }); 
     12  }, 
     13 
    12114  add: function(element) { 
    122     var element = $(element); 
     15    element = $(element); 
    12316    var options = Object.extend({ 
    12417      greedy:     true, 
    12518      hoverclass: null   
    12619    }, arguments[1] || {}); 
    127      
     20 
    12821    // cache containers 
    12922    if(options.containment) { 
    130       options._containers = new Array(); 
     23      options._containers = []; 
    13124      var containment = options.containment; 
    13225      if((typeof containment == 'object') &&  
    13326        (containment.constructor == Array)) { 
    134         for(var i=0; i<containment.length; i++) 
    135           options._containers.push($(containment[i])); 
     27        containment.each( function(c) { options._containers.push($(c)) }); 
    13628      } else { 
    13729        options._containers.push($(containment)); 
    13830      } 
    139       options._containers_length =  
    140         options._containers.length-1; 
    141     } 
    142      
     31    } 
     32     
     33    if(options.accept) options.accept = [options.accept].flatten(); 
     34 
    14335    Element.makePositioned(element); // fix IE 
    144      
    14536    options.element = element; 
    146      
    147     // activate the droppable     
    148     if(!this.drops) this.drops = []; 
     37 
    14938    this.drops.push(options); 
    15039  }, 
    151    
    152   is_contained: function(element, drop) { 
    153     var containers = drop._containers; 
     40 
     41  isContained: function(element, drop) { 
    15442    var parentNode = element.parentNode; 
    155     var i = drop._containers_length; 
    156     do { if(parentNode==containers[i]) return true; } while (i--); 
    157     return false; 
    158   }, 
    159    
    160   is_affected: function(pX, pY, element, drop) { 
     43    return drop._containers.detect(function(c) { return parentNode == c }); 
     44  }, 
     45 
     46  isAffected: function(point, element, drop) { 
    16147    return ( 
    16248      (drop.element!=element) && 
    16349      ((!drop._containers) || 
    164         this.is_contained(element, drop)) && 
     50        this.isContained(element, drop)) && 
    16551      ((!drop.accept) || 
    166         (Element.Class.has_any(element, drop.accept))) && 
    167       Position.within(drop.element, pX, pY) ); 
    168   }, 
    169    
     52        (Element.classNames(element).detect(  
     53          function(v) { return drop.accept.include(v) } ) )) && 
     54      Position.within(drop.element, point[0], point[1]) ); 
     55  }, 
     56 
    17057  deactivate: function(drop) { 
    171     Element.Class.remove(drop.element, drop.hoverclass); 
     58    if(drop.hoverclass) 
     59      Element.removeClassName(drop.element, drop.hoverclass); 
    17260    this.last_active = null; 
    17361  }, 
    174    
     62 
    17563  activate: function(drop) { 
     64    if(drop.hoverclass) 
     65      Element.addClassName(drop.element, drop.hoverclass); 
     66    this.last_active = drop; 
     67  }, 
     68 
     69  show: function(point, element) { 
     70    if(!this.drops.length) return; 
     71     
    17672    if(this.last_active) this.deactivate(this.last_active); 
    177     if(drop.hoverclass) { 
    178       Element.Class.add(drop.element, drop.hoverclass); 
    179       this.last_active = drop; 
    180     } 
    181   }, 
    182    
    183   show: function(event, element) { 
    184     if(!this.drops) return; 
    185     var pX = Event.pointerX(event); 
    186     var pY = Event.pointerY(event); 
    187     Position.prepare(); 
    188      
    189     var i = this.drops.length-1; do { 
    190       var drop = this.drops[i]; 
    191       if(this.is_affected(pX, pY, element, drop)) { 
     73    this.drops.each( function(drop) { 
     74      if(Droppables.isAffected(point, element, drop)) { 
    19275        if(drop.onHover) 
    19376           drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); 
    19477        if(drop.greedy) {  
    195           this.activate(drop); 
    196           return; 
     78          Droppables.activate(drop); 
     79          throw $break; 
    19780        } 
    19881      } 
    199     } while (i--); 
    200   }, 
    201    
     82    }); 
     83  }, 
     84 
    20285  fire: function(event, element) { 
    20386    if(!this.last_active) return; 
    20487    Position.prepare(); 
    205      
    206     if (this.is_affected(Event.pointerX(event), Event.pointerY(event), element, this.last_active)) 
     88 
     89    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) 
    20790      if (this.last_active.onDrop)  
    208         this.last_active.onDrop(element, this.last_active); 
    209      
    210   }, 
    211    
     91        this.last_active.onDrop(element, this.last_active.element, event); 
     92  }, 
     93 
    21294  reset: function() { 
    21395    if(this.last_active) 
     
    21698} 
    21799 
    218 Draggables = { 
    219   observers: new Array(), 
     100var Draggables = { 
     101  drags: [], 
     102  observers: [], 
     103   
     104  register: function(draggable) { 
     105    if(this.drags.length == 0) { 
     106      this.eventMouseUp   = this.endDrag.bindAsEventListener(this); 
     107      this.eventMouseMove = this.updateDrag.bindAsEventListener(this); 
     108      this.eventKeypress  = this.keyPress.bindAsEventListener(this); 
     109       
     110      Event.observe(document, "mouseup", this.eventMouseUp); 
     111      Event.observe(document, "mousemove", this.eventMouseMove); 
     112      Event.observe(document, "keypress", this.eventKeypress); 
     113    } 
     114    this.drags.push(draggable); 
     115  }, 
     116   
     117  unregister: function(draggable) { 
     118    this.drags = this.drags.reject(function(d) { return d==draggable }); 
     119    if(this.drags.length == 0) { 
     120      Event.stopObserving(document, "mouseup", this.eventMouseUp); 
     121      Event.stopObserving(document, "mousemove", this.eventMouseMove); 
     122      Event.stopObserving(document, "keypress", this.eventKeypress); 
     123    } 
     124  }, 
     125   
     126  activate: function(draggable) { 
     127    window.focus(); // allows keypress events if window isn't currently focused, fails for Safari 
     128    this.activeDraggable = draggable; 
     129  }, 
     130   
     131  deactivate: function(draggbale) { 
     132    this.activeDraggable = null; 
     133  }, 
     134   
     135  updateDrag: function(event) { 
     136    if(!this.activeDraggable) return; 
     137    var pointer = [Event.pointerX(event), Event.pointerY(event)]; 
     138    // Mozilla-based browsers fire successive mousemove events with 
     139    // the same coordinates, prevent needless redrawing (moz bug?) 
     140    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; 
     141    this._lastPointer = pointer; 
     142    this.activeDraggable.updateDrag(event, pointer); 
     143  }, 
     144   
     145  endDrag: function(event) { 
     146    if(!this.activeDraggable) return; 
     147    this._lastPointer = null; 
     148    this.activeDraggable.endDrag(event); 
     149  }, 
     150   
     151  keyPress: function(event) { 
     152    if(this.activeDraggable) 
     153      this.activeDraggable.keyPress(event); 
     154  }, 
     155   
    220156  addObserver: function(observer) { 
    221     this.observers.push(observer);     
    222   }, 
    223   removeObserver: function(element) {  // element instead of obsever fixes mem leaks 
    224     for(var i = 0; i < this.observers.length; i++) 
    225       if(this.observers[i].element && (this.observers[i].element == element)) 
    226         this.observers.splice(i,1); 
    227   }, 
    228   notify: function(eventName, draggable) {  // 'onStart', 'onEnd' 
    229     for(var i = 0; i < this.observers.length; i++) 
    230       this.observers[i][eventName](draggable); 
     157    this.observers.push(observer); 
     158    this._cacheObserverCallbacks(); 
     159  }, 
     160   
     161  removeObserver: function(element) {  // element instead of observer fixes mem leaks 
     162    this.observers = this.observers.reject( function(o) { return o.element==element }); 
     163    this._cacheObserverCallbacks(); 
     164  }, 
     165   
     166  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag' 
     167    if(this[eventName+'Count'] > 0) 
     168      this.observers.each( function(o) { 
     169        if(o[eventName]) o[eventName](eventName, draggable, event); 
     170      }); 
     171  }, 
     172   
     173  _cacheObserverCallbacks: function() { 
     174    ['onStart','onEnd','onDrag'].each( function(eventName) { 
     175      Draggables[eventName+'Count'] = Draggables.observers.select( 
     176        function(o) { return o[eventName]; } 
     177      ).length; 
     178    }); 
    231179  } 
    232180} 
     
    234182/*--------------------------------------------------------------------------*/ 
    235183 
    236 Draggable = Class.create(); 
     184var Draggable = Class.create(); 
    237185Draggable.prototype = { 
    238186  initialize: function(element) { 
     
    243191      }, 
    244192      reverteffect: function(element, top_offset, left_offset) { 
    245         new Effect.MoveBy(element, -top_offset, -left_offset, {duration:0.4}); 
     193        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; 
     194        element._revert = new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur}); 
    246195      }, 
    247196      endeffect: function(element) {  
    248          new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});  
     197        new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});  
    249198      }, 
    250199      zindex: 1000, 
    251       revert: false 
     200      revert: false, 
     201      snap: false   // false, or xy or [x,y] or function(x,y){ return [x,y] } 
    252202    }, arguments[1] || {}); 
    253      
    254     this.element      = $(element); 
    255     this.handle       = options.handle ? $(options.handle) : this.element; 
    256      
    257     Element.makePositioned(this.element); // fix IE 
    258      
    259     this.offsetX      = 0; 
    260     this.offsetY      = 0; 
    261     this.originalLeft = this.currentLeft(); 
    262     this.originalTop  = this.currentTop(); 
    263     this.originalX    = this.element.offsetLeft; 
    264     this.originalY    = this.element.offsetTop; 
    265     this.originalZ    = parseInt(this.element.style.zIndex || "0"); 
    266      
    267     this.options      = options; 
    268      
    269     this.active       = false; 
    270     this.dragging     = false;    
    271      
    272     this.eventMouseDown = this.startDrag.bindAsEventListener(this); 
    273     this.eventMouseUp   = this.endDrag.bindAsEventListener(this); 
    274     this.eventMouseMove = this.update.bindAsEventListener(this); 
    275     this.eventKeypress  = this.keyPress.bindAsEventListener(this); 
    276      
     203 
     204    this.element = $(element); 
     205     
     206    if(options.handle && (typeof options.handle == 'string')) 
     207      this.handle = Element.childrenWithClassName(this.element, options.handle)[0];   
     208    if(!this.handle) this.handle = $(options.handle); 
     209    if(!this.handle) this.handle = this.element; 
     210 
     211    Element.makePositioned(this.element); // fix IE     
     212 
     213    this.delta    = this.currentDelta(); 
     214    this.options  = options; 
     215    this.dragging = false;    
     216 
     217    this.eventMouseDown = this.initDrag.bindAsEventListener(this); 
    277218    Event.observe(this.handle, "mousedown", this.eventMouseDown); 
    278     Event.observe(document, "mouseup", this.eventMouseUp); 
    279     Event.observe(document, "mousemove", this.eventMouseMove); 
    280     Event.observe(document, "keypress", this.eventKeypress); 
    281   }, 
     219     
     220    Draggables.register(this); 
     221  }, 
     222   
    282223  destroy: function() { 
    283224    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); 
    284     Event.stopObserving(document, "mouseup", this.eventMouseUp); 
    285     Event.stopObserving(document, "mousemove", this.eventMouseMove); 
    286     Event.stopObserving(document, "keypress", this.eventKeypress); 
    287   }, 
    288   currentLeft: function() { 
    289     return parseInt(this.element.style.left || '0'); 
    290   }, 
    291   currentTop: function() { 
    292     return parseInt(this.element.style.top || '0') 
    293   }, 
     225    Draggables.unregister(this); 
     226  }, 
     227   
     228  currentDelta: function() { 
     229    return([ 
     230      parseInt(this.element.style.left || '0'), 
     231      parseInt(this.element.style.top || '0')]); 
     232  }, 
     233   
     234  initDrag: function(event) { 
     235    if(Event.isLeftClick(event)) {     
     236      // abort on form elements, fixes a Firefox issue 
     237      var src = Event.element(event); 
     238      if(src.tagName && ( 
     239        src.tagName=='INPUT' || 
     240        src.tagName=='SELECT' || 
     241        src.tagName=='BUTTON' || 
     242        src.tagName=='TEXTAREA')) return; 
     243         
     244      if(this.element._revert) { 
     245        this.element._revert.cancel(); 
     246        this.element._revert = null; 
     247      } 
     248       
     249      var pointer = [Event.pointerX(event), Event.pointerY(event)]; 
     250      var pos     = Position.cumulativeOffset(this.element); 
     251      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); 
     252       
     253      Draggables.activate(this); 
     254      Event.stop(event); 
     255    } 
     256  }, 
     257   
    294258  startDrag: function(event) { 
    295     if(Event.isLeftClick(event)) { 
    296       this.active = true; 
    297        
    298       var style = this.element.style; 
    299       this.originalY = this.element.offsetTop  - this.currentTop()  - this.originalTop; 
    300       this.originalX = this.element.offsetLeft - this.currentLeft() - this.originalLeft; 
    301       this.offsetY =  event.clientY - this.originalY - this.originalTop; 
    302       this.offsetX =  event.clientX - this.originalX - this.originalLeft; 
    303        
    304       Event.stop(event); 
    305     } 
    306   }, 
     259    this.dragging = true; 
     260     
     261    if(this.options.zindex) { 
     262      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); 
     263      this.element.style.zIndex = this.options.zindex; 
     264    } 
     265     
     266    if(this.options.ghosting) { 
     267      this._clone = this.element.cloneNode(true); 
     268      Position.absolutize(this.element); 
     269      this.element.parentNode.insertBefore(this._clone, this.element); 
     270    } 
     271     
     272    Draggables.notify('onStart', this, event); 
     273    if(this.options.starteffect) this.options.starteffect(this.element); 
     274  }, 
     275   
     276  updateDrag: function(event, pointer) { 
     277    if(!this.dragging) this.startDrag(event); 
     278    Position.prepare(); 
     279    Droppables.show(pointer, this.element); 
     280    Draggables.notify('onDrag', this, event); 
     281    this.draw(pointer); 
     282    if(this.options.change) this.options.change(this); 
     283     
     284    // fix AppleWebKit rendering 
     285    if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 
     286    Event.stop(event); 
     287  }, 
     288   
    307289  finishDrag: function(event, success) { 
    308     this.active = false; 
    309290    this.dragging = false; 
    310      
     291 
     292    if(this.options.ghosting) { 
     293      Position.relativize(this.element); 
     294      Element.remove(this._clone); 
     295      this._clone = null; 
     296    } 
     297 
    311298    if(success) Droppables.fire(event, this.element); 
    312     Draggables.notify('onEnd', this); 
    313      
     299    Draggables.notify('onEnd', this, event); 
     300 
    314301    var revert = this.options.revert; 
    315302    if(revert && typeof revert == 'function') revert = revert(this.element); 
    316        
     303     
     304    var d = this.currentDelta(); 
    317305    if(revert && this.options.reverteffect) { 
    318306      this.options.reverteffect(this.element,  
    319       this.currentTop()-this.originalTop, 
    320       this.currentLeft()-this.originalLeft); 
     307        d[1]-this.delta[1], d[0]-this.delta[0]); 
    321308    } else { 
    322       this.originalLeft = this.currentLeft(); 
    323       this.originalTop  = this.currentTop(); 
    324     } 
    325      
    326     this.element.style.zIndex = this.originalZ; 
    327        
     309      this.delta = d; 
     310    } 
     311 
     312    if(this.options.zindex) 
     313      this.element.style.zIndex = this.originalZ; 
     314 
    328315    if(this.options.endeffect)  
    329316      this.options.endeffect(this.element); 
    330        
     317 
     318    Draggables.deactivate(this); 
    331319    Droppables.reset(); 
    332320  }, 
     321   
    333322  keyPress: function(event) { 
    334     if(this.active) { 
    335       if(event.keyCode==Event.KEY_ESC) { 
    336         this.finishDrag(event, false); 
    337         Event.stop(event); 
    338       } 
    339     } 
    340   }, 
     323    if(!event.keyCode==Event.KEY_ESC) return; 
     324    this.finishDrag(event, false); 
     325    Event.stop(event); 
     326  }, 
     327   
    341328  endDrag: function(event) { 
    342     if(this.active && this.dragging) { 
    343       this.finishDrag(event, true); 
    344       Event.stop(event); 
    345     } 
    346     this.active = false; 
    347     this.dragging = false; 
    348   }, 
    349   draw: function(event) { 
     329    if(!this.dragging) return; 
     330    this.finishDrag(event, true); 
     331    Event.stop(event); 
     332  }, 
     333   
     334  draw: function(point) { 
     335    var pos = Position.cumulativeOffset(this.element); 
     336    var d = this.currentDelta(); 
     337    pos[0] -= d[0]; pos[1] -= d[1]; 
     338     
     339    var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this)); 
     340     
     341    if(this.options.snap) { 
     342      if(typeof this.options.snap == 'function') { 
     343        p = this.options.snap(p[0],p[1]); 
     344      } else { 
     345      if(this.options.snap instanceof Array) { 
     346        p = p.map( function(v, i) { 
     347          return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) 
     348      } else { 
     349        p = p.map( function(v) { 
     350          return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) 
     351      } 
     352    }} 
     353     
    350354    var style = this.element.style; 
    351     this.originalX = this.element.offsetLeft - this.currentLeft() - this.originalLeft; 
    352     this.originalY = this.element.offsetTop  - this.currentTop()  - this.originalTop; 
    353355    if((!this.options.constraint) || (this.options.constraint=='horizontal')) 
    354       style.left = ((event.clientX - this.originalX) - this.offsetX) + "px"; 
     356      style.left = p[0] + "px"; 
    355357    if((!this.options.constraint) || (this.options.constraint=='vertical')) 
    356       style.top  = ((event.clientY - this.originalY) - this.offsetY) + "px"; 
     358      style.top  = p[1] + "px"; 
    357359    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering 
    358   }, 
    359   update: function(event) { 
    360    if(this.active) { 
    361       if(!this.dragging) { 
    362         var style = this.element.style; 
    363         this.dragging = true; 
    364         if(style.position=="") style.position = "relative"; 
    365         style.zIndex = this.options.zindex; 
    366         Draggables.notify('onStart', this); 
    367         if(this.options.starteffect) this.options.starteffect(this.element); 
    368       } 
    369        
    370       Droppables.show(event, this.element); 
    371       this.draw(event); 
    372       if(this.options.change) this.options.change(this); 
    373        
    374       // fix AppleWebKit rendering 
    375       if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);  
    376        
    377       Event.stop(event); 
    378    } 
    379360  } 
    380361} 
     
    382363/*--------------------------------------------------------------------------*/ 
    383364 
    384 SortableObserver = Class.create(); 
     365var SortableObserver = Class.create(); 
    385366SortableObserver.prototype = { 
    386367  initialize: function(element, observer) { 
     
    389370    this.lastValue = Sortable.serialize(this.element); 
    390371  }, 
     372   
    391373  onStart: function() { 
    392374    this.lastValue = Sortable.serialize(this.element); 
    393375  }, 
    394   onEnd: function() {     
     376   
     377  onEnd: function() { 
     378    Sortable.unmark(); 
    395379    if(this.lastValue != Sortable.serialize(this.element)) 
    396380      this.observer(this.element) 
     
    398382} 
    399383 
    400 Sortable = { 
     384var Sortable = { 
    401385  sortables: new Array(), 
     386   
    402387  options: function(element){ 
    403     var element = $(element); 
    404     for(var i=0;i<this.sortables.length;i++) 
    405       if(this.sortables[i].element == element) 
    406         return this.sortables[i]; 
    407     return null;         
    408   }, 
     388    element = $(element); 
     389    return this.sortables.detect(function(s) { return s.element == element }); 
     390  }, 
     391   
    409392  destroy: function(element){ 
    410     var element = $(element); 
    411     for(var i=0;i<this.sortables.length;i++) { 
    412       if(this.sortables[i].element == element) { 
    413         var s = this.sortables[i]; 
    414         Draggables.removeObserver(s.element); 
    415         for(var j=0;j<s.droppables.length;j++) 
    416           Droppables.remove(s.droppables[j]); 
    417         for(var j=0;j<s.draggables.length;j++) 
    418           s.draggables[j].destroy(); 
    419         this.sortables.splice(i,1); 
    420       } 
    421     } 
    422   }, 
     393    element = $(element); 
     394    this.sortables.findAll(function(s) { return s.element == element }).each(function(s){ 
     395      Draggables.removeObserver(s.element); 
     396      s.droppables.each(function(d){ Droppables.remove(d) }); 
     397      s.draggables.invoke('destroy'); 
     398    }); 
     399    this.sortables = this.sortables.reject(function(s) { return s.element == element }); 
     400  }, 
     401   
    423402  create: function(element) { 
    424     var element = $(element); 
     403    element = $(element); 
    425404    var options = Object.extend({  
    426405      element:     element, 
    427406      tag:         'li',       // assumes li children, override with tag: 'tagname' 
     407      dropOnEmpty: false, 
     408      tree:        false,      // fixme: unimplemented 
    428409      overlap:     'vertical', // one of 'vertical', 'horizontal' 
    429410      constraint:  'vertical', // one of 'vertical', 'horizontal', false 
     
    432413      only:        false, 
    433414      hoverclass:  null, 
    434       onChange:    function() {}, 
    435       onUpdate:    function() {} 
     415      ghosting:    false, 
     416      format:      null, 
     417      onChange:    Prototype.emptyFunction, 
     418      onUpdate:    Prototype.emptyFunction 
    436419    }, arguments[1] || {}); 
    437      
     420 
    438421    // clear any old sortable with same element 
    439422    this.destroy(element); 
    440      
     423 
    441424    // build options for the draggables 
    442425    var options_for_draggable = { 
    443426      revert:      true, 
     427      ghosting:    options.ghosting, 
    444428      constraint:  options.constraint, 
    445       handle:      handle }; 
     429      handle:      options.handle }; 
     430 
    446431    if(options.starteffect) 
    447432      options_for_draggable.starteffect = options.starteffect; 
     433 
    448434    if(options.reverteffect) 
    449435      options_for_draggable.reverteffect = options.reverteffect; 
     436    else 
     437      if(options.ghosting) options_for_draggable.reverteffect = function(element) { 
     438        element.style.top  = 0; 
     439        element.style.left = 0; 
     440      }; 
     441 
    450442    if(options.endeffect) 
    451443      options_for_draggable.endeffect = options.endeffect; 
     444 
    452445    if(options.zindex) 
    453446      options_for_draggable.zindex = options.zindex; 
    454      
     447 
    455448    // build options for the droppables   
    456449    var options_for_droppable = { 
     
    458451      containment: options.containment, 
    459452      hoverclass:  options.hoverclass, 
    460       onHover: function(element, dropon, overlap) {  
    461         if(overlap>0.5) { 
    462           if(dropon.previousSibling != element) { 
    463             var oldParentNode = element.parentNode; 
    464             element.style.visibility = "hidden"; // fix gecko rendering 
    465             dropon.parentNode.insertBefore(element, dropon); 
    466             if(dropon.parentNode!=oldParentNode && oldParentNode.sortable)  
    467               oldParentNode.sortable.onChange(element); 
    468             if(dropon.parentNode.sortable) 
    469               dropon.parentNode.sortable.onChange(element); 
    470           } 
    471         } else {                 
    472           var nextElement = dropon.nextSibling || null; 
    473           if(nextElement != element) { 
    474             var oldParentNode = element.parentNode; 
    475             element.style.visibility = "hidden"; // fix gecko rendering 
    476             dropon.parentNode.insertBefore(element, nextElement); 
    477             if(dropon.parentNode!=oldParentNode && oldParentNode.sortable)  
    478               oldParentNode.sortable.onChange(element); 
    479             if(dropon.parentNode.sortable) 
    480               dropon.parentNode.sortable.onChange(element); 
    481           } 
    482         } 
    483       } 
     453      onHover:     Sortable.onHover, 
     454      greedy:      !options.dropOnEmpty 
    484455    } 
    485456 
    486457    // fix for gecko engine 
    487458    Element.cleanWhitespace(element);  
    488      
     459 
    489460    options.draggables = []; 
    490461    options.droppables = []; 
    491      
    492     // make it so  
    493     var elements = element.childNodes; 
    494     for (var i = 0; i < elements.length; i++)  
    495       if(elements[i].tagName && elements[i].tagName==options.tag.toUpperCase() && 
    496         (!options.only || (Element.Class.has(elements[i], options.only)))) { 
    497          
    498         // handles are per-draggable 
    499         var handle = options.handle ?  
    500           Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i]; 
    501          
    502         options.draggables.push(new Draggable(elements[i], Object.extend(options_for_draggable, { handle: handle }))); 
    503          
    504         Droppables.add(elements[i], options_for_droppable); 
    505         options.droppables.push(elements[i]); 
    506          
    507       } 
    508        
     462 
     463    // make it so 
     464 
     465    // drop on empty handling 
     466    if(options.dropOnEmpty) { 
     467      Droppables.add(element, 
     468        {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false}); 
     469      options.droppables.push(element); 
     470    } 
     471 
     472    (this.findElements(element, options) || []).each( function(e) { 
     473      // handles are per-draggable 
     474      var handle = options.handle ?  
     475        Element.childrenWithClassName(e, options.handle)[0] : e;     
     476      options.draggables.push( 
     477        new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); 
     478      Droppables.add(e, options_for_droppable); 
     479      options.droppables.push(e);       
     480    }); 
     481 
    509482    // keep reference 
    510483    this.sortables.push(options); 
    511      
     484 
    512485    // for onupdate 
    513486    Draggables.addObserver(new SortableObserver(element, options.onUpdate)); 
    514487 
    515488  }, 
     489 
     490  // return all suitable-for-sortable elements in a guaranteed order 
     491  findElements: function(element, options) { 
     492    if(!element.hasChildNodes()) return null; 
     493    var elements = []; 
     494    $A(element.childNodes).each( function(e) { 
     495      if(e.tagName && e.tagName.toUpperCase()==options.tag.toUpperCase() && 
     496        (!options.only || (Element.hasClassName(e, options.only)))) 
     497          elements.push(e); 
     498      if(options.tree) { 
     499        var grandchildren = this.findElements(e, options); 
     500        if(grandchildren) elements.push(grandchildren); 
     501      } 
     502    }); 
     503 
     504    return (elements.length>0 ? elements.flatten() : null); 
     505  }, 
     506 
     507  onHover: function(element, dropon, overlap) { 
     508    if(overlap>0.5) { 
     509      Sortable.mark(dropon, 'before'); 
     510      if(dropon.previousSibling != element) { 
     511        var oldParentNode = element.parentNode; 
     512        element.style.visibility = "hidden"; // fix gecko rendering 
     513        dropon.parentNode.insertBefore(element, dropon); 
     514        if(dropon.parentNode!=oldParentNode)  
     515          Sortable.options(oldParentNode).onChange(element); 
     516        Sortable.options(dropon.parentNode).onChange(element); 
     517      } 
     518    } else { 
     519      Sortable.mark(dropon, 'after'); 
     520      var nextElement = dropon.nextSibling || null; 
     521      if(nextElement != element) { 
     522        var oldParentNode = element.parentNode; 
     523        element.style.visibility = "hidden"; // fix gecko rendering 
     524        dropon.parentNode.insertBefore(element, nextElement); 
     525        if(dropon.parentNode!=oldParentNode)  
     526          Sortable.options(oldParentNode).onChange(element); 
     527        Sortable.options(dropon.parentNode).onChange(element); 
     528      } 
     529    } 
     530  }, 
     531 
     532  onEmptyHover: function(element, dropon) { 
     533    if(element.parentNode!=dropon) { 
     534      var oldParentNode = element.parentNode; 
     535      dropon.appendChild(element); 
     536      Sortable.options(oldParentNode).onChange(element); 
     537      Sortable.options(dropon).onChange(element); 
     538    } 
     539  }, 
     540 
     541  unmark: function() { 
     542    if(Sortable._marker) Element.hide(Sortable._marker); 
     543  }, 
     544 
     545  mark: function(dropon, position) { 
     546    // mark on ghosting only 
     547    var sortable = Sortable.options(dropon.parentNode); 
     548    if(sortable && !sortable.ghosting) return;  
     549 
     550    if(!Sortable._marker) { 
     551      Sortable._marker = $('dropmarker') || document.createElement('DIV'); 
     552      Element.hide(Sortable._marker); 
     553      Element.addClassName(Sortable._marker, 'dropmarker'); 
     554      Sortable._marker.style.position = 'absolute'; 
     555      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); 
     556    }     
     557    var offsets = Position.cumulativeOffset(dropon); 
     558    Sortable._marker.style.left = offsets[0] + 'px'; 
     559    Sortable._marker.style.top = offsets[1] + 'px'; 
     560     
     561    if(position=='after') 
     562      if(sortable.overlap == 'horizontal')  
     563        Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px'; 
     564      else 
     565        Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; 
     566     
     567    Element.show(Sortable._marker); 
     568  }, 
     569 
    516570  serialize: function(element) { 
    517     var element = $(element); 
     571    element = $(element); 
    518572    var sortableOptions = this.options(element); 
    519573    var options = Object.extend({ 
    520574      tag:  sortableOptions.tag, 
    521575      only: sortableOptions.only, 
    522       name: element.id 
     576      name: element.id, 
     577      format: sortableOptions.format || /^[^_]*_(.*)$/ 
    523578    }, arguments[1] || {}); 
    524      
    525     var items = $(element).childNodes; 
    526     var queryComponents = new Array(); 
    527   
    528     for(var i=0; i<items.length; i++) 
    529       if(items[i].tagName && items[i].tagName==options.tag.toUpperCase() && 
    530         (!options.only || (Element.Class.has(items[i], options.only)))) 
    531         queryComponents.push( 
    532           encodeURIComponent(options.name) + "[]=" +  
    533           encodeURIComponent(items[i].id.split("_")[1])); 
    534  
    535     return queryComponents.join("&"); 
     579    return $(this.findElements(element, options) || []).map( function(item) { 
     580      return (encodeURIComponent(options.name) + "[]=" +  
     581              encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : '')); 
     582    }).join("&"); 
    536583  } 
    537 }  
     584} 
  • trunk/public/javascripts/effects.js

    r5 r39  
    11// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 
    2 // 
    3 // Parts (c) 2005 Justin Palmer (http://encytemedia.com/) 
    4 // Parts (c) 2005 Mark Pilgrim (http://diveintomark.org/) 
     2// Contributors: 
     3//  Justin Palmer (http://encytemedia.com/) 
     4//  Mark Pilgrim (http://diveintomark.org/) 
     5//  Martin Bialasinki 
    56//  
    6 // Permission is hereby granted, free of charge, to any person obtaining 
    7 // a copy of this software and associated documentation files (the 
    8 // "Software"), to deal in the Software without restriction, including 
    9 // without limitation the rights to use, copy, modify, merge, publish, 
    10 // distribute, sublicense, and/or sell copies of the Software, and to 
    11 // permit persons to whom the Software is furnished to do so, subject to 
    12 // the following conditions: 
    13 //  
    14 // The above copyright notice and this permission notice shall be 
    15 // included in all copies or substantial portions of the Software. 
    16 //  
    17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
    18 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
    19 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
    20 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
    21 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
    22 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
    23 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
    24  
    25  
    26 Effect = {} 
    27 Effect2 = Effect; // deprecated 
     7// See scriptaculous.js for full license.   
     8 
     9/* ------------- element ext -------------- */   
     10  
     11// converts rgb() and #xxx to #xxxxxx format,   
     12// returns self (or first argument) if not convertable   
     13String.prototype.parseColor = function() {   
     14  var color = '#';   
     15  if(this.slice(0,4) == 'rgb(') {   
     16    var cols = this.slice(4,this.length-1).split(',');   
     17    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);   
     18  } else {   
     19    if(this.slice(0,1) == '#') {   
     20      if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();   
     21      if(this.length==7) color = this.toLowerCase();   
     22    }   
     23  }   
     24  return(color.length==7 ? color : (arguments[0] || this));   
     25 
     26 
     27Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {   
     28  var children = $(element).childNodes;   
     29  var text     = '';   
     30  var classtest = new RegExp('^([^ ]+ )*' + ignoreclass+ '( [^ ]+)*$','i');   
     31  
     32  for (var i = 0; i < children.length; i++) {   
     33    if(children[i].nodeType==3) {   
     34      text+=children[i].nodeValue;   
     35    } else {   
     36      if((!children[i].className.match(classtest)) && children[i].hasChildNodes())   
     37        text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);   
     38    }   
     39  }   
     40  
     41  return text; 
     42} 
     43 
     44Element.setStyle = function(element, style) { 
     45  element = $(element); 
     46  for(k in style) element.style[k.camelize()] = style[k]; 
     47} 
     48 
     49Element.setContentZoom = function(element, percent) {   
     50  Element.setStyle(element, {fontSize: (percent/100) + 'em'});    
     51  if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);   
     52} 
     53 
     54Element.getOpacity = function(element){   
     55  var opacity; 
     56  if (opacity = Element.getStyle(element, 'opacity'))   
     57    return parseFloat(opacity);   
     58  if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))   
     59    if(opacity[1]) return parseFloat(opacity[1]) / 100;   
     60  return 1.0;   
     61} 
     62 
     63Element.setOpacity = function(element, value){   
     64  element= $(element);   
     65  if (value == 1){ 
     66    Element.setStyle(element, { opacity:  
     67      (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ?  
     68      0.999999 : null }); 
     69    if(/MSIE/.test(navigator.userAgent))   
     70      Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});   
     71  } else {   
     72    if(value < 0.00001) value = 0;   
     73    Element.setStyle(element, {opacity: value}); 
     74    if(/MSIE/.test(navigator.userAgent))   
     75     Element.setStyle(element,  
     76       { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + 
     77                 'alpha(opacity='+value*100+')' });   
     78  }    
     79 
     80  
     81Element.getInlineOpacity = function(element){   
     82  return $(element).style.opacity || ''; 
     83 
     84 
     85Element.childrenWithClassName = function(element, className) {   
     86  return $A($(element).getElementsByTagName('*')).select( 
     87    function(c) { return Element.hasClassName(c, className) }); 
     88} 
     89 
     90Array.prototype.call = function() { 
     91  var args = arguments; 
     92  this.each(function(f){ f.apply(this, args) }); 
     93} 
     94 
     95/*--------------------------------------------------------------------------*/ 
     96 
     97var Effect = { 
     98  tagifyText: function(element) { 
     99    var tagifyStyle = 'position:relative'; 
     100    if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1'; 
     101    element = $(element); 
     102    $A(element.childNodes).each( function(child) { 
     103      if(child.nodeType==3) { 
     104        child.nodeValue.toArray().each( function(character) { 
     105          element.insertBefore( 
     106            Builder.node('span',{style: tagifyStyle}, 
     107              character == ' ' ? String.fromCharCode(160) : character),  
     108              child); 
     109        }); 
     110        Element.remove(child); 
     111      } 
     112    }); 
     113  }, 
     114  multiple: function(element, effect) { 
     115    var elements; 
     116    if(((typeof element == 'object') ||  
     117        (typeof element == 'function')) &&  
     118       (element.length)) 
     119      elements = element; 
     120    else 
     121      elements = $(element).childNodes; 
     122       
     123    var options = Object.extend({ 
     124      speed: 0.1, 
     125      delay: 0.0 
     126    }, arguments[2] || {}); 
     127    var masterDelay = options.delay; 
     128 
     129    $A(elements).each( function(element, index) { 
     130      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); 
     131    }); 
     132  } 
     133}; 
     134 
     135var Effect2 = Effect; // deprecated 
    28136 
    29137/* ------------- transitions ------------- */ 
     
    41149} 
    42150Effect.Transitions.flicker = function(pos) { 
    43   return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random(0.25); 
     151  return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; 
    44152} 
    45153Effect.Transitions.wobble = function(pos) { 
     
    57165} 
    58166 
    59 /* ------------- element ext -------------- */ 
    60  
    61 Element.makePositioned = function(element) { 
    62   element = $(element); 
    63   if(element.style.position == "") 
    64     element.style.position = "relative"; 
    65 } 
    66  
    67 Element.makeClipping = function(element) { 
    68   element = $(element); 
    69   element._overflow = element.style.overflow || 'visible'; 
    70   if(element._overflow!='hidden') element.style.overflow = 'hidden'; 
    71 } 
    72  
    73 Element.undoClipping = function(element) { 
    74   element = $(element); 
    75   if(element._overflow!='hidden') element.style.overflow = element._overflow; 
    76 } 
    77  
    78167/* ------------- core effects ------------- */ 
     168 
     169Effect.Queue = { 
     170  effects:  [], 
     171  _each: function(iterator) { 
     172    this.effects._each(iterator); 
     173  }, 
     174  interval: null, 
     175  add: function(effect) { 
     176    var timestamp = new Date().getTime(); 
     177     
     178    switch(effect.options.queue) { 
     179      case 'front': 
     180        // move unstarted effects after this effect   
     181        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { 
     182            e.startOn  += effect.finishOn; 
     183            e.finishOn += effect.finishOn; 
     184          }); 
     185        break; 
     186      case 'end': 
     187        // start effect after last queued effect has finished 
     188        timestamp = this.effects.pluck('finishOn').max() || timestamp; 
     189        break; 
     190    } 
     191     
     192    effect.startOn  += timestamp; 
     193    effect.finishOn += timestamp; 
     194    this.effects.push(effect); 
     195    if(!this.interval)  
     196      this.interval = setInterval(this.loop.bind(this), 40); 
     197  }, 
     198  remove: function(effect) { 
     199    this.effects = this.effects.reject(function(e) { return e==effect }); 
     200    if(this.effects.length == 0) { 
     201      clearInterval(this.interval); 
     202      this.interval = null; 
     203    } 
     204  }, 
     205  loop: function() { 
     206    var timePos = new Date().getTime(); 
     207    this.effects.invoke('loop', timePos); 
     208  } 
     209} 
     210Object.extend(Effect.Queue, Enumerable); 
    79211 
    80212Effect.Base = function() {}; 
    81213Effect.Base.prototype = { 
     214  position: null, 
    82215  setOptions: function(options) { 
    83216    this.options = Object.extend({ 
    84217      transition: Effect.Transitions.sinoidal, 
    85218      duration:   1.0,   // seconds 
    86       fps:        25.0,  // max. 100fps 
     219      fps:        25.0,  // max. 25fps due to Effect.Queue implementation 
    87220      sync:       false, // true for combining 
    88221      from:       0.0, 
    89       to:         1.0 
     222      to:         1.0, 
     223      delay:      0.0, 
     224      queue:      'parallel' 
    90225    }, options || {}); 
    91226  }, 
     
    93228    this.setOptions(options || {}); 
    94229    this.currentFrame = 0; 
    95     this.startOn      = new Date().getTime(); 
     230    this.state        = 'idle'; 
     231    this.startOn      = this.options.delay*1000; 
    96232    this.finishOn     = this.startOn + (this.options.duration*1000); 
    97     if(this.options.beforeStart) this.options.beforeStart(this); 
    98     if(!this.options.sync) this.loop();   
    99   }, 
    100   loop: function() { 
    101     var timePos = new Date().getTime(); 
    102     if(timePos >= this.finishOn) { 
    103       this.render(this.options.to); 
    104       if(this.finish) this.finish();  
    105       if(this.options.afterFinish) this.options.afterFinish(this); 
    106       return;   
     233    this.event('beforeStart'); 
     234    if(!this.options.sync) Effect.Queue.add(this); 
     235  }, 
     236  loop: function(timePos) { 
     237    if(timePos >= this.startOn) { 
     238      if(timePos >= this.finishOn) { 
     239        this.render(1.0); 
     240        this.cancel(); 
     241        this.event('beforeFinish'); 
     242        if(this.finish) this.finish();  
     243        this.event('afterFinish'); 
     244        return;   
     245      } 
     246      var pos   = (timePos - this.startOn) / (this.finishOn - this.startOn); 
     247      var frame = Math.round(pos * this.options.fps * this.options.duration); 
     248      if(frame > this.currentFrame) { 
     249        this.render(pos); 
     250        this.currentFrame = frame; 
     251      } 
    107252    } 
    108     var pos   = (timePos - this.startOn) / (this.finishOn - this.startOn); 
    109     var frame = Math.round(pos * this.options.fps * this.options.duration); 
    110     if(frame > this.currentFrame) { 
    111       this.render(pos); 
    112       this.currentFrame = frame; 
     253  }, 
     254  render: function(pos) { 
     255    if(this.state == 'idle') { 
     256      this.state = 'running'; 
     257      this.event('beforeSetup'); 
     258      if(this.setup) this.setup(); 
     259      this.event('afterSetup'); 
    113260    } 
    114     this.timeout = setTimeout(this.loop.bind(this), 10); 
    115   }, 
    116   render: function(pos) { 
    117     if(this.options.transition) pos = this.options.transition(pos); 
    118     pos *= (this.options.to-this.options.from); 
    119     pos += this.options.from;  
    120     if(this.options.beforeUpdate) this.options.beforeUpdate(this); 
    121     if(this.update) this.update(pos); 
    122     if(this.options.afterUpdate) this.options.afterUpdate(this);   
     261    if(this.state == 'running') { 
     262      if(this.options.transition) pos = this.options.transition(pos); 
     263      pos *= (this.options.to-this.options.from); 
     264      pos += this.options.from; 
     265      this.position = pos; 
     266      this.event('beforeUpdate'); 
     267      if(this.update) this.update(pos); 
     268      this.event('afterUpdate'); 
     269    } 
    123270  }, 
    124271  cancel: function() { 
    125     if(this.timeout) clearTimeout(this.timeout); 
     272    if(!this.options.sync) Effect.Queue.remove(this); 
     273    this.state = 'finished'; 
     274  }, 
     275  event: function(eventName) { 
     276    if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); 
     277    if(this.options[eventName]) this.options[eventName](this); 
     278  }, 
     279  inspect: function() { 
     280    return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>'; 
    126281  } 
    127282} 
     
    134289  }, 
    135290  update: function(position) { 
    136     for (var i = 0; i < this.effects.length; i++) 
    137       this.effects[i].render(position);   
     291    this.effects.invoke('render', position); 
    138292  }, 
    139293  finish: function(position) { 
    140     for (var i = 0; i < this.effects.length; i++) 
    141       if(this.effects[i].finish) this.effects[i].finish(position); 
     294    this.effects.each( function(effect) { 
     295      effect.render(1.0); 
     296      effect.cancel(); 
     297      effect.event('beforeFinish'); 
     298      if(effect.finish) effect.finish(position); 
     299      effect.event('afterFinish'); 
     300    }); 
    142301  } 
    143302}); 
    144303 
    145 // Internet Explorer caveat: works only on elements the have 
    146 // a 'layout', meaning having a given width or height.  
    147 // There is no way to safely set this automatically. 
    148304Effect.Opacity = Class.create(); 
    149305Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { 
    150306  initialize: function(element) { 
    151307    this.element = $(element); 
    152     options = Object.extend({ 
    153       from: 0.0, 
     308    // make this work on IE on elements without 'layout' 
     309    if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout)) 
     310      Element.setStyle(this.element, {zoom: 1}); 
     311    var options = Object.extend({ 
     312      from: Element.getOpacity(this.element) || 0.0, 
    154313      to:   1.0 
    155314    }, arguments[1] || {}); 
     
    157316  }, 
    158317  update: function(position) { 
    159     this.setOpacity(position); 
    160   },  
    161   setOpacity: function(opacity) { 
    162     opacity = (opacity == 1) ? 0.99999 : opacity; 
    163     this.element.style.opacity = opacity; 
    164     this.element.style.filter = "alpha(opacity:"+opacity*100+")"; 
     318    Element.setOpacity(this.element, position); 
    165319  } 
    166320}); 
     
    170324  initialize: function(element, toTop, toLeft) { 
    171325    this.element      = $(element); 
    172     this.originalTop  = parseFloat(this.element.style.top || '0'); 
    173     this.originalLeft = parseFloat(this.element.style.left || '0'); 
    174326    this.toTop        = toTop; 
    175327    this.toLeft       = toLeft; 
     328    this.start(arguments[3]); 
     329  }, 
     330  setup: function() { 
     331    // Bug in Opera: Opera returns the "real" position of a static element or 
     332    // relative element that does not have top/left explicitly set. 
     333    // ==> Always set top and left for position relative elements in your stylesheets  
     334    // (to 0 if you do not need them)  
    176335    Element.makePositioned(this.element); 
    177     this.start(arguments[3]); 
     336    this.originalTop  = parseFloat(Element.getStyle(this.element,'top')  || '0'); 
     337    this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0'); 
    178338  }, 
    179339  update: function(position) { 
    180     topd  = this.toTop  * position + this.originalTop; 
    181     leftd = this.toLeft * position + this.originalLeft; 
    182     this.setPosition(topd, leftd); 
    183   }, 
    184   setPosition: function(topd, leftd) { 
    185     this.element.style.top  = topd  + "px"; 
    186     this.element.style.left = leftd + "px"; 
     340    Element.setStyle(this.element, { 
     341      top:  this.toTop  * position + this.originalTop + 'px', 
     342      left: this.toLeft * position + this.originalLeft + 'px' 
     343    }); 
    187344  } 
    188345}); 
     
    192349  initialize: function(element, percent) { 
    193350    this.element = $(element) 
    194     options = Object.extend({ 
     351    var options = Object.extend({ 
    195352      scaleX: true, 
    196353      scaleY: true, 
     
    198355      scaleFromCenter: false, 
    199356      scaleMode: 'box',        // 'box' or 'contents' or {} with provided values 
    200       scaleFrom: 100.0 
     357      scaleFrom: 100.0, 
     358      scaleTo:   percent 
    201359    }, arguments[2] || {}); 
    202     this.originalTop    = this.element.offsetTop; 
    203     this.originalLeft   = this.element.offsetLeft; 
    204     if(this.element.style.fontSize=="") this.sizeEm = 1.0; 
    205     if(this.element.style.fontSize && this.element.style.fontSize.indexOf("em")>0) 
    206       this.sizeEm      = parseFloat(this.element.style.fontSize); 
    207     this.factor = (percent/100.0) - (options.scaleFrom/100.0); 
    208     if(options.scaleMode=='box') { 
    209       this.originalHeight = this.element.clientHeight; 
    210       this.originalWidth  = this.element.clientWidth;  
    211     } else  
    212     if(options.scaleMode=='contents') { 
    213       this.originalHeight = this.element.scrollHeight; 
    214       this.originalWidth  = this.element.scrollWidth; 
    215     } else { 
    216       this.originalHeight = options.scaleMode.originalHeight; 
    217       this.originalWidth  = options.scaleMode.originalWidth; 
    218     } 
    219360    this.start(options); 
    220361  }, 
    221  
     362  setup: function() { 
     363    this.restoreAfterFinish = this.options.restoreAfterFinish || false; 
     364    this.elementPositioning = Element.getStyle(this.element,'position'); 
     365     
     366    this.originalStyle = {}; 
     367    ['top','left','width','height','fontSize'].each( function(k) { 
     368      this.originalStyle[k] = this.element.style[k]; 
     369    }.bind(this)); 
     370       
     371    this.originalTop  = this.element.offsetTop; 
     372    this.originalLeft = this.element.offsetLeft; 
     373     
     374    var fontSize = Element.getStyle(this.element,'font-size') || '100%'; 
     375    ['em','px','%'].each( function(fontSizeType) { 
     376      if(fontSize.indexOf(fontSizeType)>0) { 
     377        this.fontSize     = parseFloat(fontSize); 
     378        this.fontSizeType = fontSizeType; 
     379      } 
     380    }.bind(this)); 
     381     
     382    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; 
     383     
     384    this.dims = null; 
     385    if(this.options.scaleMode=='box') 
     386      this.dims = [this.element.offsetHeight, this.element.offsetWidth]; 
     387    if(/^content/.test(this.options.scaleMode)) 
     388      this.dims = [this.element.scrollHeight, this.element.scrollWidth]; 
     389    if(!this.dims) 
     390      this.dims = [this.options.scaleMode.originalHeight, 
     391                   this.options.scaleMode.originalWidth]; 
     392  }, 
    222393  update: function(position) { 
    223     currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); 
    224     if(this.options.scaleContent && this.sizeEm)  
    225       this.element.style.fontSize = this.sizeEm*currentScale + "em"; 
    226     this.setDimensions( 
    227       this.originalWidth * currentScale,  
    228       this.originalHeight * currentScale); 
    229   }, 
    230  
    231   setDimensions: function(width, height) { 
    232     if(this.options.scaleX) this.element.style.width = width + 'px'; 
    233     if(this.options.scaleY) this.element.style.height = height + 'px'; 
     394    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); 
     395    if(this.options.scaleContent && this.fontSize) 
     396      Element.setStyle(this.element, {fontSize: this.fontSize * currentScale + this.fontSizeType }); 
     397    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); 
     398  }, 
     399  finish: function(position) { 
     400    if (this.restoreAfterFinish) Element.setStyle(this.element, this.originalStyle); 
     401  }, 
     402  setDimensions: function(height, width) { 
     403    var d = {}; 
     404    if(this.options.scaleX) d.width = width + 'px'; 
     405    if(this.options.scaleY) d.height = height + 'px'; 
    234406    if(this.options.scaleFromCenter) { 
    235       topd  = (height - this.originalHeight)/2; 
    236       leftd = (width  - this.originalWidth)/2; 
    237       if(this.element.style.position=='absolute') { 
    238         if(this.options.scaleY) this.element.style.top = this.originalTop-topd + "px"; 
    239         if(this.options.scaleX) this.element.style.left = this.originalLeft-leftd + "px"; 
     407      var topd  = (height - this.dims[0])/2; 
     408      var leftd = (width  - this.dims[1])/2; 
     409      if(this.elementPositioning == 'absolute') { 
     410        if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; 
     411        if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; 
    240412      } else { 
    241         if(this.options.scaleY) this.element.style.top = -topd + "px"; 
    242         if(this.options.scaleX) this.element.style.left = -leftd + "px"; 
     413        if(this.options.scaleY) d.top = -topd + 'px'; 
     414        if(this.options.scaleX) d.left = -leftd + 'px'; 
    243415      } 
    244416    } 
     417    Element.setStyle(this.element, d); 
    245418  } 
    246419}); 
     
    250423  initialize: function(element) { 
    251424    this.element = $(element); 
    252      
    253     // try to parse current background color as default for endcolor 
    254     // browser stores this as: "rgb(255, 255, 255)", convert to "#ffffff" format 
    255     var endcolor = "#ffffff"; 
    256     var current = this.element.style.backgroundColor; 
    257     if(current && current.slice(0,4) == "rgb(") { 
    258       endcolor = "#"; 
    259       var cols = current.slice(4,current.length-1).split(','); 
    260       var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3); } 
    261        
    262     var options = Object.extend({ 
    263       startcolor:   "#ffff99", 
    264       endcolor:     endcolor, 
    265       restorecolor: current  
    266     }, arguments[1] || {}); 
    267      
     425    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); 
     426    this.start(options); 
     427  }, 
     428  setup: function() { 
     429    // Prevent executing on elements not in the layout flow 
     430    if(Element.getStyle(this.element, 'display')=='none') { this.cancel(); return; } 
     431    // Disable background image during the effect 
     432    this.oldStyle = { 
     433      backgroundImage: Element.getStyle(this.element, 'background-image') }; 
     434    Element.setStyle(this.element, {backgroundImage: 'none'}); 
     435    if(!this.options.endcolor) 
     436      this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff'); 
     437    if(!this.options.restorecolor) 
     438      this.options.restorecolor = Element.getStyle(this.element, 'background-color'); 
    268439    // init color calculations 
    269     this.colors_base = [ 
    270       parseInt(options.startcolor.slice(1,3),16), 
    271       parseInt(options.startcolor.slice(3,5),16), 
    272       parseInt(options.startcolor.slice(5),16) ]; 
    273     this.colors_delta = [ 
    274       parseInt(options.endcolor.slice(1,3),16)-this.colors_base[0], 
    275       parseInt(options.endcolor.slice(3,5),16)-this.colors_base[1], 
    276       parseInt(options.endcolor.slice(5),16)-this.colors_base[2] ]; 
    277  
    278     this.start(options); 
     440    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); 
     441    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); 
    279442  }, 
    280443  update: function(position) { 
    281     var colors = [ 
    282       Math.round(this.colors_base[0]+(this.colors_delta[0]*position)), 
    283       Math.round(this.colors_base[1]+(this.colors_delta[1]*position)), 
    284       Math.round(this.colors_base[2]+(this.colors_delta[2]*position)) ]; 
    285     this.element.style.backgroundColor = "#" + 
    286       colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart(); 
     444    Element.setStyle(this.element,{backgroundColor: $R(0,2).inject('#',function(m,v,i){ 
     445      return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); 
    287446  }, 
    288447  finish: function() { 
    289     this.element.style.backgroundColor = this.options.restorecolor; 
     448    Element.setStyle(this.element, Object.extend(this.oldStyle, { 
     449      backgroundColor: this.options.restorecolor 
     450    })); 
    290451  } 
    291452}); 
     
    295456  initialize: function(element) { 
    296457    this.element = $(element); 
     458    this.start(arguments[1] || {}); 
     459  }, 
     460  setup: function() { 
    297461    Position.prepare(); 
    298462    var offsets = Position.cumulativeOffset(this.element); 
     463    if(this.options.offset) offsets[1] += this.options.offset; 
    299464    var max = window.innerHeight ?  
    300465      window.height - window.innerHeight : 
     
    303468          document.documentElement.clientHeight : document.body.clientHeight); 
    304469    this.scrollStart = Position.deltaY; 
    305     this.delta  = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; 
    306     this.start(arguments[1] || {}); 
     470    this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; 
    307471  }, 
    308472  update: function(position) { 
     
    313477}); 
    314478 
    315 /* ------------- prepackaged effects ------------- */ 
     479/* ------------- combination effects ------------- */ 
    316480 
    317481Effect.Fade = function(element) { 
    318   options = Object.extend({ 
    319   from: 1.0, 
     482  var oldOpacity = Element.getInlineOpacity(element); 
     483  var options = Object.extend({ 
     484  from: Element.getOpacity(element) || 1.0, 
    320485  to:   0.0, 
    321   afterFinish: function(effect)  
    322     { Element.hide(effect.element); 
    323       effect.setOpacity(1); }  
     486  afterFinishInternal: function(effect) { with(Element) {  
     487    if(effect.options.to!=0) return; 
     488    hide(effect.element); 
     489    setStyle(effect.element, {opacity: oldOpacity}); }} 
    324490  }, arguments[1] || {}); 
    325   new Effect.Opacity(element,options); 
     491  return new Effect.Opacity(element,options); 
    326492} 
    327493 
    328494Effect.Appear = function(element) { 
    329   options = Object.extend({ 
    330   from: 0.0, 
     495  var options = Object.extend({ 
     496  from: (Element.getStyle(element, 'display') == 'none' ? 0.0 : Element.getOpacity(element) || 0.0), 
    331497  to:   1.0, 
    332   beforeStart: function(effect)   
    333     { effect.setOpacity(0); 
    334       Element.show(effect.element); }, 
    335   afterUpdate: function(effect)   
    336     { Element.show(effect.element); } 
     498  beforeSetup: function(effect) { with(Element) { 
     499    setOpacity(effect.element, effect.options.from); 
     500    show(effect.element); }} 
    337501  }, arguments[1] || {}); 
    338   new Effect.Opacity(element,options); 
     502  return new Effect.Opacity(element,options); 
    339503} 
    340504 
    341505Effect.Puff = function(element) { 
    342   new Effect.Parallel( 
    343    [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true }),  
    344      new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ],  
    345      { duration: 1.0,  
    346       afterUpdate: function(effect)  
    347        { effect.effects[0].element.style.position = 'absolute'; }, 
    348       afterFinish: function(effect) 
    349        { Element.hide(effect.effects[0].element); } 
    350      } 
     506  element = $(element); 
     507  var oldStyle = { opacity: Element.getInlineOpacity(element), position: Element.getStyle(element, 'position') }; 
     508  return new Effect.Parallel( 
     509   [ new Effect.Scale(element, 200,  
     510      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),  
     511     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],  
     512     Object.extend({ duration: 1.0,  
     513      beforeSetupInternal: function(effect) { with(Element) { 
     514        setStyle(effect.effects[0].element, {position: 'absolute'}); }}, 
     515      afterFinishInternal: function(effect) { with(Element) { 
     516         hide(effect.effects[0].element); 
     517         setStyle(effect.effects[0].element, oldStyle); }} 
     518     }, arguments[1] || {}) 
    351519   ); 
    352520} 
    353521 
    354522Effect.BlindUp = function(element) { 
     523  element = $(element); 
    355524  Element.makeClipping(element); 
    356   new Effect.Scale(element, 0,  
     525  return new Effect.Scale(element, 0,  
    357526    Object.extend({ scaleContent: false,  
    358527      scaleX: false,  
    359       afterFinish: function(effect)  
    360         {  
    361           Element.hide(effect.element); 
    362           Element.undoClipping(effect.element); 
    363         }  
     528      restoreAfterFinish: true, 
     529      afterFinishInternal: function(effect) { with(Element) { 
     530        [hide, undoClipping].call(effect.element); }}  
    364531    }, arguments[1] || {}) 
    365532  ); 
     
    367534 
    368535Effect.BlindDown = function(element) { 
    369   $(element).style.height   = '0px'; 
    370   Element.makeClipping(element); 
    371   Element.show(element); 
    372   new Effect.Scale(element, 100,  
     536  element = $(element); 
     537  var oldHeight = Element.getStyle(element, 'height'); 
     538  var elementDimensions = Element.getDimensions(element); 
     539  return new Effect.Scale(element, 100,  
    373540    Object.extend({ scaleContent: false,  
    374       scaleX: false,  
    375       scaleMode: 'contents', 
     541      scaleX: false, 
    376542      scaleFrom: 0, 
    377       afterFinish: function(effect) { 
    378         Element.undoClipping(effect.element); 
    379       } 
     543      scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, 
     544      restoreAfterFinish: true, 
     545      afterSetup: function(effect) { with(Element) { 
     546        makeClipping(effect.element); 
     547        setStyle(effect.element, {height: '0px'}); 
     548        show(effect.element);  
     549      }},   
     550      afterFinishInternal: function(effect) { with(Element) { 
     551        undoClipping(effect.element); 
     552        setStyle(effect.element, {height: oldHeight}); 
     553      }} 
    380554    }, arguments[1] || {}) 
    381555  ); 
     
    383557 
    384558Effect.SwitchOff = function(element) { 
    385   new Effect.Appear(element, 
    386     { duration: 0.4, 
    387      transition: Effect.Transitions.flicker, 
    388      afterFinish: function(effect) 
    389       { effect.element.style.overflow = 'hidden'; 
    390         new Effect.Scale(effect.element, 1,  
    391          { duration: 0.3, scaleFromCenter: true, 
    392           scaleX: false, scaleContent: false, 
    393           afterUpdate: function(effect) {  
    394            if(effect.element.style.position=="") 
    395              effect.element.style.position = 'relative'; }, 
    396           afterFinish: function(effect) { Element.hide(effect.element); } 
    397          } ) 
    398       } 
    399     } ); 
     559  element = $(element); 
     560  var oldOpacity = Element.getInlineOpacity(element); 
     561  return new Effect.Appear(element, {  
     562    duration: 0.4, 
     563    from: 0, 
     564    transition: Effect.Transitions.flicker, 
     565    afterFinishInternal: function(effect) { 
     566      new Effect.Scale(effect.element, 1, {  
     567        duration: 0.3, scaleFromCenter: true, 
     568        scaleX: false, scaleContent: false, restoreAfterFinish: true, 
     569        beforeSetup: function(effect) { with(Element) { 
     570          [makePositioned,makeClipping].call(effect.element); 
     571        }}, 
     572        afterFinishInternal: function(effect) { with(Element) { 
     573          [hide,undoClipping,undoPositioned].call(effect.element); 
     574          setStyle(effect.element, {opacity: oldOpacity}); 
     575        }} 
     576      }) 
     577    } 
     578  }); 
    400579} 
    401580 
    402581Effect.DropOut = function(element) { 
    403   new Effect.Parallel( 
     582  element = $(element); 
     583  var oldStyle = { 
     584    top: Element.getStyle(element, 'top'), 
     585    left: Element.getStyle(element, 'left'), 
     586    opacity: Element.getInlineOpacity(element) }; 
     587  return new Effect.Parallel( 
    404588    [ new Effect.MoveBy(element, 100, 0, { sync: true }),  
    405       new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ],  
    406     { duration: 0.5,  
    407      afterFinish: function(effect) 
    408        { Element.hide(effect.effects[0].element); }  
    409     }); 
     589      new Effect.Opacity(element, { sync: true, to: 0.0 }) ], 
     590    Object.extend( 
     591      { duration: 0.5, 
     592        beforeSetup: function(effect) { with(Element) { 
     593          makePositioned(effect.effects[0].element); }}, 
     594        afterFinishInternal: function(effect) { with(Element) { 
     595          [hide, undoPositioned].call(effect.effects[0].element); 
     596          setStyle(effect.effects[0].element, oldStyle); }}  
     597      }, arguments[1] || {})); 
    410598} 
    411599 
    412600Effect.Shake = function(element) { 
    413   new Effect.MoveBy(element, 0, 20,  
    414     { duration: 0.05, afterFinish: function(effect) { 
     601  element = $(element); 
     602  var oldStyle = { 
     603    top: Element.getStyle(element, 'top'), 
     604    left: Element.getStyle(element, 'left') }; 
     605  return new Effect.MoveBy(element, 0, 20,  
     606    { duration: 0.05, afterFinishInternal: function(effect) { 
    415607  new Effect.MoveBy(effect.element, 0, -40,  
    416     { duration: 0.1, afterFinish: function(effect) {  
     608    { duration: 0.1, afterFinishInternal: function(effect) { 
    417609  new Effect.MoveBy(effect.element, 0, 40,  
    418     { duration: 0.1, afterFinish: function(effect) {   
     610    { duration: 0.1, afterFinishInternal: function(effect) { 
    419611  new Effect.MoveBy(effect.element, 0, -40,  
    420     { duration: 0.1, afterFinish: function(effect) {   
     612    { duration: 0.1, afterFinishInternal: function(effect) { 
    421613  new Effect.MoveBy(effect.element, 0, 40,  
    422     { duration: 0.1, afterFinish: function(effect) {   
     614    { duration: 0.1, afterFinishInternal: function(effect) { 
    423615  new Effect.MoveBy(effect.element, 0, -20,  
    424     { duration: 0.05, afterFinish: function(effect) {   
    425   }}) }}) }}) }}) }}) }}); 
     616    { duration: 0.05, afterFinishInternal: function(effect) { with(Element) { 
     617        undoPositioned(effect.element); 
     618        setStyle(effect.element, oldStyle); 
     619  }}}) }}) }}) }}) }}) }}); 
    426620} 
    427621 
    428622Effect.SlideDown = function(element) { 
    429623  element = $(element); 
    430   element.style.height   = '0px'; 
    431   Element.makeClipping(element); 
    432624  Element.cleanWhitespace(element); 
    433   Element.makePositioned(element.firstChild); 
    434   Element.show(element); 
    435   new Effect.Scale(element, 100,  
     625  // SlideDown need to have the content of the element wrapped in a container element with fixed height! 
     626  var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom'); 
     627  var elementDimensions = Element.getDimensions(element); 
     628  return new Effect.Scale(element, 100, Object.extend({  
     629    scaleContent: false,  
     630    scaleX: false,  
     631    scaleFrom: 0, 
     632    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, 
     633    restoreAfterFinish: true, 
     634    afterSetup: function(effect) { with(Element) { 
     635      makePositioned(effect.element); 
     636      makePositioned(effect.element.firstChild); 
     637      if(window.opera) setStyle(effect.element, {top: ''}); 
     638      makeClipping(effect.element); 
     639      setStyle(effect.element, {height: '0px'}); 
     640      show(element); }}, 
     641    afterUpdateInternal: function(effect) { with(Element) { 
     642      setStyle(effect.element.firstChild, {bottom: 
     643        (effect.dims[0] - effect.element.clientHeight) + 'px' }); }}, 
     644    afterFinishInternal: function(effect) { with(Element) { 
     645      undoClipping(effect.element);  
     646      undoPositioned(effect.element.firstChild); 
     647      undoPositioned(effect.element); 
     648      setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }} 
     649    }, arguments[1] || {}) 
     650  ); 
     651} 
     652   
     653Effect.SlideUp = function(element) { 
     654  element = $(element); 
     655  Element.cleanWhitespace(element); 
     656  var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom'); 
     657  return new Effect.Scale(element, 0,  
    436658   Object.extend({ scaleContent: false,  
    437659    scaleX: false,  
    438     scaleMode: 'contents', 
    439     scaleFrom: 0, 
    440     afterUpdate: function(effect)  
    441       { effect.element.firstChild.style.bottom =  
    442           (effect.originalHeight - effect.element.clientHeight) + 'px'; }, 
    443     afterFinish: function(effect)  
    444       {  Element.undoClipping(effect.element); } 
    445     }, arguments[1] || {}) 
    446   ); 
    447 } 
    448    
    449 Effect.SlideUp = function(element) { 
    450   element = $(element); 
    451   Element.makeClipping(element); 
    452   Element.cleanWhitespace(element); 
    453   Element.makePositioned(element.firstChild); 
    454   Element.show(element); 
    455   new Effect.Scale(element, 0,  
    456    Object.extend({ scaleContent: false,  
    457     scaleX: false,  
    458     afterUpdate: function(effect)  
    459       { effect.element.firstChild.style.bottom =  
    460           (effect.originalHeight - effect.element.clientHeight) + 'px'; }, 
    461     afterFinish: function(effect) 
    462       {  
    463         Element.hide(effect.element); 
    464         Element.undoClipping(effect.element); 
    465       } 
     660    scaleMode: 'box', 
     661    scaleFrom: 100, 
     662    restoreAfterFinish: true, 
     663    beforeStartInternal: function(effect) { with(Element) { 
     664      makePositioned(effect.element); 
     665      makePositioned(effect.element.firstChild); 
     666      if(window.opera) setStyle(effect.element, {top: ''}); 
     667      makeClipping(effect.element); 
     668      show(element); }},   
     669    afterUpdateInternal: function(effect) { with(Element) { 
     670      setStyle(effect.element.firstChild, {bottom: 
     671        (effect.dims[0] - effect.element.clientHeight) + 'px' }); }}, 
     672    afterFinishInternal: function(effect) { with(Element) { 
     673        [hide, undoClipping].call(effect.element);  
     674        undoPositioned(effect.element.firstChild); 
     675        undoPositioned(effect.element); 
     676        setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }} 
    466677   }, arguments[1] || {}) 
    467678  ); 
    468679} 
    469680 
     681// Bug in opera makes the TD containing this element expand for a instance after finish  
    470682Effect.Squish = function(element) { 
    471  new Effect.Scale(element, 0,  
    472    { afterFinish: function(effect) { Element.hide(effect.element); } }); 
     683  return new Effect.Scale(element, window.opera ? 1 : 0,  
     684    { restoreAfterFinish: true, 
     685      beforeSetup: function(effect) { with(Element) { 
     686        makeClipping(effect.element); }},   
     687      afterFinishInternal: function(effect) { with(Element) { 
     688        hide(effect.element);  
     689        undoClipping(effect.element); }} 
     690  }); 
    473691} 
    474692 
    475693Effect.Grow = function(element) { 
    476694  element = $(element); 
    477   var options = arguments[1] || {}; 
    478    
    479   var originalWidth = element.clientWidth; 
    480   var originalHeight = element.clientHeight; 
    481   element.style.overflow = 'hidden'; 
    482   Element.show(element); 
    483    
    484   var direction = options.direction || 'center'; 
    485   var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; 
    486   var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; 
    487   var opacityTransition = options.opacityTransition || Effect.Transitions.full; 
    488    
     695  var options = Object.extend({ 
     696    direction: 'center', 
     697    moveTransistion: Effect.Transitions.sinoidal, 
     698    scaleTransition: Effect.Transitions.sinoidal, 
     699    opacityTransition: Effect.Transitions.full 
     700  }, arguments[1] || {}); 
     701  var oldStyle = { 
     702    top: element.style.top, 
     703    left: element.style.left, 
     704    height: element.style.height, 
     705    width: element.style.width, 
     706    opacity: Element.getInlineOpacity(element) }; 
     707 
     708  var dims = Element.getDimensions(element);     
    489709  var initialMoveX, initialMoveY; 
    490710  var moveX, moveY; 
    491711   
    492   switch (direction) { 
     712  switch (options.direction) { 
    493713    case 'top-left': 
    494714      initialMoveX = initialMoveY = moveX = moveY = 0;  
    495715      break; 
    496716    case 'top-right': 
    497       initialMoveX = originalWidth; 
     717      initialMoveX = dims.width; 
    498718      initialMoveY = moveY = 0; 
    499       moveX = -originalWidth; 
     719      moveX = -dims.width; 
    500720      break; 
    501721    case 'bottom-left': 
    502722      initialMoveX = moveX = 0; 
    503       initialMoveY = originalHeight; 
    504       moveY = -originalHeight; 
     723      initialMoveY = dims.height; 
     724      moveY = -dims.height; 
    505725      break; 
    506726    case 'bottom-right': 
    507       initialMoveX = originalWidth; 
    508       initialMoveY = originalHeight; 
    509       moveX = -originalWidth; 
    510       moveY = -originalHeight; 
     727      initialMoveX = dims.width; 
     728      initialMoveY = dims.height; 
     729      moveX = -dims.width; 
     730      moveY = -dims.height; 
    511731      break; 
    512732    case 'center': 
    513       initialMoveX = originalWidth / 2; 
    514       initialMoveY = originalHeight / 2; 
    515       moveX = -originalWidth / 2; 
    516       moveY = -originalHeight / 2; 
     733      initialMoveX = dims.width / 2; 
     734      initialMoveY = dims.height / 2; 
     735      moveX = -dims.width / 2; 
     736      moveY = -dims.height / 2; 
    517737      break; 
    518738  } 
    519739   
    520   new Effect.MoveBy(element, initialMoveY, initialMoveX, {  
     740  return new Effect.MoveBy(element, initialMoveY, initialMoveX, {  
    521741    duration: 0.01,  
    522     beforeUpdate: function(effect) { $(element).style.height = '0px'; }, 
    523     afterFinish: function(effect) { 
     742    beforeSetup: function(effect) { with(Element) { 
     743      hide(effect.element); 
     744      makeClipping(effect.element); 
     745      makePositioned(effect.element); 
     746    }}, 
     747    afterFinishInternal: function(effect) { 
    524748      new Effect.Parallel( 
    525         [ new Effect.Opacity(element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }), 
    526           new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }), 
    527           new Effect.Scale(element, 100, {  
    528             scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth },  
    529             sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })], 
    530         options); } 
    531     }); 
     749        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), 
     750          new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: options.moveTransition }), 
     751          new Effect.Scale(effect.element, 100, { 
     752            scaleMode: { originalHeight: dims.height, originalWidth: dims.width },  
     753            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) 
     754        ], Object.extend({ 
     755             beforeSetup: function(effect) { with(Element) { 
     756               setStyle(effect.effects[0].element, {height: '0px'}); 
     757               show(effect.effects[0].element); }}, 
     758             afterFinishInternal: function(effect) { with(Element) { 
     759               [undoClipping, undoPositioned].call(effect.effects[0].element);  
     760               setStyle(effect.effects[0].element, oldStyle); }} 
     761           }, options) 
     762      ) 
     763    } 
     764  }); 
    532765} 
    533766 
    534767Effect.Shrink = function(element) { 
    535768  element = $(element); 
    536   var options = arguments[1] || {}; 
    537    
    538   var originalWidth = element.clientWidth; 
    539   var originalHeight = element.clientHeight; 
    540   element.style.overflow = 'hidden'; 
    541   Element.show(element); 
    542  
    543   var direction = options.direction || 'center'; 
    544   var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; 
    545   var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; 
    546   var opacityTransition = options.opacityTransition || Effect.Transitions.none; 
    547    
     769  var options = Object.extend({ 
     770    direction: 'center', 
     771    moveTransistion: Effect.Transitions.sinoidal, 
     772    scaleTransition: Effect.Transitions.sinoidal, 
     773    opacityTransition: Effect.Transitions.none 
     774  }, arguments[1] || {}); 
     775  var oldStyle = { 
     776    top: element.style.top, 
     777    left: element.style.left, 
     778    height: element.style.height, 
     779    width: element.style.width, 
     780    opacity: Element.getInlineOpacity(element) }; 
     781 
     782  var dims = Element.getDimensions(element); 
    548783  var moveX, moveY; 
    549784   
    550   switch (direction) { 
     785  switch (options.direction) { 
    551786    case 'top-left': 
    552787      moveX = moveY = 0; 
    553788      break; 
    554789    case 'top-right': 
    555       moveX = originalWidth; 
     790      moveX = dims.width; 
    556791      moveY = 0; 
    557792      break; 
    558793    case 'bottom-left': 
    559794      moveX = 0; 
    560       moveY = originalHeight; 
     795      moveY = dims.height; 
    561796      break; 
    562797    case 'bottom-right': 
    563       moveX = originalWidth; 
    564       moveY = originalHeight; 
     798      moveX = dims.width; 
     799      moveY = dims.height; 
    565800      break; 
    566801    case 'center':   
    567       moveX = originalWidth / 2; 
    568       moveY = originalHeight / 2; 
     802      moveX = dims.width / 2; 
     803      moveY = dims.height / 2; 
    569804      break; 
    570805  } 
    571806   
    572   new Effect.Parallel( 
    573     [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }), 
    574       new Effect.Scale(element, 0, { sync: true, transition: moveTransition }), 
    575       new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: scaleTransition }) ], 
    576     options); 
     807  return new Effect.Parallel( 
     808    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), 
     809      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), 
     810      new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: options.moveTransition }) 
     811    ], Object.extend({             
     812         beforeStartInternal: function(effect) { with(Element) { 
     813           [makePositioned, makeClipping].call(effect.effects[0].element) }}, 
     814         afterFinishInternal: function(effect) { with(Element) { 
     815           [hide, undoClipping, undoPositioned].call(effect.effects[0].element); 
     816           setStyle(effect.effects[0].element, oldStyle); }} 
     817       }, options) 
     818  ); 
    577819} 
    578820 
    579821Effect.Pulsate = function(element) { 
     822  element = $(element); 
    580823  var options    = arguments[1] || {}; 
     824  var oldOpacity = Element.getInlineOpacity(element); 
    581825  var transition = options.transition || Effect.Transitions.sinoidal; 
    582826  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; 
    583827  reverser.bind(transition); 
    584   new Effect.Opacity(element,  
    585     Object.extend(Object.extend({  duration: 3.0, 
    586        afterFinish: function(effect) { Element.show(effect.element); } 
     828  return new Effect.Opacity(element,  
     829    Object.extend(Object.extend({  duration: 3.0, from: 0, 
     830      afterFinishInternal: function(effect) { Element.setStyle(effect.element, {opacity: oldOpacity}); } 
    587831    }, options), {transition: reverser})); 
    588832} 
    589833 
    590834Effect.Fold = function(element) { 
    591  $(element).style.overflow = 'hidden'; 
    592  new Effect.Scale(element, 5, Object.extend({    
    593    scaleContent: false, 
    594    scaleTo: 100, 
    595    scaleX: false, 
    596    afterFinish: function(effect) { 
    597    new Effect.Scale(element, 1, {  
    598      scaleContent: false,  
    599      scaleTo: 0, 
    600      scaleY: false, 
    601      afterFinish: function(effect) { Element.hide(effect.element) } }); 
    602  }}, arguments[1] || {})); 
    603 } 
    604  
    605 // old: new Effect.ContentZoom(element, percent) 
    606 // new: Element.setContentZoom(element, percent)  
    607  
    608 Element.setContentZoom = function(element, percent) { 
    609   var element = $(element); 
    610   element.style.fontSize = (percent/100) + "em";   
    611   if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 
    612 } 
     835  element = $(element); 
     836  var oldStyle = { 
     837    top: element.style.top, 
     838    left: element.style.left, 
     839    width: element.style.width, 
     840    height: element.style.height }; 
     841  Element.makeClipping(element); 
     842  return new Effect.Scale(element, 5, Object.extend({    
     843    scaleContent: false, 
     844    scaleX: false, 
     845    afterFinishInternal: function(effect) { 
     846    new Effect.Scale(element, 1, {  
     847      scaleContent: false,  
     848      scaleY: false, 
     849      afterFinishInternal: function(effect) { with(Element) { 
     850        [hide, undoClipping].call(effect.element);  
     851        setStyle(effect.element, oldStyle); 
     852      }} }); 
     853  }}, arguments[1] || {})); 
     854} 
  • trunk/public/javascripts/prototype.js

    r5 r39  
    1 /*  Prototype JavaScript framework, version 1.3.1 
     1/*  Prototype JavaScript framework, version 1.4.0 
    22 *  (c) 2005 Sam Stephenson <sam@conio.net> 
    33 * 
    44 *  THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff 
    5  *  against the source tree, available from the Prototype darcs repository.  
     5 *  against the source tree, available from the Prototype darcs repository. 
    66 * 
    77 *  Prototype is freely distributable under the terms of an MIT-style license. 
     
    1212 
    1313var Prototype = { 
    14   Version: '1.3.1', 
    15   emptyFunction: function() {} 
     14  Version: '1.4.0', 
     15  ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)', 
     16 
     17  emptyFunction: function() {}, 
     18  K: function(x) {return x} 
    1619} 
    1720 
    1821var Class = { 
    1922  create: function() { 
    20     return function() {  
     23    return function() { 
    2124      this.initialize.apply(this, arguments); 
    2225    } 
     
    3336} 
    3437 
    35 Object.prototype.extend = function(object) { 
    36   return Object.extend.apply(this, [this, object]); 
    37 } 
    38  
    39 Function.prototype.bind = function(object) { 
    40   var __method = this; 
     38Object.inspect = function(object) { 
     39  try { 
     40    if (object == undefined) return 'undefined'; 
     41    if (object == null) return 'null'; 
     42    return object.inspect ? object.inspect() : object.toString(); 
     43  } catch (e) { 
     44    if (e instanceof RangeError) return '...'; 
     45    throw e; 
     46  } 
     47} 
     48 
     49Function.prototype.bind = function() { 
     50  var __method = this, args = $A(arguments), object = args.shift(); 
    4151  return function() { 
    42     __method.apply(object, arguments); 
     52    return __method.apply(object, args.concat($A(arguments))); 
    4353  } 
    4454} 
     
    4757  var __method = this; 
    4858  return function(event) { 
    49     __method.call(object, event || window.event); 
    50   } 
    51 } 
    52  
    53 Number.prototype.toColorPart = function() { 
    54   var digits = this.toString(16); 
    55   if (this < 16) return '0' + digits; 
    56   return digits; 
    57 } 
     59    return __method.call(object, event || window.event); 
     60  } 
     61} 
     62 
     63Object.extend(Number.prototype, { 
     64  toColorPart: function() { 
     65    var digits = this.toString(16); 
     66    if (this < 16) return '0' + digits; 
     67    return digits; 
     68  }, 
     69 
     70  succ: function() { 
     71    return this + 1; 
     72  }, 
     73 
     74  times: function(iterator) { 
     75    $R(0, this, true).each(iterator); 
     76    return this; 
     77  } 
     78}); 
    5879 
    5980var Try = { 
     
    91112  onTimerEvent: function() { 
    92113    if (!this.currentlyExecuting) { 
    93       try {  
     114      try { 
    94115        this.currentlyExecuting = true; 
    95         this.callback();  
    96       } finally {  
     116        this.callback(); 
     117      } finally { 
    97118        this.currentlyExecuting = false; 
    98119      } 
     
    111132      element = document.getElementById(element); 
    112133 
    113     if (arguments.length == 1)  
     134    if (arguments.length == 1) 
    114135      return element; 
    115136 
     
    119140  return elements; 
    120141} 
    121  
    122 if (!Array.prototype.push) { 
    123   Array.prototype.push = function() { 
    124                 var startLength = this.length; 
    125                 for (var i = 0; i < arguments.length; i++) 
    126       this[startLength + i] = arguments[i]; 
    127           return this.length; 
    128   } 
    129 } 
    130  
    131 if (!Function.prototype.apply) { 
    132   // Based on code from http://www.youngpup.net/ 
    133   Function.prototype.apply = function(object, parameters) { 
    134     var parameterStrings = new Array(); 
    135     if (!object)     object = window; 
    136     if (!parameters) parameters = new Array(); 
    137      
    138     for (var i = 0; i < parameters.length; i++) 
    139       parameterStrings[i] = 'parameters[' + i + ']'; 
    140      
    141     object.__apply__ = this; 
    142     var result = eval('object.__apply__(' +  
    143       parameterStrings[i].join(', ') + ')'); 
    144     object.__apply__ = null; 
    145      
    146     return result; 
    147   } 
    148 } 
    149  
    150 String.prototype.extend({ 
     142Object.extend(String.prototype, { 
    151143  stripTags: function() { 
    152144    return this.replace(/<\/?[^>]+>/gi, ''); 
     145  }, 
     146 
     147  stripScripts: function() { 
     148    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); 
     149  }, 
     150 
     151  extractScripts: function() { 
     152    var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); 
     153    var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); 
     154    return (this.match(matchAll) || []).map(function(scriptTag) { 
     155      return (scriptTag.match(matchOne) || ['', ''])[1]; 
     156    }); 
     157  }, 
     158 
     159  evalScripts: function() { 
     160    return this.extractScripts().map(eval); 
    153161  }, 
    154162 
     
    163171    var div = document.createElement('div'); 
    164172    div.innerHTML = this.stripTags(); 
    165     return div.childNodes[0].nodeValue; 
    166   } 
    167 }); 
     173    return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; 
     174  }, 
     175 
     176  toQueryParams: function() { 
     177    var pairs = this.match(/^\??(.*)$/)[1].split('&'); 
     178    return pairs.inject({}, function(params, pairString) { 
     179      var pair = pairString.split('='); 
     180      params[pair[0]] = pair[1]; 
     181      return params; 
     182    }); 
     183  }, 
     184 
     185  toArray: function() { 
     186    return this.split(''); 
     187  }, 
     188 
     189  camelize: function() { 
     190    var oStringList = this.split('-'); 
     191    if (oStringList.length == 1) return oStringList[0]; 
     192 
     193    var camelizedString = this.indexOf('-') == 0 
     194      ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) 
     195      : oStringList[0]; 
     196 
     197    for (var i = 1, len = oStringList.length; i < len; i++) { 
     198      var s = oStringList[i]; 
     199      camelizedString += s.charAt(0).toUpperCase() + s.substring(1); 
     200    } 
     201 
     202    return camelizedString; 
     203  }, 
     204 
     205  inspect: function() { 
     206    return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'"; 
     207  } 
     208}); 
     209 
     210String.prototype.parseQuery = String.prototype.toQueryParams; 
     211 
     212var $break    = new Object(); 
     213var $continue = new Object(); 
     214 
     215var Enumerable = { 
     216  each: function(iterator) { 
     217    var index = 0; 
     218    try { 
     219      this._each(function(value) { 
     220        try { 
     221          iterator(value, index++); 
     222        } catch (e) { 
     223          if (e != $continue) throw e; 
     224        } 
     225      }); 
     226    } catch (e) { 
     227      if (e != $break) throw e; 
     228    } 
     229  }, 
     230 
     231  all: function(iterator) { 
     232    var result = true; 
     233    this.each(function(value, index) { 
     234      result = result && !!(iterator || Prototype.K)(value, index); 
     235      if (!result) throw $break; 
     236    }); 
     237    return result; 
     238  }, 
     239 
     240  any: function(iterator) { 
     241    var result = true; 
     242    this.each(function(value, index) { 
     243      if (result = !!(iterator || Prototype.K)(value, index)) 
     244        throw $break; 
     245    }); 
     246    return result; 
     247  }, 
     248 
     249  collect: function(iterator) { 
     250    var results = []; 
     251    this.each(function(value, index) { 
     252      results.push(iterator(value, index)); 
     253    }); 
     254    return results; 
     255  }, 
     256 
     257  detect: function (iterator) { 
     258    var result; 
     259    this.each(function(value, index) { 
     260      if (iterator(value, index)) { 
     261        result = value; 
     262        throw $break; 
     263      } 
     264    }); 
     265    return result; 
     266  }, 
     267 
     268  findAll: function(iterator) { 
     269    var results = []; 
     270    this.each(function(value, index) { 
     271      if (iterator(value, index)) 
     272        results.push(value); 
     273    }); 
     274    return results; 
     275  }, 
     276 
     277  grep: function(pattern, iterator) { 
     278    var results = []; 
     279    this.each(function(value, index) { 
     280      var stringValue = value.toString(); 
     281      if (stringValue.match(pattern)) 
     282        results.push((iterator || Prototype.K)(value, index)); 
     283    }) 
     284    return results; 
     285  }, 
     286 
     287  include: function(object) { 
     288    var found = false; 
     289    this.each(function(value) { 
     290      if (value == object) { 
     291        found = true; 
     292        throw $break; 
     293      } 
     294    }); 
     295    return found; 
     296  }, 
     297 
     298  inject: function(memo, iterator) { 
     299    this.each(function(value, index) { 
     300      memo = iterator(memo, value, index); 
     301    }); 
     302    return memo; 
     303  }, 
     304 
     305  invoke: function(method) { 
     306    var args = $A(arguments).slice(1); 
     307    return this.collect(function(value) { 
     308      return value[method].apply(value, args); 
     309    }); 
     310  }, 
     311 
     312  max: function(iterator) { 
     313    var result; 
     314    this.each(function(value, index) { 
     315      value = (iterator || Prototype.K)(value, index); 
     316      if (value >= (result || value)) 
     317        result = value; 
     318    }); 
     319    return result; 
     320  }, 
     321 
     322  min: function(iterator) { 
     323    var result; 
     324    this.each(function(value, index) { 
     325      value = (iterator || Prototype.K)(value, index); 
     326      if (value <= (result || value)) 
     327        result = value; 
     328    }); 
     329    return result; 
     330  }, 
     331 
     332  partition: function(iterator) { 
     333    var trues = [], falses = []; 
     334    this.each(function(value, index) { 
     335      ((iterator || Prototype.K)(value, index) ? 
     336        trues : falses).push(value); 
     337    }); 
     338    return [trues, falses]; 
     339  }, 
     340 
     341  pluck: function(property) { 
     342    var results = []; 
     343    this.each(function(value, index) { 
     344      results.push(value[property]); 
     345    }); 
     346    return results; 
     347  }, 
     348 
     349  reject: function(iterator) { 
     350    var results = []; 
     351    this.each(function(value, index) { 
     352      if (!iterator(value, index)) 
     353        results.push(value); 
     354    }); 
     355    return results; 
     356  }, 
     357 
     358  sortBy: function(iterator) { 
     359    return this.collect(function(value, index) { 
     360      return {value: value, criteria: iterator(value, index)}; 
     361    }).sort(function(left, right) { 
     362      var a = left.criteria, b = right.criteria; 
     363      return a < b ? -1 : a > b ? 1 : 0; 
     364    }).pluck('value'); 
     365  }, 
     366 
     367  toArray: function() { 
     368    return this.collect(Prototype.K); 
     369  }, 
     370 
     371  zip: function() { 
     372    var iterator = Prototype.K, args = $A(arguments); 
     373    if (typeof args.last() == 'function') 
     374      iterator = args.pop(); 
     375 
     376    var collections = [this].concat(args).map($A); 
     377    return this.map(function(value, index) { 
     378      iterator(value = collections.pluck(index)); 
     379      return value; 
     380    }); 
     381  }, 
     382 
     383  inspect: function() { 
     384    return '#<Enumerable:' + this.toArray().inspect() + '>'; 
     385  } 
     386} 
     387 
     388Object.extend(Enumerable, { 
     389  map:     Enumerable.collect, 
     390  find:    Enumerable.detect, 
     391  select:  Enumerable.findAll, 
     392  member:  Enumerable.include, 
     393  entries: Enumerable.toArray 
     394}); 
     395var $A = Array.from = function(iterable) { 
     396  if (!iterable) return []; 
     397  if (iterable.toArray) { 
     398    return iterable.toArray(); 
     399  } else { 
     400    var results = []; 
     401    for (var i = 0; i < iterable.length; i++) 
     402      results.push(iterable[i]); 
     403    return results; 
     404  } 
     405} 
     406 
     407Object.extend(Array.prototype, Enumerable); 
     408 
     409Array.prototype._reverse = Array.prototype.reverse; 
     410 
     411Object.extend(Array.prototype, { 
     412  _each: function(iterator) { 
     413    for (var i = 0; i < this.length; i++) 
     414      iterator(this[i]); 
     415  }, 
     416 
     417  clear: function() { 
     418    this.length = 0; 
     419    return this; 
     420  }, 
     421 
     422  first: function() { 
     423    return this[0]; 
     424  }, 
     425 
     426  last: function() { 
     427    return this[this.length - 1]; 
     428  }, 
     429 
     430  compact: function() { 
     431    return this.select(function(value) { 
     432      return value != undefined || value != null; 
     433    }); 
     434  }, 
     435 
     436  flatten: function() { 
     437    return this.inject([], function(array, value) { 
     438      return array.concat(value.constructor == Array ? 
     439        value.flatten() : [value]); 
     440    }); 
     441  }, 
     442 
     443  without: function() { 
     444    var values = $A(arguments); 
     445    return this.select(function(value) { 
     446      return !values.include(value); 
     447    }); 
     448  }, 
     449 
     450  indexOf: function(object) { 
     451    for (var i = 0; i < this.length; i++) 
     452      if (this[i] == object) return i; 
     453    return -1; 
     454  }, 
     455 
     456  reverse: function(inline) { 
     457    return (inline !== false ? this : this.toArray())._reverse(); 
     458  }, 
     459 
     460  shift: function() { 
     461    var result = this[0]; 
     462    for (var i = 0; i < this.length - 1; i++) 
     463      this[i] = this[i + 1]; 
     464    this.length--; 
     465    return result; 
     466  }, 
     467 
     468  inspect: function() { 
     469    return '[' + this.map(Object.inspect).join(', ') + ']'; 
     470  } 
     471}); 
     472var Hash = { 
     473  _each: function(iterator) { 
     474    for (key in this) { 
     475      var value = this[key]; 
     476      if (typeof value == 'function') continue; 
     477 
     478      var pair = [key, value]; 
     479      pair.key = key; 
     480      pair.value = value; 
     481      iterator(pair); 
     482    } 
     483  }, 
     484 
     485  keys: function() { 
     486    return this.pluck('key'); 
     487  }, 
     488 
     489  values: function() { 
     490    return this.pluck('value'); 
     491  }, 
     492 
     493  merge: function(hash) { 
     494    return $H(hash).inject($H(this), function(mergedHash, pair) { 
     495      mergedHash[pair.key] = pair.value; 
     496      return mergedHash; 
     497    }); 
     498  }, 
     499 
     500  toQueryString: function() { 
     501    return this.map(function(pair) { 
     502      return pair.map(encodeURIComponent).join('='); 
     503    }).join('&'); 
     504  }, 
     505 
     506  inspect: function() { 
     507    return '#<Hash:{' + this.map(function(pair) { 
     508      return pair.map(Object.inspect).join(': '); 
     509    }).join(', ') + '}>'; 
     510  } 
     511} 
     512 
     513function $H(object) { 
     514  var hash = Object.extend({}, object || {}); 
     515  Object.extend(hash, Enumerable); 
     516  Object.extend(hash, Hash); 
     517  return hash; 
     518} 
     519ObjectRange = Class.create(); 
     520Object.extend(ObjectRange.prototype, Enumerable); 
     521Object.extend(ObjectRange.prototype, { 
     522  initialize: function(start, end, exclusive) { 
     523    this.start = start; 
     524    this.end = end; 
     525    this.exclusive = exclusive; 
     526  }, 
     527 
     528  _each: function(iterator) { 
     529    var value = this.start; 
     530    do { 
     531      iterator(value); 
     532      value = value.succ(); 
     533    } while (this.include(value)); 
     534  }, 
     535 
     536  include: function(value) { 
     537    if (value < this.start) 
     538      return false; 
     539    if (this.exclusive) 
     540      return value < this.end; 
     541    return value <= this.end; 
     542  } 
     543}); 
     544 
     545var $R = function(start, end, exclusive) { 
     546  return new ObjectRange(start, end, exclusive); 
     547} 
    168548 
    169549var Ajax = { 
     
    174554      function() {return new XMLHttpRequest()} 
    175555    ) || false; 
    176   } 
    177 } 
     556  }, 
     557 
     558  activeRequestCount: 0 
     559} 
     560 
     561Ajax.Responders = { 
     562  responders: [], 
     563 
     564  _each: function(iterator) { 
     565    this.responders._each(iterator); 
     566  }, 
     567 
     568  register: function(responderToAdd) { 
     569    if (!this.include(responderToAdd)) 
     570      this.responders.push(responderToAdd); 
     571  }, 
     572 
     573  unregister: function(responderToRemove) { 
     574    this.responders = this.responders.without(responderToRemove); 
     575  }, 
     576 
     577  dispatch: function(callback, request, transport, json) { 
     578    this.each(function(responder) { 
     579      if (responder[callback] && typeof responder[callback] == 'function') { 
     580        try { 
     581          responder[callback].apply(responder, [request, transport, json]); 
     582        } catch (e) {} 
     583      } 
     584    }); 
     585  } 
     586}; 
     587 
     588Object.extend(Ajax.Responders, Enumerable); 
     589 
     590Ajax.Responders.register({ 
     591  onCreate: function() { 
     592    Ajax.activeRequestCount++; 
     593  }, 
     594 
     595  onComplete: function() { 
     596    Ajax.activeRequestCount--; 
     597  } 
     598}); 
    178599 
    179600Ajax.Base = function() {}; 
     
    184605      asynchronous: true, 
    185606      parameters:   '' 
    186     }.extend(options || {}); 
     607    } 
     608    Object.extend(this.options, options || {}); 
    187609  }, 
    188610 
    189611  responseIsSuccess: function() { 
    190612    return this.transport.status == undefined 
    191         || this.transport.status == 0  
     613        || this.transport.status == 0 
    192614        || (this.transport.status >= 200 && this.transport.status < 300); 
    193615  }, 
     
    199621 
    200622Ajax.Request = Class.create(); 
    201 Ajax.Request.Events =  
     623Ajax.Request.Events = 
    202624  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; 
    203625 
    204 Ajax.Request.prototype = (new Ajax.Base()).extend({ 
     626Ajax.Request.prototype = Object.extend(new Ajax.Base(), { 
    205627  initialize: function(url, options) { 
    206628    this.transport = Ajax.getTransport(); 
     
    214636 
    215637    try { 
    216       if (this.options.method == 'get') 
    217         url += '?' + parameters; 
    218  
    219       this.transport.open(this.options.method, url, 
     638      this.url = url; 
     639      if (this.options.method == 'get' && parameters.length > 0) 
     640        this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; 
     641 
     642      Ajax.Responders.dispatch('onCreate', this, this.transport); 
     643 
     644      this.transport.open(this.options.method, this.url, 
    220645        this.options.asynchronous); 
    221646 
     
    231656 
    232657    } catch (e) { 
     658      this.dispatchException(e); 
    233659    } 
    234660  }, 
    235661 
    236662  setRequestHeaders: function() { 
    237     var requestHeaders =  
     663    var requestHeaders = 
    238664      ['X-Requested-With', 'XMLHttpRequest', 
    239665       'X-Prototype-Version', Prototype.Version]; 
    240666 
    241667    if (this.options.method == 'post') { 
    242       requestHeaders.push('Content-type',  
     668      requestHeaders.push('Content-type', 
    243669        'application/x-www-form-urlencoded'); 
    244670 
    245671      /* Force "Connection: close" for Mozilla browsers to work around 
    246672       * a bug where XMLHttpReqeuest sends an incorrect Content-length 
    247        * header. See Mozilla Bugzilla #246651.  
     673       * header. See Mozilla Bugzilla #246651. 
    248674       */ 
    249675      if (this.transport.overrideMimeType) 
     
    264690  }, 
    265691 
     692  header: function(name) { 
     693    try { 
     694      return this.transport.getResponseHeader(name); 
     695    } catch (e) {} 
     696  }, 
     697 
     698  evalJSON: function() { 
     699    try { 
     700      return eval(this.header('X-JSON')); 
     701    } catch (e) {} 
     702  }, 
     703 
     704  evalResponse: function() { 
     705    try { 
     706      return eval(this.transport.responseText); 
     707    } catch (e) { 
     708      this.dispatchException(e); 
     709    } 
     710  }, 
     711 
    266712  respondToReadyState: function(readyState) { 
    267713    var event = Ajax.Request.Events[readyState]; 
    268  
    269     if (event == 'Complete') 
    270       (this.options['on' + this.transport.status] 
    271        || this.options['on' + this.responseIsSuccess() ? 'Success' : 'Failure'] 
    272        || Prototype.emptyFunction)(this.transport); 
    273  
    274     (this.options['on' + event] || Prototype.emptyFunction)(this.transport); 
     714    var transport = this.transport, json = this.evalJSON(); 
     715 
     716    if (event == 'Complete') { 
     717      try { 
     718        (this.options['on' + this.transport.status] 
     719         || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] 
     720         || Prototype.emptyFunction)(transport, json); 
     721      } catch (e) { 
     722        this.dispatchException(e); 
     723      } 
     724 
     725      if ((this.header('Content-type') || '').match(/^text\/javascript/i)) 
     726        this.evalResponse(); 
     727    } 
     728 
     729    try { 
     730      (this.options['on' + event] || Prototype.emptyFunction)(transport, json); 
     731      Ajax.Responders.dispatch('on' + event, this, transport, json); 
     732    } catch (e) { 
     733      this.dispatchException(e); 
     734    } 
    275735 
    276736    /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ 
    277737    if (event == 'Complete') 
    278738      this.transport.onreadystatechange = Prototype.emptyFunction; 
     739  }, 
     740 
     741  dispatchException: function(exception) { 
     742    (this.options.onException || Prototype.emptyFunction)(this, exception); 
     743    Ajax.Responders.dispatch('onException', this, exception); 
    279744  } 
    280745}); 
    281746 
    282747Ajax.Updater = Class.create(); 
    283 Ajax.Updater.ScriptFragment = '(?:<script.*?>)((\n|.)*?)(?:<\/script>)'; 
    284  
    285 Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({ 
     748 
     749Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { 
    286750  initialize: function(container, url, options) { 
    287751    this.containers = { 
     
    295759 
    296760    var onComplete = this.options.onComplete || Prototype.emptyFunction; 
    297     this.options.onComplete = (function() { 
     761    this.options.onComplete = (function(transport, object) { 
    298762      this.updateContent(); 
    299       onComplete(this.transport); 
     763      onComplete(transport, object); 
    300764    }).bind(this); 
    301765 
     
    306770    var receiver = this.responseIsSuccess() ? 
    307771      this.containers.success : this.containers.failure; 
    308  
    309     var match    = new RegExp(Ajax.Updater.ScriptFragment, 'img'); 
    310     var response = this.transport.responseText.replace(match, ''); 
    311     var scripts  = this.transport.responseText.match(match); 
     772    var response = this.transport.responseText; 
     773 
     774    if (!this.options.evalScripts) 
     775      response = response.stripScripts(); 
    312776 
    313777    if (receiver) { 
     
    315779        new this.options.insertion(receiver, response); 
    316780      } else { 
    317         receiver.innerHTML = response; 
     781        Element.update(receiver, response); 
    318782      } 
    319783    } 
     
    321785    if (this.responseIsSuccess()) { 
    322786      if (this.onComplete) 
    323         setTimeout((function() {this.onComplete( 
    324           this.transport)}).bind(this), 10); 
    325     } 
    326  
    327     if (this.options.evalScripts && scripts) { 
    328       match = new RegExp(Ajax.Updater.ScriptFragment, 'im'); 
    329       setTimeout((function() { 
    330         for (var i = 0; i < scripts.length; i++) 
    331           eval(scripts[i].match(match)[1]); 
    332       }).bind(this), 10); 
     787        setTimeout(this.onComplete.bind(this), 10); 
    333788    } 
    334789  } 
     
    336791 
    337792Ajax.PeriodicalUpdater = Class.create(); 
    338 Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({ 
     793Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { 
    339794  initialize: function(container, url, options) { 
    340795    this.setOptions(options); 
     
    342797 
    343798    this.frequency = (this.options.frequency || 2); 
    344     this.decay = 1; 
     799    this.decay = (this.options.decay || 1); 
    345800 
    346801    this.updater = {}; 
     
    359814    this.updater.onComplete = undefined; 
    360815    clearTimeout(this.timer); 
    361     (this.onComplete || Ajax.emptyFunction).apply(this, arguments); 
     816    (this.onComplete || Prototype.emptyFunction).apply(this, arguments); 
    362817  }, 
    363818 
    364819  updateComplete: function(request) { 
    365820    if (this.options.decay) { 
    366       this.decay = (request.responseText == this.lastText ?  
     821      this.decay = (request.responseText == this.lastText ? 
    367822        this.decay * this.options.decay : 1); 
    368823 
    369824      this.lastText = request.responseText; 
    370825    } 
    371     this.timer = setTimeout(this.onTimerEvent.bind(this),  
     826    this.timer = setTimeout(this.onTimerEvent.bind(this), 
    372827      this.decay * this.frequency * 1000); 
    373828  }, 
     
    377832  } 
    378833}); 
    379  
    380 document.getElementsByClassName = function(className) { 
    381   var children = document.getElementsByTagName('*') || document.all; 
    382   var elements = new Array(); 
    383    
    384   for (var i = 0; i < children.length; i++) { 
    385     var child = children[i]; 
    386     var classNames = child.className.split(' '); 
    387     for (var j = 0; j < classNames.length; j++) { 
    388       if (classNames[j] == className) { 
    389         elements.push(child); 
    390         break; 
    391       } 
    392     } 
    393   } 
    394    
    395   return elements; 
     834document.getElementsByClassName = function(className, parentElement) { 
     835  var children = ($(parentElement) || document.body).getElementsByTagName('*'); 
     836  return $A(children).inject([], function(elements, child) { 
     837    if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) 
     838      elements.push(child); 
     839    return elements; 
     840  }); 
    396841} 
    397842 
     
    403848 
    404849Object.extend(Element, { 
     850  visible: function(element) { 
     851    return $(element).style.display != 'none'; 
     852  }, 
     853 
    405854  toggle: function() { 
    406855    for (var i = 0; i < arguments.length; i++) { 
    407856      var element = $(arguments[i]); 
    408       element.style.display =  
    409         (element.style.display == 'none' ? '' : 'none'); 
     857      Element[Element.visible(element) ? 'hide' : 'show'](element); 
    410858    } 
    411859  }, 
     
    429877    element.parentNode.removeChild(element); 
    430878  }, 
    431     
     879 
     880  update: function(element, html) { 
     881    $(element).innerHTML = html.stripScripts(); 
     882    setTimeout(function() {html.evalScripts()}, 10); 
     883  }, 
     884 
    432885  getHeight: function(element) { 
    433886    element = $(element); 
    434     return element.offsetHeight;  
     887    return element.offsetHeight; 
     888  }, 
     889 
     890  classNames: function(element) { 
     891    return new Element.ClassNames(element); 
    435892  }, 
    436893 
    437894  hasClassName: function(element, className) { 
    438     element = $(element); 
    439     if (!element) 
    440       return; 
    441     var a = element.className.split(' '); 
    442     for (var i = 0; i < a.length; i++) { 
    443       if (a[i] == className) 
    444         return true; 
    445     } 
    446     return false; 
     895    if (!(element = $(element))) return; 
     896    return Element.classNames(element).include(className); 
    447897  }, 
    448898 
    449899  addClassName: function(element, className) { 
    450     element = $(element); 
    451     Element.removeClassName(element, className); 
    452     element.className += ' ' + className; 
     900    if (!(element = $(element))) return; 
     901    return Element.classNames(element).add(className); 
    453902  }, 
    454903 
    455904  removeClassName: function(element, className) { 
    456     element = $(element); 
    457     if (!element) 
    458       return; 
    459     var newClassName = ''; 
    460     var a = element.className.split(' '); 
    461     for (var i = 0; i < a.length; i++) { 
    462       if (a[i] != className) { 
    463         if (i > 0) 
    464           newClassName += ' '; 
    465         newClassName += a[i]; 
    466       } 
    467     } 
    468     element.className = newClassName; 
    469   }, 
    470    
     905    if (!(element = $(element))) return; 
     906    return Element.classNames(element).remove(className); 
     907  }, 
     908 
    471909  // removes whitespace-only text node children 
    472910  cleanWhitespace: function(element) { 
    473     var element = $(element); 
     911    element = $(element); 
    474912    for (var i = 0; i < element.childNodes.length; i++) { 
    475913      var node = element.childNodes[i]; 
    476       if (node.nodeType == 3 && !/\S/.test(node.nodeValue))  
     914      if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) 
    477915        Element.remove(node); 
    478916    } 
     917  }, 
     918 
     919  empty: function(element) { 
     920    return $(element).innerHTML.match(/^\s*$/); 
     921  }, 
     922 
     923  scrollTo: function(element) { 
     924    element = $(element); 
     925    var x = element.x ? element.x : element.offsetLeft, 
     926        y = element.y ? element.y : element.offsetTop; 
     927    window.scrollTo(x, y); 
     928  }, 
     929 
     930  getStyle: function(element, style) { 
     931    element = $(element); 
     932    var value = element.style[style.camelize()]; 
     933    if (!value) { 
     934      if (document.defaultView && document.defaultView.getComputedStyle) { 
     935        var css = document.defaultView.getComputedStyle(element, null); 
     936        value = css ? css.getPropertyValue(style) : null; 
     937      } else if (element.currentStyle) { 
     938        value = element.currentStyle[style.camelize()]; 
     939      } 
     940    } 
     941 
     942    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) 
     943      if (Element.getStyle(element, 'position') == 'static') value = 'auto'; 
     944 
     945    return value == 'auto' ? null : value; 
     946  }, 
     947 
     948  setStyle: function(element, style) { 
     949    element = $(element); 
     950    for (name in style) 
     951      element.style[name.camelize()] = style[name]; 
     952  }, 
     953 
     954  getDimensions: function(element) { 
     955    element = $(element); 
     956    if (Element.getStyle(element, 'display') != 'none') 
     957      return {width: element.offsetWidth, height: element.offsetHeight}; 
     958 
     959    // All *Width and *Height properties give 0 on elements with display none, 
     960    // so enable the element temporarily 
     961    var els = element.style; 
     962    var originalVisibility = els.visibility; 
     963    var originalPosition = els.position; 
     964    els.visibility = 'hidden'; 
     965    els.position = 'absolute'; 
     966    els.display = ''; 
     967    var originalWidth = element.clientWidth; 
     968    var originalHeight = element.clientHeight; 
     969    els.display = 'none'; 
     970    els.position = originalPosition; 
     971    els.visibility = originalVisibility; 
     972    return {width: originalWidth, height: originalHeight}; 
     973  }, 
     974 
     975