diff --git a/.gitignore b/.gitignore index 1c58a2b..fd50413 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ .bundle *.lock tryruby/log/* +tmp/* +tmp/* diff --git a/Capfile b/Capfile new file mode 100644 index 0000000..f8a4084 --- /dev/null +++ b/Capfile @@ -0,0 +1,8 @@ +load 'deploy' if respond_to?(:namespace) # cap2 differentiator + +# Uncomment if you are using Rails' asset pipeline +# load 'deploy/assets' + +Dir['vendor/gems/*/recipes/*.rb','vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) } + +load 'config/deploy' # remove this line to skip loading any of the default tasks \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..35a352e --- /dev/null +++ b/Gemfile @@ -0,0 +1,41 @@ +source 'http://rubygems.org' + +gem 'rails', '3.0.9' + +gem 'mysql2', '< 0.3' +gem 'fakefs', '0.2.1', :git => "http://github.com/defunkt/fakefs.git", :ref => "aa0cb96b8ebc81287a2e", :require => 'fakefs/safe' +# Use unicorn as the web server +# gem 'unicorn' +gem 'i18n' +# Deploy with Capistrano +# gem 'capistrano' + +gem 'devise' + +# To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+) +# gem 'ruby-debug' +# gem 'ruby-debug19', :require => 'ruby-debug' +gem 'jquery-rails' +gem 'ruby_parser' +gem 'racc' + +#gem 'ruby2ruby' +#gem 'newrelic_rpm' +#gem 'madmimi' +#gem 'delayed_job' +#gem 'dalli' +# Bundle the extra gems: +# gem 'bj' + +gem 'nokogiri' +# gem 'sqlite3-ruby', :require => 'sqlite3' +# gem 'aws-s3', :require => 'aws/s3' + +# Bundle gems for the local environment. Make sure to +# put test-only gems in this group so their generators +# and rake tasks are available in development mode: +# group :development, :test do + gem 'capybara' + gem 'rspec-rails' + gem 'cucumber-rails' + #gem 'factory-girl' diff --git a/README b/README index 1139d90..fe7013d 100644 --- a/README +++ b/README @@ -1,97 +1,256 @@ -TRYRUBY! is now 1.9 ready and hosted using 1.9. Please note this is beta software. +== Welcome to Rails -Copyright (c) 2009 Andrew McElroy +Rails is a web-application framework that includes everything needed to create +database-backed web applications according to the Model-View-Control pattern. -index.html is Copyright (c) 2009 _why -test.rb, tryruby_runner.rb Copyright (c) 2009 David Miani +This pattern splits the view (also called the presentation) into "dumb" +templates that are primarily responsible for inserting pre-built data in between +HTML tags. The model contains the "smart" domain objects (such as Account, +Product, Person, Post) that holds all the business logic and knows how to +persist themselves to a database. The controller handles the incoming requests +(such as Save New Account, Update Product, Show Post) by manipulating the model +and directing data to the view. +In Rails, the model is handled by what's called an object-relational mapping +layer entitled Active Record. This layer allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in +link:files/vendor/rails/activerecord/README.html. -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: +The controller and view are handled by the Action Pack, which handles both +layers by its two parts: Action View and Action Controller. These two layers +are bundled in a single package due to their heavy interdependence. This is +unlike the relationship between the Active Record and Action Pack that is much +more separate. Each of these packages can be used independently outside of +Rails. You can read more about Action Pack in +link:files/vendor/rails/actionpack/README.html. -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. +== Getting Started -What is TryRuby? --------------------------------------------------------------------------------------- -Try Ruby is a interactive shell that quickly and whimsically teaches the Ruby programming language. Originally _why's idea, it has been recreated from the ground up by Rubists who have a passion for Ruby and for teaching their fellow (wo)man how to program. +1. At the command prompt, create a new Rails application: + rails new myapp (where myapp is the application name) -Please note that If you feel like you are stuck in the middle of a lesson try typing: +2. Change directory to myapp and start the web server: + cd myapp; rails server (run with --help for options) -next +3. Go to http://localhost:3000/ and you'll see: + "Welcome aboard: You're riding Ruby on Rails!" -Doing so will skip to to the next part of the less/next lesson +4. Follow the guidelines to start developing your application. You can find +the following resources handy: -UPDATE: -EXPECT TO SEE THIS CHANGE IN THE NEXT FEW DAYS. I AM LOOKING INTO A SQLITE STORE OPTION. -The sessions are now stored in a tmp folder. -For the time being it is within the htdoc path. I know this is bad. -I have included an index.html that redirects you back to the home page in the mean time. -Ideally, I need to put this some place like /var/logs/tryruby. +* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html +* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ +== Debugging Rails -from there I will see about the viability of making this use the r bridge so you -can have ruby to R lessons. :-) +Sometimes your application goes wrong. Fortunately there are a lot of tools that +will help you debug it and get it back on the rails. -Explaination of /irb.cgi -======================== -The only part of the TryRuby implementation that wasn't able to be recovered from archive.org is the /irb file (or /irb.cgi currently, you can change the name of the file used in js/irb.js). This script should: - - Take one GET param "cmd", which will contain a single line of ruby to run. It can also be "!INIT!IRB", which is called at the start of the session. The current implementation of irb.cgi ignores this. "reset" should also do something special (but doesn't atm). - - Return the output, optionally formatted using normal shell escapes (eg "\033[1;33mThis appears orange") +First area to check is the application log files. Have "tail -f" commands +running on the server.log and development.log. Rails will automatically display +debugging and runtime information to these files. Debugging info will also be +shown in the browser on requests from 127.0.0.1. -The output has four main formats for returning a result, error output, javascript function and line continuation. This is used by the help system so that it can detect when a user has entered the next step for the tutorial. +You can also log your own messages directly into the log file from your code +using the Ruby logger class from inside your controllers. Example: -Output results should be formatted with a "=> " at the front of the output. For example: /irb.cgi?cmd=3*4 should output "=> 12". Shell escape codes can be used to give the output different colors, for example "=> \033[1;20m12". This will automatically be removed when used with the help system. Note that you don't and shouldn't terminate these shell escapes with \033[m (like with a normal shell). + class WeblogController < ActionController::Base + def destroy + @weblog = Weblog.find(params[:id]) + @weblog.destroy + logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") + end + end -Standard output should not be prefixed by =>, so /irb.cgi?cmd=puts(44) should output "44\n=> nil". Here 44 is output, and nil is returned. Again shell escapes can be used. +The result will be a message in your log file along the lines of: -Errors should be formated just like standard output, however it should have no return. For example, /irb.cgi?cmd=non_existant_function should output something like "\033[1;33mNameError: undefined local variable or method `non_existant_function' for main:Object". - -Javascript functions (such as Popup.goto) should use the format "\033[1;JsmJAVASCRIPT CODE\033[m". The javascript cod will then be run. For example, /irb.cgi?cmd=Popup.goto("http://www.google.com") should output \033[1;JSmwindow.irb.options.popup_goto("http://www.google.com")\033[m. - -Finally, if the command isn't finished (eg "def myfunc"), then ".." should be returned. Eg /irb.cgi?cmd=def%20myfunc should return "..". - - -How this works with the help system: -==================================== -The help system works with the file /tutorials/intro.html . There is a
section for each part of the tutorial. Most of it is just the text for that part of the tutorial. However, there is also a
regex
section. If this matches against the output for a command, then the tutorial will go to the next step. - -When the class is "answer", then it will match against lines beginning with "=>". So
\d+
will match any code that returns a number. Other output can be shown as well, eg "someoutput\n=> 42" will match this. The exact regexp is '^\s*=> match_regex_from_help\s*$'. So the line must be an exact match, other than spaces and the initial => - -When the class is "stdout", the match must work on a complete line. Eg
hello
will match "hello", or "start\n hello\ngoodbye" (leading spaces are ignored, and multiple lines are searched. The exact regex is '^\s*match_regex_from_help$' - -As a special case, if the tutorial expects that a command isn't finished (such as with def myfunc), then it
..
will be used. This is changed from the original implementation _why used, which was
as I couldn't get it to work like that. - -Another special case, if the return should some javascript code, then something like '
\033\[1;JSm.*popup_goto\(.*\)\033\[m.*
' will be used - - - -Current Implementation -====================== -The current implementation doesn't use persistent processes like irb. What it does is it stores all previous successfully run commands in session['previous_commands'], and runs them all first (disabling stdout). Then it will finally run the command. It uses a primitive method of detecting incomplete statements (with the function unfinished_statement?) and when a incomplete statement is finished (finished_statement?). The current nesting level (eg "class X" then "def myfunc" will having a nesting level of 2) is stored in $session['nesting_level']. All the lines of the current incomplete statement are stored in $session['current_statement']. - -To work with javascript functions, a special class JavascriptResult was written, with one accessor :js. If the result of an eval returns this object, then the output will be formatted in the javascript method. + Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! +More information on how to use the logger is at http://www.ruby-doc.org/core/ +Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are +several books available online as well: +* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) +* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) +These two books will bring you up to speed on the Ruby language and also on +programming in general. +== Debugger +Debugger support is available through the debugger command when you start your +Mongrel or WEBrick server with --debugger. This means that you can break out of +execution at any point in the code, investigate and change the model, and then, +resume execution! You need to install ruby-debug to run the server in debugging +mode. With gems, use sudo gem install ruby-debug. Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.find(:all) + debugger + end + end + +So the controller will accept the action, run the first line, then present you +with a IRB prompt in the server window. Here you can do things like: + + >> @posts.inspect + => "[#nil, "body"=>nil, "id"=>"1"}>, + #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" + >> @posts.first.title = "hello from a debugger" + => "hello from a debugger" + +...and even better, you can examine how your runtime objects actually work: + + >> f = @posts.first + => #nil, "body"=>nil, "id"=>"1"}> + >> f. + Display all 152 possibilities? (y or n) + +Finally, when you're ready to resume execution, you can enter "cont". + + +== Console + +The console is a Ruby shell, which allows you to interact with your +application's domain model. Here you'll have all parts of the application +configured, just like it is when the application is running. You can inspect +domain models, change values, and save to the database. Starting the script +without arguments will launch it in the development environment. +To start the console, run rails console from the application +directory. + +Options: + +* Passing the -s, --sandbox argument will rollback any modifications + made to the database. +* Passing an environment name as an argument will load the corresponding + environment. Example: rails console production. + +To reload your controllers and models after launching the console run +reload! + +More information about irb can be found at: +link:http://www.rubycentral.com/pickaxe/irb.html + + +== dbconsole + +You can go to the command line of your database directly through rails +dbconsole. You would be connected to the database with the credentials +defined in database.yml. Starting the script without arguments will connect you +to the development database. Passing an argument will connect you to a different +database, like rails dbconsole production. Currently works for MySQL, +PostgreSQL and SQLite 3. + +== Description of Contents + +The default directory structure of a generated Ruby on Rails application: + + |-- app + | |-- controllers + | |-- helpers + | |-- mailers + | |-- models + | `-- views + | `-- layouts + |-- config + | |-- environments + | |-- initializers + | `-- locales + |-- db + |-- doc + |-- lib + | `-- tasks + |-- log + |-- public + | |-- images + | |-- javascripts + | `-- stylesheets + |-- script + |-- test + | |-- fixtures + | |-- functional + | |-- integration + | |-- performance + | `-- unit + |-- tmp + | |-- cache + | |-- pids + | |-- sessions + | `-- sockets + `-- vendor + `-- plugins + +app + Holds all the code that's specific to this particular application. + +app/controllers + Holds controllers that should be named like weblogs_controller.rb for + automated URL mapping. All controllers should descend from + ApplicationController which itself descends from ActionController::Base. + +app/models + Holds models that should be named like post.rb. Models descend from + ActiveRecord::Base by default. + +app/views + Holds the template files for the view that should be named like + weblogs/index.html.erb for the WeblogsController#index action. All views use + eRuby syntax by default. + +app/views/layouts + Holds the template files for layouts to be used with views. This models the + common header/footer method of wrapping views. In your views, define a layout + using the layout :default and create a file named default.html.erb. + Inside default.html.erb, call <% yield %> to render the view using this + layout. + +app/helpers + Holds view helpers that should be named like weblogs_helper.rb. These are + generated for you automatically when using generators for controllers. + Helpers can be used to wrap functionality for your views into methods. + +config + Configuration files for the Rails environment, the routing map, the database, + and other dependencies. + +db + Contains the database schema in schema.rb. db/migrate contains all the + sequence of Migrations for your schema. + +doc + This directory is where your application documentation will be stored when + generated using rake doc:app + +lib + Application specific libraries. Basically, any kind of custom code that + doesn't belong under controllers, models, or helpers. This directory is in + the load path. + +public + The directory available for the web server. Contains subdirectories for + images, stylesheets, and javascripts. Also contains the dispatchers and the + default HTML files. This should be set as the DOCUMENT_ROOT of your web + server. + +script + Helper scripts for automation and generation. + +test + Unit and functional tests along with fixtures. When using the rails generate + command, template test files will be generated for you and placed in this + directory. + +vendor + External libraries that the application depends on. Also includes the plugins + subdirectory. If the app has frozen rails, those gems also go here, under + vendor/rails/. This directory is in the load path. diff --git a/tryruby/Rakefile b/Rakefile similarity index 57% rename from tryruby/Rakefile rename to Rakefile index 3bb0e85..1e7daf6 100644 --- a/tryruby/Rakefile +++ b/Rakefile @@ -1,10 +1,7 @@ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require(File.join(File.dirname(__FILE__), 'config', 'boot')) - +require File.expand_path('../config/application', __FILE__) require 'rake' -require 'rake/testtask' -require 'rake/rdoctask' -require 'tasks/rails' +Tryruby::Application.load_tasks diff --git a/tryruby/app/controllers/application_controller.rb b/app/controllers/application_controller.rb similarity index 51% rename from tryruby/app/controllers/application_controller.rb rename to app/controllers/application_controller.rb index 02c13e5..c0bea36 100644 --- a/tryruby/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,15 +1,38 @@ # Filters added to this controller apply to all controllers in the application. # Likewise, all the methods added will be available for all controllers. +#require File.dirname(__FILE__) + '/../../lib/tryruby' class ApplicationController < ActionController::Base + + + + + layout 'tryruby' + +# attr_accessor :past_commands, :current_statement, :start_time + + +# attr_accessor :past_commands, :current_statement, :start_time + + + + + +=begin + #attr_accessor :session + TryRuby.session = session + + TryRuby.session['start_time'] ||= Time.now + TryRuby.session['current_statement'] ||= '' + TryRuby.session['past_commands'] ||= '' + helper :all # include all helpers, all the time protect_from_forgery # See ActionController::RequestForgeryProtection for details # Scrub sensitive parameters from your log # filter_parameter_logging :password #class << self - attr_accessor :session - TryRuby.session = TryRuby::Session.new +=end # not needed #end end diff --git a/app/controllers/classic_controller.rb b/app/controllers/classic_controller.rb new file mode 100644 index 0000000..019e234 --- /dev/null +++ b/app/controllers/classic_controller.rb @@ -0,0 +1,2 @@ +class ClassicController < ApplicationController +end diff --git a/app/controllers/index_controller.rb b/app/controllers/index_controller.rb new file mode 100644 index 0000000..6a4089a --- /dev/null +++ b/app/controllers/index_controller.rb @@ -0,0 +1,8 @@ +class IndexController < ApplicationController + def terminal + end + + def index + + end +end diff --git a/app/controllers/irb_controller.rb b/app/controllers/irb_controller.rb new file mode 100644 index 0000000..f296b07 --- /dev/null +++ b/app/controllers/irb_controller.rb @@ -0,0 +1,85 @@ +class IrbController < ApplicationController + # GET /irb + # GET /irb.xml + def index + + @irb = [] + + respond_to do |format| + format.html # index.html.erb + format.to_json + format.xml { render :xml => @irb } + end + end + + # GET /irb/1 + # GET /irb/1.xml + def show + @irb = Irb.find(params[:id]) + + respond_to do |format| + format.html # show.html.erb + format.xml { render :xml => @irb } + end + end + + # GET /irb/new + # GET /irb/new.xml + def new + @irb = Irb.new + + respond_to do |format| + format.html # new.html.erb + format.xml { render :xml => @irb } + end + end + + # GET /irb/1/edit + def edit + @irb = Irb.find(params[:id]) + end + + # POST /irb + # POST /irb.xml + def create + @irb = Irb.new(params[:irb]) + + respond_to do |format| + if @irb.save + format.html { redirect_to(@irb, :notice => 'Irb was successfully created.') } + format.xml { render :xml => @irb, :status => :created, :location => @irb } + else + format.html { render :action => "new" } + format.xml { render :xml => @irb.errors, :status => :unprocessable_entity } + end + end + end + + # PUT /irb/1 + # PUT /irb/1.xml + def update + @irb = Irb.find(params[:id]) + + respond_to do |format| + if @irb.update_attributes(params[:irb]) + format.html { redirect_to(@irb, :notice => 'Irb was successfully updated.') } + format.xml { head :ok } + else + format.html { render :action => "edit" } + format.xml { render :xml => @irb.errors, :status => :unprocessable_entity } + end + end + end + + # DELETE /irb/1 + # DELETE /irb/1.xml + def destroy + @irb = Irb.find(params[:id]) + @irb.destroy + + respond_to do |format| + format.html { redirect_to(irb_url) } + format.xml { head :ok } + end + end +end diff --git a/app/controllers/public_controller.rb b/app/controllers/public_controller.rb new file mode 100644 index 0000000..2857026 --- /dev/null +++ b/app/controllers/public_controller.rb @@ -0,0 +1,2 @@ +class PublicController < ApplicationController +end diff --git a/app/controllers/tryruby_controller.rb b/app/controllers/tryruby_controller.rb new file mode 100644 index 0000000..ec763b7 --- /dev/null +++ b/app/controllers/tryruby_controller.rb @@ -0,0 +1,42 @@ + +class TryrubyController < ApplicationController + + layout 'tryruby' + def index + end + + def run + eval(params[:cmd]) + + #@cmd=params[:cmd] + # @a= run_script(@cmd) + # @b = "handleJSON({\"type\": #{@a.type.to_json}, \"output\":#{@a.output.to_json},\"result\":#{@a.result.inspect.to_json}, \"error\": #{@a.error.inspect.to_json}})" + +begin + render :json => @b + rescue + end + end + + + def run_script(command) + #output = begin + eval(command) + # rescue StandardError => e + # e.message + ". On the " + begin + # Tryrubyengine.session ||= TRSession.new + #TryRuby.run_line(TryRuby.session.cgi['cmd']).format + #Tryrubyengine.new + # @c= Tryrubyengine.session + # @c.inspect + # Tryrubyengine.run_line(command) + rescue + + end + # end + + # return "=> #{output}" + ", says yoda" + end + +end diff --git a/tryruby/app/controllers/tutorials_controller.rb b/app/controllers/tutorials_controller.rb similarity index 100% rename from tryruby/app/controllers/tutorials_controller.rb rename to app/controllers/tutorials_controller.rb diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000..d995b05 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,18 @@ +module ApplicationHelper + + def google_analytics_js + ua_code = "UA-2365371-3" + '' + + end + +end diff --git a/app/helpers/classic_helper.rb b/app/helpers/classic_helper.rb new file mode 100644 index 0000000..e85110a --- /dev/null +++ b/app/helpers/classic_helper.rb @@ -0,0 +1,2 @@ +module ClassicHelper +end diff --git a/app/helpers/index_helper.rb b/app/helpers/index_helper.rb new file mode 100644 index 0000000..cdc64c7 --- /dev/null +++ b/app/helpers/index_helper.rb @@ -0,0 +1,2 @@ +module IndexHelper +end diff --git a/app/helpers/irb_helper.rb b/app/helpers/irb_helper.rb new file mode 100644 index 0000000..6c9f3c9 --- /dev/null +++ b/app/helpers/irb_helper.rb @@ -0,0 +1,2 @@ +module IrbHelper +end diff --git a/app/helpers/public_helper.rb b/app/helpers/public_helper.rb new file mode 100644 index 0000000..0d8e188 --- /dev/null +++ b/app/helpers/public_helper.rb @@ -0,0 +1,2 @@ +module PublicHelper +end diff --git a/app/helpers/tutorials_helper.rb b/app/helpers/tutorials_helper.rb new file mode 100644 index 0000000..30716fd --- /dev/null +++ b/app/helpers/tutorials_helper.rb @@ -0,0 +1,2 @@ +module TutorialsHelper +end diff --git a/tryruby/log/production.log b/app/views/index/index.html.erb similarity index 100% rename from tryruby/log/production.log rename to app/views/index/index.html.erb diff --git a/tryruby/log/server.log b/app/views/index/terminal.html.erb similarity index 100% rename from tryruby/log/server.log rename to app/views/index/terminal.html.erb diff --git a/app/views/irb/_form.html.erb b/app/views/irb/_form.html.erb new file mode 100644 index 0000000..4e3cdae --- /dev/null +++ b/app/views/irb/_form.html.erb @@ -0,0 +1,17 @@ +<%= form_for(@irb) do |f| %> + <% if @irb.errors.any? %> +
+

<%= pluralize(@irb.errors.count, "error") %> prohibited this irb from being saved:

+ +
    + <% @irb.errors.full_messages.each do |msg| %> +
  • <%= msg %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= f.submit %> +
+<% end %> diff --git a/app/views/irb/edit.html.erb b/app/views/irb/edit.html.erb new file mode 100644 index 0000000..9e50dc3 --- /dev/null +++ b/app/views/irb/edit.html.erb @@ -0,0 +1,6 @@ +

Editing irb

+ +<%= render 'form' %> + +<%= link_to 'Show', @irb %> | +<%= link_to 'Back', irb_path %> diff --git a/app/views/irb/index.html.erb b/app/views/irb/index.html.erb new file mode 100644 index 0000000..90b773b --- /dev/null +++ b/app/views/irb/index.html.erb @@ -0,0 +1,21 @@ +

Listing irb

+ + + + + + + + +<% @irb.each do |irb| %> + + + + + +<% end %> +
<%= link_to 'Show', irb %><%= link_to 'Edit', edit_irb_path(irb) %><%= link_to 'Destroy', irb, :confirm => 'Are you sure?', :method => :delete %>
+ +
+ +<%= link_to 'New Irb', new_irb_path %> diff --git a/app/views/irb/new.html.erb b/app/views/irb/new.html.erb new file mode 100644 index 0000000..9a7b635 --- /dev/null +++ b/app/views/irb/new.html.erb @@ -0,0 +1,5 @@ +

New irb

+ +<%= render 'form' %> + +<%= link_to 'Back', irb_path %> diff --git a/app/views/irb/show.html.erb b/app/views/irb/show.html.erb new file mode 100644 index 0000000..c271c01 --- /dev/null +++ b/app/views/irb/show.html.erb @@ -0,0 +1,5 @@ +

<%= notice %>

+ + +<%= link_to 'Edit', edit_irb_path(@irb) %> | +<%= link_to 'Back', irb_path %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000..62a6614 --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,182 @@ + + + + + + + + + + + try ruby! (in your browser) + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

A Popup Browser

+

[x]

+
+ +
+
+ +
+
+ <%= yield %> +
+
+
+
+
+

Got 15 minutes? Give Ruby a shot right now!

+

Ruby is a programming language from Japan + (available at ruby-lang.org) + which is revolutionizing the web. + The beauty of Ruby is found in its balance between simplicity and power.

+ +

Try out Ruby code in the prompt above. In addition + to Ruby's builtin methods, the following commands are available:

+
    +
  • help + Start the 15 minute interactive tutorial. Trust me, it's very basic!
  • +
  • help 2 + Hop to chapter two.
  • + +
  • clear + Clear screen. Useful if your browser starts slowing down. + Your command history will be remembered. +
  • back + Go back one screen in the tutorial.
  • +
  • reset + Reset the interpreter if you get too deep. (or Ctrl-D!)
  • +
  • next +Allows you to skip to the next section of a lesson.
  • + +
  • time + A stopwatch. Prints the time your session has been open.
  • +
+

If you happen to leave or refresh the page, your session will still be here for + unless it is left inactive for ten minutes.

+
+
+
+ +
+
Trapped in double dots? A quote or something was left open. Type: reset or hit Ctrl-D.
+
+ +

This place was sired by why the lucky stiff. + Please contact me using the email address at that link.is maintained by Andrew McElroy and David Miani. For support issues, please post a ticket or contact Sophrinix on github. +

+ +
+ + + + + + + + +Please Support
Try Ruby!
+ + + diff --git a/tryruby/public/favicon.ico b/app/views/layouts/blank.html.erb similarity index 100% rename from tryruby/public/favicon.ico rename to app/views/layouts/blank.html.erb diff --git a/tryruby/app/views/layouts/tryruby.rhtml b/app/views/layouts/tryruby.rhtml similarity index 52% rename from tryruby/app/views/layouts/tryruby.rhtml rename to app/views/layouts/tryruby.rhtml index 7d7fadc..9ab71c1 100644 --- a/tryruby/app/views/layouts/tryruby.rhtml +++ b/app/views/layouts/tryruby.rhtml @@ -2,90 +2,85 @@ - - try ruby! (in your browser) + try ruby! (en tu navegador) - - - + - +
- +

A Popup Browser

[x]

- <%= yield %> - - +
- +
-

Got 15 minutes? Give Ruby a shot right now!

-

Ruby is a programming language from Japan - (available at ruby-lang.org) - which is revolutionizing the web. - The beauty of Ruby is found in its balance between simplicity and power.

- -

Try out Ruby code in the prompt above. In addition - to Ruby's builtin methods, the following commands are available:

+

¿Tienes 15 minutos? ¡Prueba Ruby ahora mismo!

+

Ruby es un lenguaje de programación de Japón + (disponible en ruby-lang.org) + que está revolucionando la web. + La belleza de Ruby se encuentra en su balance entre la simplicidad y el poder.

+ +

Prueba código Ruby en el prompt de arriba. Además de los métodos + originales de Ruby, los siguientes comandos están disponibles:

  • help - Start the 15 minute interactive tutorial. Trust me, it's very basic!
  • + Empieza el tutorial interactivo de 15 minutos. ¡Creeme, es muy básico!
  • help 2 - Hop to chapter two.
  • - + Salta al capítulo 2. +
  • clear - Clear screen. Useful if your browser starts slowing down. - Your command history will be remembered. + Limpia la pantalla. Útil si tu navegador empieza a alerdarce. + Tu historial de comandos será recordado.
  • back - Go back one screen in the tutorial.
  • + Retrocede una pantalla en el tutorial.
  • reset - Reset the interpreter if you get too deep. (or Ctrl-D!)
  • -
  • next -Allows you to skip to the next section of a lesson.
  • - + Resetea el interprete. (o Ctrl-D!) +
  • next + Te permite saltear la siguiente lección
  • +
  • time - A stopwatch. Prints the time your session has been open.
  • + Detiene el reloj. Imprime cuanto tiempo tu sesión estuvo abierta.
-

If you happen to leave or refresh the page, your session will still be here for - unless it is left inactive for ten minutes.

+

Si te pasa de dejar o refrescar la página, tu sesión seguirá aquí a menos que + se deje inactiva por diez minutos.


- +
-
Trapped in double dots? A quote or something was left open. Type: reset or hit Ctrl-D.
+
¿Atrapado en los dos puntos? Unas comillas o algo fue dejado abierto. Escribe: reset o aprieta Ctrl-D.
-

This place is maintained by Andrew McElroy and David Miani and Orangea. For support issues, please post a ticket or contact Sophrinix on github. +

This place was sired by why the lucky stiff. + Please contact me using the email address at that link.is maintained by Andrew McElroy and David Miani. For support issues, please post a ticket or contact Sophrinix on github.
Por asuntos de traducción, mandar un ticket o contactarse con Cristian Re (leizzer) en Github.

- +
- - + - + diff --git a/tryruby/app/views/layouts/tryruby2.html.erb b/app/views/layouts/tryruby_2.html.erb similarity index 100% rename from tryruby/app/views/layouts/tryruby2.html.erb rename to app/views/layouts/tryruby_2.html.erb diff --git a/app/views/tryruby/_donate.html.erb b/app/views/tryruby/_donate.html.erb new file mode 100644 index 0000000..f7095b1 --- /dev/null +++ b/app/views/tryruby/_donate.html.erb @@ -0,0 +1,41 @@ + \ No newline at end of file diff --git a/app/views/tryruby/index.html.erb b/app/views/tryruby/index.html.erb new file mode 100644 index 0000000..e69de29 diff --git a/app/views/tryruby/run.html.erb b/app/views/tryruby/run.html.erb new file mode 100644 index 0000000..e69de29 diff --git a/tryruby/app/views/tutorials/es_intro.html b/app/views/tutorials/es_intro.html similarity index 100% rename from tryruby/app/views/tutorials/es_intro.html rename to app/views/tutorials/es_intro.html diff --git a/app/views/tutorials/intro.html.erb b/app/views/tutorials/intro.html.erb new file mode 100644 index 0000000..56c1d1c --- /dev/null +++ b/app/views/tutorials/intro.html.erb @@ -0,0 +1,680 @@ + +
+

Using the Prompt

+

The blue window above is a Ruby prompt. Type a line of Ruby code, hit Enter + and watch it run!

+

For example, try typing some math. Like: 2 + 6

+
\d+
+
+
+

Numbers & Math

+

Good! You did a bit of math. See how the answer popped out?

+

Ruby recognizes numbers and mathematic symbols. You could try some other math like:

+
  • 4 * 10
  • +
  • 5 - 12
  • +
  • 40 / 4
+

Sure, computers are handy and fast for math. Let's move on. Want to see your name reversed? + Type your first name in quotes like this: "Jimmy"

+
"(\w+)"
+
+
+

Say Your Name Backwards

+

Perfect, you've formed a string from the letters of your name. A string + is a set of characters the computer can process.

+

Imagine the letters are on a string of + laundry line and the quotes are clothespins holding the ends. The quotes mark the beginning and end.

+

To reverse your name, type: "Jimmy".reverse (Don't forget the dot!)

+
"(\w+)"
+
+
+

Counting the Letters

+

You have used the reverse method on your name! By enclosing your name in + quotes, you made a string. Then you called the reverse method, which works on strings to flip + all the letters backwards.

+

Now, let's see how many letters are in your name: "Jimmy".length

+
\d+
+
+
+

On Repeat

+

Now, I'm sure by now you're wondering what any of this is good for. Well, I'm sure you've been to + a website that screamed, Hey, your password is too short! See, some programs + use this simple code.

+

Watch this. Let's multiply your name by 5. "Jimmy" * 5

+
"(\w+)"
+
+
+

Hey, Summary #1 Already

+

Let's look at what you've learned in the first minute.

+
    +
  • The prompt. Typing code into the green prompt gives you + an answer from a red prompt. All code gives an answer.
  • +
  • Numbers and strings are Ruby's math and text objects.
  • +
  • Methods. You've used English-language methods like reverse + and symbolic methods like * (the multiplication method.) Methods are action!
  • +
+

This is the essence of your learning. Taking simple things, toying with + them and turning them into new things. Feeling comfortable yet? I promise you are.

+

Okay, let's do something uncomfortable. Try reversing a number: 40.reverse

+
NoMethodError: undefined method `reverse' for (\d+):Fixnum
+
+
+

Stop, You're Barking Mad!

+

You can't reverse the number forty. I guess you can hold your monitor up to the + mirror, but reversing a number just doesn't make sense. Ruby has tossed an error + message. Ruby is telling you there is no method reverse for numbers.

+

Maybe if you turn it into a string: 40.to_s.reverse.

+
\"(\d+)\"
+
+
+

Boys are Different From Girls

+

And numbers are different from strings. While you can use methods on any object + in Ruby, some methods only work on certain types of things. But you can always + convert between different types using Ruby's "to" methods.

+
  • to_s converts things to strings.
  • +
  • to_i converts things to integers (numbers.)
  • +
  • to_a converts things to arrays.
  • +
+

What are arrays?! They are lists. Type in a pair of brackets: [].

+
\[\]
+
+
+

Standing in Line

+

Great, that's an empty list. Lists store things in order. + Like standing in line for popcorn. You are behind someone and you wouldn't + dream of pushing them aside, right? And the guy behind you, you've got a + close eye on him, right?

+

Here's a list for you. Lottery numbers: [12, 47, 35].

+
\[(\d+(, )?){2,}\]
+
+
+

One Raises Its Hand

+

A list of lottery numbers. Which one is the highest?

+

Try: [12, 47, 35].max.

+
(\d+)
+
+
+

Tucking a List Away

+

Good, good. But it's annoying to have to retype that list, isn't it?

+

Let's save our numbers inside a ticket like so: ticket = [12, 47, 35]

+
\[(\d+(, )?){2,}\]
+
+
+

Now Type Ticket

+

Now, type: ticket

+
\[(\d+(, )?){2,}\]
+
+
+

Saved, Tucked Away

+

Fantastic! You've hung on to your lotto numbers, tucking them away inside a + variable called ticket.

+

Let's put your lotto numbers in order, how about? Use: ticket.sort!

+
\[(\d+(, )?){2,}\]
+
+
+

Summary #2 is Upon Us

+

You had a list. You sorted the list. The ticket variable is now changed.

+

Did you notice that the sort! method has a big, bright exclamation at the end? + A lot of times Ruby methods shout like that if they alter the variable for good. It's nothin + special, just a mark.

+

Now, look how your second minute went:

+
    +
  • Errors. If you try to reverse a number or do anything fishy, + Ruby will skip the prompt and tell you so.
  • +
  • Arrays are lists for storing things in order.
  • +
  • Variables save a thing and give it a name. You used the + equals sign to do this.
    Like: ticket = [14, 37, 18].
  • +
+

In all there are eight lessons. You are two-eighths of the way there! + This is simple stuff, don't you think? Good stuff up ahead.

+

Let's change directions for a moment. I've stuffed a bit of poetry for you in + a certain variable. Take a look. Type print poem

+
poem = "My toast has flown from my hand\nAnd my toast has gone to the +moon.\nBut when I saw it on television,\nPlanting our flag on Halley's +comet,\nMore still did I want to eat it.\n"
+
My toast (.+)
+
+
+

Sadly, You Hate Toast Poetry

+

Look, it's okay. You don't have to like it. Hack it up, be my guest.

+

Instead of toast, go for a melon or something. Try this: poem['toast'] = 'honeydew'

+

And then type print poem by itself to see the new poem.

+
My honey(.+)
+
+
+

Ready, Aim

+

The square brackets you just used are very common in Ruby. Remember, you typed: poem['toast'] = 'honeydew'. That box with the word toast has a square bracket on each side, see?

+

The +two brackets are like sights used to line up a target. Exactly. These +brackets mean, "I am looking for ____." Ready, aim. Here you're looking +for toast and swapping it out with fruit.

+

Here's a question: what happens when we reverse this whole poem? poem.reverse +

"\\n.ti tae ot (.+)"
+
+
+

Too Much Reversal

+

Okay, sure. So the whole poem's been turned backwards, letter-by-letter. I really want to just + reverse the lines, though. Move the last line up to first and the first line down to last. Backwards, but not + that backwards.

+

Here's how: poem.lines.to_a.reverse

+
\["More still did I(.+)"\]
+
+
+

Ringlets of Chained Methods

+

So what do you see? What happened there? You typed poem.lines.to_a.reverse and what happened?

+

Two things happened. You turned the poem into a +list using lines.to_a. lines decides the way +the string is split up, then to_a converted it into an +Array. (To array.) Different methods, such +as bytes and chars can be used in place +of lines. By using lines, ruby will return each line of the poem.

+

Then, you reversed that list. You had each line. You reversed them. That's it.

+

Let's tack one more method on the end there: print poem.lines.to_a.reverse.join +

More still did I(.+)
+
+
+

Of All the Summaries, #3 is Here Now

+

Good show, my friend! The join method took that list of reversed lines and put them + together into a string. (Sure, you could have also just used to_s.)

+

Review time.

+
    +
  • Exclamations. Methods may have exclamations (and also question marks) + in their name. No big deal. Try: poem.include? "my hand"
  • +
  • Square brackets. Target and find things. Search and replace.
  • +
  • Chaining methods lets you get a lot more done. Break up a poem, + reverse it, reassemble it: poem.lines.to_a.reverse.join
  • +
+

At this point, you may want to tinker with the poem a bit more. A complete list of all + the String methods is + + here. + Go ahead and try a few (such as poem.downcase or poem.delete.)

+

When you're ready to move on, type: books = {}

+
\{\}
+
+
+

A Wee Blank Book

+

You've made an empty hash. (Also known as: an empty dictionary.)

+

We're going to stuff some miniature book reviews in this hash. Here's our rating system:

+
    +
  • :splendid → a masterpiece.
  • +
  • :quite_good → enjoyed, sure, yes.
  • +
  • :mediocre → equal parts great and terrible.
  • +
  • :quite_not_good → notably bad.
  • +
  • :abyssmal → steaming wreck.
  • +
+

To rate a book, put the title in square brackets and put the rating after the equals.

+

For example: books["Gravity's Rainbow"] = :splendid

+
:\w+
+
+
+

More Bite-Size Reviews

+

Keep going, fill it up with reviews. And, if you want to see the whole list, + just type: books

+

Again, the ratings are: :splendid, :quite_good, :mediocre, + :quite_not_good, and :abyssmal.

+

These ratings are not strings. When you place a colon in front of a simple word, you get a + symbol. Symbols are cheaper than strings (in terms of computer memory.) If + you use a word over and over in your program, use a symbol. Rather than having thousands of + copies of that word in memory, the computer will store the symbol only once.

+

Once you've got three or four books in + there, type: books.length.

+
[3-9]
+
+
+

Wait, Did I Like Gravity's Rainbow?

+

See, the length method works on strings, list and hashes. One great thing about + Ruby is that names are often reused, which means fewer names you need to remember.

+

If you'd like to look up one of your old reviews, again put the title in the square. But leave off + the equals.

+

Just like this: books["Gravity's Rainbow"]

+
:\w+
+
+
+

Hashes as Pairs

+

Keep in mind that hashes won't keep things in order. That's not their job. It'll just pair up two + things: a key and a value. In your reviews, the key is the book's + title and the value is the rating.

+

If you want to just see the titles of the books you've reviewed: books.keys

+
\[".*"\]
+
+
+

Are You Harsh?

+

So are you giving out harsh, unfair reviews? Let's keep score with this hash:
ratings = Hash.new {0}

+

Then, okay, now let's count up your reviews. Just stay with me. Type:
+ books.values.each { |rate| ratings[rate] += 1 }

+

(The straight line in the code is the pipe character, probably located right above the + Enter key on your keyboard.)

+
\[:.+\]
+
+
+

A Tally

+

Great, wow! You've made a scorecard of your ratings. Type ratings to see the count. + This new hash shows a rating and then the number of times you've given that rating.

+

One of the amazing new things we've just used is a block. We're going to + explore these more in the next summary. But, basically, a block is a bit of Ruby code surrounded + by curly braces.

+

Let's try another block: 5.times { print "Odelay!" }

+
Odelay!Od.*
+
+
+

Now Arriving at Summary #4

+

Blocks are always attached to methods. Like the times method, which takes the + block and runs the code over and over. (In this case: five times.)

+

This last lesson was a bit longer. You've probably used up three minutes learning about:

+
    +
  • Hashes. The little dictionary with the curly pages: {}.
  • +
  • Symbols. Tiny, efficient code words with a colon: :splendid.
  • +
  • Blocks. Chunks of code which can be tacked on to many of Ruby's methods. Here's the + code you used to build a scorecard:
    books.values.each { |rate| ratings[rate] += 1 }.
  • +
+

On your computer, you probably have a lot of different files. Files with pictures in them, + files with programs in them. And files are often organized into folders, also called: + directories.

+

I've prepared a few directories for you. Take a look: + Dir.entries "/"

+
\["\.", .+\]
+
+
+

The Private Collection of Dr. Dir

+

You've just listed out everything in the top directory. The root directory, indicated + by a single slash. Containing some programs and other tutorials and such.

+

So, what is the Dir.entries method? Well, it's just a method, right? + entries is a method called on the Dir variable. + And Dir has a collection of methods for checking out file directories.

+

One other little thing we haven't really talked about openly. Method arguments, highlighted in green.

+
    +
  • Dir.entries "/": Anything listed after a method + is considered an attachment.
  • +
  • print poem: See, print is an ordinary method. And the + poem is attached. To be printed.
  • +
  • print "pre", "event", "ual", "ism" has several arguments, with commas + between them.
  • +
+

To list just the text files in that directory: Dir["/*.txt"]

+
\["\/comics\.txt"\]
+
+
+

Come, Read Comics With Me

+

The Dir[] method is like entries but you search for files + with wildcard characters. Here, we see those square brackets again! Notice how + they still mean, "I am looking for _____?"

+

More specifically: "I am looking for files which end with .txt."

+

Let's crack open this comics file, then. Here's the way:
+ print File.read("/comics.txt")

+
Achewood.+
+
+
+

Mi Comicas, Tu Comicas

+

All right! We can start to use files to store things. This is great because normally when + we exit Ruby, all our variables will be gone. Ruby, by itself, forgets these things. + But if we save things in files, we can read those files in future Ruby escapades.

+

Hey, and guess what? The /Home directory is yours! I gave it to you! I am generous! Let's make a copy of the comics file.

+

You'll want to: FileUtils.copy('/comics.txt', '/Home/comics.txt') +

If you've already created the file, use File.delete('/Home/comics.txt') to trash it.

+
nil
+
+
+

Your Own Turf

+

Okay, you've got a copy. Check it: Dir["/Home/*.txt"]

+

To add your own comic to the list, let's open the file in append mode.

+

Start like this: File.open("/Home/comics.txt", "a") do |f|.

+
..
+
+
+

And Now For the Startling Conclusion

+

So your prompt has changed. See that? Your prompt is a double dot now.

+

In this tutorial, this prompt means that Ruby is expecting you to type more. + As you type in the lines of Ruby code, the double dots will continue until you + are completely finished.

+

Hot tip: If you want to stop working on the code and break out of the double dots, use the reset + command. If you want to go the previous page of the tutorial, use the back command.

+

Here's your code. You've already typed the first line, so just enter the second line. (The \n + is an Enter character.

+
  • File.open("/Home/comics.txt", "a") do |f|
  • +
  •   f << "Cat and Girl: http://catandgirl.com/\n"
  • +
  • end
  • +
+

And, since you're getting so advanced and capable here, one other tip: you can use the up and down arrow keys to + edit your old commands or run them again.

+
..
+
+
+

Ruby Sits Still

+

That last line adds the Cat and Girl comic to the list, but Ruby's going to wait until you're totally finished to + take action.

+

Now, to finish the code you've started. You opened a new block when you typed do. + So far the blocks we've seen have used curly braces. This time we'll be using do and end instead + of curly braces. A lot of Rubyists will use do...end when the block goes on for many lines.

+

Let's get that block finished now, with: end

+
  • File.open("/Home/comics.txt", "a") do |f|
  • +
  •   f << "Cat and Girl: http://catandgirl.com/\n"
  • +
  • end
  • +
+
#.File:/Home/comics\.txt \(closed\).
+
+
+

The Clock Nailed To the File

+

Good, good! You've added that new comic to the file. You can see for yourself: print File.read("/Home/comics.txt")

+

What time was it when you changed the file? Let's check. Type: File.mtime("/Home/comics.txt")

+
\d{4}-\d+-\d+ \d{2}:\d{2}:\d{2} [+-]\d{4}
+
+
+

Just the Hour Hand

+

Great, there's the time. The precise time exactly when you added to the file. The mtime gives you a Ruby Time object.

+

If you want to check just what hour it was, hit the up arrow key and change the line to: File.mtime("/Home/comics.txt").hour

+
\d+
+
+
+

Hallo, Who's There? And Summary #5 Waves Its Hat!

+

Well done, well done, well done, well done! Truly, truly, truly, truly, truuuuuuuuly!

+

Here's the last few minutes of your life in review:

+
    +
  • Files. What more can be said? Lots of methods for editing files and lookin around in directories.
  • +
  • Arguments. Arguments are a list of things sent into a method. With commas between.
  • +
  • We also spoke about do and end which are another way to make a block.
  • +
+

You totally know how to use Ruby now. I mean you've got down the essentials. You just need to keep learning more methods and + try out more complex blocks.

+

But there's one side of Ruby we haven't settled. Making your own methods and classes.

+

Ahem! Let's get it over with then.

+

Start with: def load_comics( path )

+
..
+
+
+

In Ruby, Def Leppard Means Define Leppard (a Method)!

+

Hey, okay, you done it. You're making your own method. You started with def, followed by the name of the method. + And a list of arguments which the method will need. This isn't too scary and dangerous!

+

All we have to do is fill it up with Ruby and finish it up with end.

+

Here's the code:

+
  • def load_comics( path )
  • +
  •   comics = {}
  • +
  •   File.foreach(path) do |line|
  • +
  •     name, url = line.split(': ')
  • +
  •     comics[name] = url.strip
  • +
  •   end
  • +
  •   comics
  • +
  • end
  • +
+

No need to indent, if you don't want. I just do that to make it read easier.

+
nil
+
+
+

The Ripened Fruit of Your Own Creation

+

A new method is born. Let us use it: comics = load_comics('/comics.txt')

+

If you have a problem, you might have mistyped. Use the back command and try again.

+
\{.*"Achewood"=."http://achewood.com/".*\}
+
+
+

Hey, Cool, a Comics Thing

+

In your Ruby window above, look at the code you've typed for the load_comics method. What is happening? You're + passing in the path variable and you're getting back the comics variable. Ruby lets the comics + hash trickle out the end of the method.

+

A number of methods were used to get the job done. See if you can spot them.

+
  • File.foreach is a method which opens a file and hands each line to the block. The line + variable inside the do...end block took turns with each line in the file.
  • +
  • split is a method for strings, which breaks the string up into an array. An axe is laid on the colon + and the line is chopped in half, giving us the url and name for each comic.
  • +
  • strip removes extra spaces around the name. Just in case.
  • +
+

Right on. Bravo. You've got the comics in a Ruby hash. But what now? What good is this really?

+

Let's make a page of links. How about that? I went ahead and loaded a little library I've made for you.

+

Type: next. This is temporary as I updates new lessons.

+
true
+
+
+

Browser Puppetry

+

Excellent, you've loaded the popup library. It's saved in a file in the Libraries folder. See: Dir["/Libraries/*"]

+

The popup library contains a bunch of methods I've written which let you control a popup here on the Try Ruby site.

+

Here, try this: Popup.goto "http://google.com/"

+
\033\[1;JSm.*popup_goto\(.*\)\033\[m.*
+
+
+

Making Links and Spinning Webs

+

Our own lovely, little popup to manipulate. You can also fill it with your own goodies. We'll start small:

+
  • Popup.make {
  • +
  •   h1 "My Links"
  • +
  •   link "Go to Google", "http://google.com/"
  • +
  • }
  • +
+

The term h1 (h-one) means a level-one header. In HTML, this is the largest size of header.

+
\033\[1;JSm.*popup_make\(.*h1.*a href.*\)\033\[m.*
+
+
+

Popups Are So Easy, It's Crazy

+

Looks good, you did it perfectly, just as you were asked. Let's make a list then.

+

Here's how you make a list with the popup library:

+
  • Popup.make do
  • +
  •   h1 "Things To Do"
  • +
  •   list do
  • +
  •     p "Try out Ruby"
  • +
  •     p "Ride a tiger"
  • +
  •     p "(down River Euphrates)"
  • +
  •   end
  • +
  • end
  • +
+

The p method is short for "paragraph".

+
\033\[1;JSm.*popup_make\(.*h1.*ul.*li.*li.*\)\033\[m.*
+
+
+

Spread the Comics on the Table

+

Okay, this is coming along wonderfully. This is simple stuff, but keep in mind that you didn't know any Ruby whatsoever just fifteen minutes ago!

+

Last +step. Let's tie it all together, you know? Let's make it chime together +like a very nice set of glistening chimes on the beach in the +maginificent sunlight!

+

Make sure the comics are loaded: comics = load_comics( '/comics.txt' )

+

Now, let's make a list of the links to each comic:

+
  • Popup.make do
  • +
  •   h1 "Comics on the Web"
  • +
  •   list do
  • +
  •     comics.each do |name, url|
  • +
  •       link name, url
  • +
  •     end
  • +
  •   end
  • +
  • end
  • +
+

You can click on the links and read the comics in the little window even! Smashing!

+
\033\[1;JSm.*popup_make\(.*h1.*ul.*li.*a href.*li.*a href.*\)\033\[m.*
+
+
+

Summary #6 Which Means You've Come So Far

+

You're a level six Ruby cleric. I mean what a great job you've done. Let's review:

+
    +
  • You added your own method with def and you used that load_comics method several times.
  • +
  • Libraries. You used the require method to load the popup library.
    By typing: require 'popup'
  • +
  • And if that wasn't enough, you made your own web page from a list of comics in a file. You made a real program!
  • +
+

So +what could possibly be next? What could you possibly have to learn now? +Ha, this is the best part. You've come such a long way that we're going +to uncover classes. For two more short lessons and you're done.

+

Earlier, we created a hash like this: Hash.new Try it.

+
\{\}
+
+
+

Not a School Class, a Working Class

+

You see, the empty curly braces {} is a shortcut for Hash.new. The new +method is used to make objects of a certain class. (Think "class" as in +"working class" — a specific group of objects which are similar, have +the same jobs, the same shirts.)

+

Ask yourself this: How would I make a blog in Ruby? +Where would you start? Well, you might store your blog entries in a +file, right? But how would you keep track of the title of the entry and +the time it was posted? And when you loaded the file, how would it look +in Ruby? Would it be a Hash? Or an Array? Or an Array of Arrays? Or +something else?

I really think you'll want to use a class. You are already familiar with many classes: Hash, Array, String.

+

Let's make a new class: class BlogEntry.

+
..
+
+
+

The Stuff Blogs are Made of

+

You've opened up a new BlogEntry class. What is your blog entry made of? A title, sure. Also, a time when the entry was posted. The + full text of the entry.

+

We'll do a mood setting, too, just like LiveJournal. The Internet has really brought back stick people and smileys + out of bankruptcy. Emote!

+

Okay, so you've got the first line of the class, here's the rest:

+
  • class BlogEntry
  • +
  •   attr_accessor :title, :time, :fulltext, :mood
  • +
  • end
  • +
+
nil
+
+
+

Accessors Are the Dangling Limbs

+

Hey, good class, man. You've got a new BlogEntry class. To start an entry:
entry = BlogEntry.new.

+

In the class definition, you used a method called attr_accessor. There are many attribute methods like + this which add little settings to classes. These attributes are just variables attached to a class.

+

Think +of it this way. A class is like a person. That star-shaped human thing +out there. And the attributes are the dangling limbs, the different +parts that make up a body.

+

To set the title of your entry: entry.title = "Today Mt. Hood Was Stolen!"

+
".+"
+
+
+

An Object, That Neat Little Package

+

Go ahead and set the post time: entry.time = Time.now

+

And the mood: entry.mood = :sick

+

And the post itself: entry.fulltext = "I can't believe Mt. Hood was stolen! I am speechless! It was stolen by a giraffe who drove away + in his Cadillac Seville very nonchalant!!"

+

To see all your settings, just type at the prompt: entry.

+
#.BlogEntry:0x[0-9a-f]+ ((@title|@mood|@time|@fulltext)=.*?, ){3}.*
+
+
+

Quickening it Up

+

Cool, +you're blog is awesome. Hey, let's make things a bit easier on you. +You're not going to want to set the time like that every time you post. +You just want to type in the title and the entry and the mood quickly, +right?

+

Let's add an initialize method.

+
  • class BlogEntry
  • +
  •   def initialize( title, mood, fulltext )
  • +
  •     @time = Time.now
  • +
  •     @title, @mood, @fulltext = title, mood, fulltext
  • +
  •   end
  • +
  • end
  • +
+

Once you've got that typed in, try making a new entry: BlogEntry.new

+
ArgumentError: wrong number of arguments \(0 for 3\).*
+
+
+

You've Taught Your Blog to Reject Worthless Things

+

Did you see how inside the class we used the at-symbols? Like this: @time = Time.now

+

Outside the class, we use accessors: entry.time = Time.now But inside we use instance variables: @time = Time.now + They're the exact same thing, but expressed in two different places of your program.

+

Your blog now needs a title, a mood and a post in order to work. When a new BlogEntry is created, the initialize method + is used to check for any arguments to new. Uh, we need three arguments!

+

Try it again with all three.

+

entry2 += BlogEntry.new( "I Left my Hoodie on the Mountain!", :confused, "I am +never going back to that mountain and I hope a giraffe steals it." )

+
#.BlogEntry:0x[0-9a-f]+ ((@title|@mood|@time|@fulltext)=.*?, ){3}.*
+
+
+

A Giraffe Has Not Stolen Summary #7

+

Aha, you're here. And all in one piece. We're still going to make your blog real, but until then, let's review, okay?

+
    +
  • Classes. Everything in Ruby is some kind of object. Classes explain objects. How a certain object works. + For example, you made a few blog entry objects and these objects are explained in the BlogEntry class. + In other words: you call them BlogEntry objects.
  • +
  • Accessors are variables attached to an object which can be used outside the object. (entry.time = Time.now)
  • +
  • Instance variables are the same variables you're using for accessors when inside the object. + Like in a method definition. (@time = Time.now)
  • +
+

Okay, +let's wrap things up, kid. Here's the last chapter of the GRIPPING epic +story of Try Ruby! Now that you've got a taste of how it all works, how +are you going to use it around the house and in your grocer's freezer? +You're a great person (one of my favorites), but you need guidance.

+

Let's finish your blog. You have blog entries, but no actual blog.

+

Put the entries into an array: blog = [entry, entry2]

+
\[#.BlogEntry:0x[0-9a-f]+.*, #.BlogEntry:0x[0-9a-f]+.*\]
+
+
+

It's All About Combining Things

+

Some +beautiful things can be done with the simple parts of Ruby, especially +when you combine them together into new things. Here we've got a blog +made of an array of classes. And, actually, Ruby really does good with +this kind of creature.

+

Here's a few things you can do with your array blog:

+
  • You'll want to sort your entries from newest to oldest. You can do this with:
    + blog.sort_by { |entry| entry.time }.reverse
    See the sort_by explanation for more.
  • +
  • If you want to search your blog for anything related to "cadillac":
    + blog.find_all { |entry| entry.fulltext.match(/cadillac/i) }
    + Read all about find_all + and match + to figure out how that works. Also: the slashy /giraffe/i is a Regexp object, used for matching words.
  • +
  • Add new entries with blog << new_entry
    + And check out the << method documentation.
  • +
+

You can browse a list of all Ruby's built-in methods at ruby-doc.org's core list. + Another good list is at the online pickaxe.

+

One really useful method (I probably use this more than anything else) is map. Type: blog.map { |entry| entry.mood }

+
\[(:\w+, )+:\w+\]
+
+
+

Look at His Face — The Transformation Has Begun

+

The map method cycles through an array and replaces each item with something new. Say you wanted to replace each of your blog entries + with the name Bruce Willis. Do it so: blog.map { "Bruce Willis" }

+

Since the block always returns the string "Bruce Willis", that's what you get. In the code you just used, the entry was swapped out + for only the entry.mood.

+

Now, +I want you to make a popup with your blog entries. I'm not going to +give you all of the code. I'm just going to give you part of it.

+
  • blog.each do |entry|
  • +
  •   h2 entry.title
  • +
  •   p entry.fulltext
  • +
  • end
  • +
+

Now, I expect you to put the popup code around it and add an h1 title with the name of your blog. For extra haroompf, have the time of each entry display.

+
\033\[1;JSm.*popup_make\(.*h1.*h2.*li.*h2.*li.*\)\033\[m.*
+
+
+

You are Some Kind of Web Guru, I Have Stars in My Eyes

+

Good, +that's it! This is exactly the code you can use to write your own real +Ruby blog. If you're feeling adventurous, I'd check out the Rails videos which show a swift young fellow creating a blog in 15 minutes. You just sit back and watch.

+

I +should mention Rails. You have been learning the Ruby language, how to +speak it. But Rails is a bunch of libraries (sort of like the popup +library we've been using.) It's a very powerful toolkit for building +websites. If you're interested in learning about Rails, I would head + over there right away. Start using your Ruby skills proper!

+

One thing Rails has is easy methods for dates. Like, try: Time.now - 2.weeks

+
class Integer; def weeks; self * 7*24*60*60; end; end
+
\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [-+]\d{4}
+
+
+

If You Want to Start Small

+

If you'd like to start writing little Ruby programs just to practice, I have a project called MouseHole + which is a little web toolkit for writing short Ruby programs. You can look over a few + scripts to see what I mean.

+

MouseHole +isn't for writing web sites really. It's just for writing little +programs you run inside your browser. Like there's a notepad program +for MouseHole and a program which adds a mouse picture next to links on +the web which link to MouseHole programs.

+

I've got a MouseHole script inside a file here:
+ print File.read("/MouseHole/flickrpedia.user.rb")

+
.*Inserts Wikipedia links for Flickr tags.*
+
+
+

Summary #8, The Hey-Relax-You-Did-Good Summary

+

This last section took a moment to wind down, to give you some pointers as to how you can use Ruby. If you enjoyed yourself, + download Ruby and install it.

+ +

Once you have Ruby installed, you can use Interactive Ruby by running irb on your system's prompt. For more on Irb, + there's The Tiger's Vest to help you.

+

You +really deserve a double-layer cake with double-double frosting and a +guy playing one of those guitars that's a double guitar. I mean you +finished, you really did! No doubt about it, you're a certified +red-blooded smartiac!

+
+
+ + diff --git a/tryruby/app/views/tutorials/intro.rhtml b/app/views/tutorials/intro.rhtml similarity index 99% rename from tryruby/app/views/tutorials/intro.rhtml rename to app/views/tutorials/intro.rhtml index 4b43137..7443c46 100644 --- a/tryruby/app/views/tutorials/intro.rhtml +++ b/app/views/tutorials/intro.rhtml @@ -543,7 +543,7 @@ parts that make up a body.

Quickening it Up

Cool, -you're blog is awesome. Hey, let's make things a bit easier on you. +your blog is awesome. Hey, let's make things a bit easier on you. You're not going to want to set the time like that every time you post. You just want to type in the title and the entry and the mood quickly, right?

diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..73c9258 --- /dev/null +++ b/config.ru @@ -0,0 +1,4 @@ +# This file is used by Rack-based servers to start the application. + +require ::File.expand_path('../config/environment', __FILE__) +run Tryruby::Application diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 0000000..d12142e --- /dev/null +++ b/config/application.rb @@ -0,0 +1,42 @@ +require File.expand_path('../boot', __FILE__) + +require 'rails/all' + +# If you have a Gemfile, require the gems listed there, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(:default, Rails.env) if defined?(Bundler) + +module Tryruby + class Application < Rails::Application + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Custom directories with classes and modules you want to be autoloadable. + # config.autoload_paths += %W(#{config.root}/extras) + + # Only load the plugins named here, in the order given (default is alphabetical). + # :all can be used as a placeholder for all plugins not explicitly named. + # config.plugins = [ :exception_notification, :ssl_requirement, :all ] + + # Activate observers that should always be running. + # config.active_record.observers = :cacher, :garbage_collector, :forum_observer + + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. + # config.time_zone = 'Central Time (US & Canada)' + + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + # config.i18n.default_locale = :de + + # JavaScript files you want as :defaults (application.js is always included). + # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) + + # Configure the default encoding used in templates for Ruby 1.9. + config.encoding = "utf-8" + + # Configure sensitive parameters which will be filtered from the log file. + config.filter_parameters += [:password] + end +end diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000..4489e58 --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,6 @@ +require 'rubygems' + +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) + +require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 0000000..07a70ee --- /dev/null +++ b/config/database.yml @@ -0,0 +1,34 @@ + +development: + adapter: mysql2 + encoding: utf8 + reconnect: false + database: tryruby + username: root + password: + pool: 5 + timeout: 5000 + socket: /tmp/mysql.sock + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + adapter: mysql2 + database: tryruby_test + username: root + password: + pool: 5 + timeout: 5000 + socket: /tmp/mysql.sock + +production: + adapter: mysql2 + database: tryruby_production + username: root + password: + pool: 5 + timeout: 5000 + socket: /tmp/mysql.sock +cucumber: + <<: *test diff --git a/config/deploy.rb b/config/deploy.rb new file mode 100644 index 0000000..36bef48 --- /dev/null +++ b/config/deploy.rb @@ -0,0 +1,61 @@ +$:.unshift(File.expand_path('./lib', ENV['rvm_path'])) # Add RVM's lib directory to the load path. +require "rvm/capistrano" # Load RVM's capistrano plugin. +set :rvm_ruby_string, 'ree' + +require 'bundler/capistrano' + +default_run_options[:pty] = true # Must be set for the password prompt from git to work +set :application, "TryRuby" + +default_run_options[:pty] = true # Must be set for the password prompt from git to work +set :repository, "git@github.com:mosteam/TryRuby.git" # Your clone URL +set :branch, "master" +set :scm, "git" +set :user, "TryRuby" # The server's user for deploys +set :deploy_to, "/var/rails/TryRuby" +set :deploy_via, :remote_cache +set :use_sudo, false + +ssh_options[:forward_agent] = true + +set :git_shallow_clone, 1 +set :git_enable_submodules, 1 + +role :web, "106.187.37.16" # Your HTTP server, Apache/etc +role :app, "106.187.37.16" # This may be the same as your `Web` server +role :db, "106.187.37.16", :primary => true # This is where Rails migrations will run + +# tasks +namespace :deploy do + task :start, :roles => :app do + run "touch #{current_path}/tmp/restart.txt" + end + + desc "Compile assets" + task :assets do + run "cd #{current_path}; chmod -R 0777 public/" + run "cd #{current_path}; RAILS_ENV=production rake assets:precompile" + end + + task :stop, :roles => :app do + # Do nothing. + end + + desc "Restart Application" + task :restart, :roles => :app do + run "touch #{current_path}/tmp/restart.txt" + run "mkdir -p #{current_path}/tmp/cache" + run "cd #{current_path}; chmod -R 0777 log/" + run "cd #{current_path}; chmod -R 0777 tmp/" + end + + desc "Symlink shared resources on each release" + task :symlink_shared, :roles => :app do + run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml" + run "ln -nfs #{shared_path}/temp #{release_path}/public/temp" + # run "ln -nfs #{shared_path}/config/newrelic.yml #{release_path}/config/newrelic.yml" + end +end + +after 'deploy:update_code', 'deploy:symlink_shared' +after "deploy:symlink_shared", "deploy:assets" \ No newline at end of file diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000..8e899fa --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,5 @@ +# Load the rails application +require File.expand_path('../application', __FILE__) + +# Initialize the rails application +Tryruby::Application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 0000000..38d039f --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,26 @@ +Tryruby::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the webserver when you make code changes. + config.cache_classes = false + + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true + + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_view.debug_rjs = true + config.action_controller.perform_caching = false + + # Don't care if the mailer can't send + config.action_mailer.raise_delivery_errors = false + + # Print deprecation notices to the Rails logger + config.active_support.deprecation = :log + + # Only use best-standards-support built into browsers + config.action_dispatch.best_standards_support = :builtin +end + diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 0000000..fdd5b09 --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,49 @@ +Tryruby::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # The production environment is meant for finished, "live" apps. + # Code is not reloaded between requests + config.cache_classes = true + + # Full error reports are disabled and caching is turned on + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Specifies the header that your server uses for sending files + config.action_dispatch.x_sendfile_header = "X-Sendfile" + + # For nginx: + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' + + # If you have no front-end server that supports something like X-Sendfile, + # just comment this out and Rails will serve the files + + # See everything in the log (default is :info) + # config.log_level = :debug + + # Use a different logger for distributed setups + # config.logger = SyslogLogger.new + + # Use a different cache store in production + # config.cache_store = :mem_cache_store + + # Disable Rails's static asset server + # In production, Apache or nginx will already do this + config.serve_static_assets = false + + # Enable serving of images, stylesheets, and javascripts from an asset server + # config.action_controller.asset_host = "http://assets.example.com" + + # Disable delivery errors, bad email addresses will be ignored + # config.action_mailer.raise_delivery_errors = false + + # Enable threaded mode + # config.threadsafe! + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation can not be found) + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners + config.active_support.deprecation = :notify +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 0000000..6bf007b --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,35 @@ +Tryruby::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true + + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment + config.action_controller.allow_forgery_protection = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Print deprecation notices to the stderr + config.active_support.deprecation = :stderr +end diff --git a/tryruby/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb similarity index 61% rename from tryruby/config/initializers/backtrace_silencers.rb rename to config/initializers/backtrace_silencers.rb index c2169ed..59385cd 100644 --- a/tryruby/config/initializers/backtrace_silencers.rb +++ b/config/initializers/backtrace_silencers.rb @@ -3,5 +3,5 @@ # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } -# You can also remove all the silencers if you're trying do debug a problem that might steem from framework code. -# Rails.backtrace_cleaner.remove_silencers! \ No newline at end of file +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/tryruby/config/initializers/inflections.rb b/config/initializers/inflections.rb similarity index 85% rename from tryruby/config/initializers/inflections.rb rename to config/initializers/inflections.rb index d531b8b..9e8b013 100644 --- a/tryruby/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -1,6 +1,6 @@ # Be sure to restart your server when you modify this file. -# Add new inflection rules using the following format +# Add new inflection rules using the following format # (all these examples are active by default): # ActiveSupport::Inflector.inflections do |inflect| # inflect.plural /^(ox)$/i, '\1en' diff --git a/tryruby/config/initializers/mime_types.rb b/config/initializers/mime_types.rb similarity index 100% rename from tryruby/config/initializers/mime_types.rb rename to config/initializers/mime_types.rb diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb new file mode 100644 index 0000000..88f9a7f --- /dev/null +++ b/config/initializers/secret_token.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +Tryruby::Application.config.secret_token = '0b9697ad5c8979de943825eeb25418d1f8cf963ad4e3a6758acc2558ae77b0c097997142fea5c9cb5969f2126ee433aefccbae922c04943ca4074d2c31e9c608' diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb new file mode 100644 index 0000000..b4dee00 --- /dev/null +++ b/config/initializers/session_store.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +Tryruby::Application.config.session_store :cookie_store, :key => '_tryruby_session' + +# Use the database for sessions instead of the cookie-based default, +# which shouldn't be used to store highly confidential information +# (create the session table with "rails generate session_migration") +# Tryruby::Application.config.session_store :active_record_store diff --git a/tryruby/config/locales/en.yml b/config/locales/en.yml similarity index 89% rename from tryruby/config/locales/en.yml rename to config/locales/en.yml index f265c06..a747bfa 100644 --- a/tryruby/config/locales/en.yml +++ b/config/locales/en.yml @@ -2,4 +2,4 @@ # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. en: - hello: "Hello world" \ No newline at end of file + hello: "Hello world" diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..b0cd5a4 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,68 @@ +Tryruby::Application.routes.draw do + + resources :irb + + # The priority is based upon order of creation: + # first created -> highest priority. + root :to => "tryruby#index" + match '/tryruby/run' => 'tryruby#run' + # connect ':controller/:action/:id' + # connect ':controller/:action/:id.:format' + + # Sample of regular route: + # match 'products/:id' => 'catalog#view' + # Keep in mind you can assign values other than :controller and :action + + # Sample of named route: + # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase + # This route can be invoked with purchase_url(:id => product.id) + + # Sample resource route (maps HTTP verbs to controller actions automatically): + # resources :products + + # Sample resource route with options: + # resources :products do + # member do + # get 'short' + # post 'toggle' + # end + # + # collection do + # get 'sold' + # end + # end + + # Sample resource route with sub-resources: + # resources :products do + # resources :comments, :sales + # resource :seller + # end + + # Sample resource route with more complex sub-resources + # resources :products do + # resources :comments + # resources :sales do + # get 'recent', :on => :collection + # end + # end + + # Sample resource route within a namespace: + # namespace :admin do + # # Directs /admin/products/* to Admin::ProductsController + # # (app/controllers/admin/products_controller.rb) + # resources :products + # end + + # You can have the root of your site routed with "root" + # just remember to delete public/index.html. + # root :to => "welcome#index" + + # See how all your routes lay out with "rake routes" + + # This is a legacy wild controller route that's not recommended for RESTful applications. + # Note: This route will make all actions in every controller accessible via GET requests. + # match ':controller(/:action(/:id(.:format)))' + end + + + \ No newline at end of file diff --git a/db/development.sqlite3 b/db/development.sqlite3 new file mode 100644 index 0000000..e69de29 diff --git a/tryruby/db/seeds.rb b/db/seeds.rb similarity index 82% rename from tryruby/db/seeds.rb rename to db/seeds.rb index 3174d0c..664d8c7 100644 --- a/tryruby/db/seeds.rb +++ b/db/seeds.rb @@ -2,6 +2,6 @@ # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). # # Examples: -# +# # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) -# Major.create(:name => 'Daley', :city => cities.first) +# Mayor.create(:name => 'Daley', :city => cities.first) diff --git a/tryruby/doc/README_FOR_APP b/doc/README_FOR_APP similarity index 100% rename from tryruby/doc/README_FOR_APP rename to doc/README_FOR_APP diff --git a/tryruby/lib/popup.rb b/lib/popup.rb similarity index 94% rename from tryruby/lib/popup.rb rename to lib/popup.rb index 481fee9..87edf38 100755 --- a/tryruby/lib/popup.rb +++ b/lib/popup.rb @@ -7,7 +7,7 @@ module LoadPopup module Popup def self.goto(url) url = url.gsub '"', '\"' - TryRuby::Output.javascript "window.irb.options.popup_goto(\"#{url}\")" + Output.javascript "window.irb.options.popup_goto(\"#{url}\")" end class Header @@ -108,7 +108,7 @@ def self.make(&block) html = result.generate_html.gsub('\\', '\\\\').gsub('"', '\"') command = "window.irb.options.popup_make(\"#{html}\")" - TryRuby::Output.javascript command + Tryrubyengine::Output.javascript command end end diff --git a/tryruby/lib/setup.rb b/lib/setup.rb similarity index 99% rename from tryruby/lib/setup.rb rename to lib/setup.rb index 6bae16a..20fad62 100644 --- a/tryruby/lib/setup.rb +++ b/lib/setup.rb @@ -1,4 +1,4 @@ -module TryRuby +module Tryrubyengine SetupCode = < 'db:test:prepare'}, 'Run features that should pass') do |t| + t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. + t.fork = true # You may get faster startup if you set this to false + t.profile = 'default' + end + + Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'wip' + end + + Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'rerun' + end + + desc 'Run all features' + task :all => [:ok, :wip] + end + desc 'Alias for cucumber:ok' + task :cucumber => 'cucumber:ok' + + task :default => :cucumber + + task :features => :cucumber do + STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" + end +rescue LoadError + desc 'cucumber rake task not available (cucumber not installed)' + task :cucumber do + abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' + end +end + +end diff --git a/lib/tryruby.rb b/lib/tryruby.rb new file mode 100644 index 0000000..7fe0912 --- /dev/null +++ b/lib/tryruby.rb @@ -0,0 +1,206 @@ +#require 'ruby_parser' +require 'ruby_parser' + +require 'stringio' +require 'popup.rb' +require 'setup.rb' +require 'fakefs/safe' +require 'cgi' +require 'cgi/session' +require 'cgi/session/pstore' + + + + + + + module Tryrubyengine + extend self + class TRSession + #include ActionDispatch::Session + # < TryRuby::Session + attr_accessor :cgi, :session + + def initialize + + @session = CGI::Session.new @cgi = CGI.new, + 'database_manager' => CGI::Session::PStore, # use PStore + 'session_key' => 'trb_sess_id', # custom $session key + 'session_expires' => Time.now + 60 * 60, # 60 minute timeout + 'prefix' => 'pstore_sid_', #Pstore option + 'tmpdir' => 'tmp' # Temp Directory for sessions + + @session['start_time'] ||= Time.now + #ActionController::Base.session + @session['current_statement'] ||= '' + @session['past_commands'] ||= '' + end + + def header + @cgi.header 'text/plain' + end + + [:current_statement, :past_commands, :start_time].each do |accessor| + define_method(accessor) { @session[accessor.to_s] } + define_method(:"#{accessor.to_s}=") { |new_val| @session[accessor.to_s] = new_val } + end + end + + + + class Output + attr_reader :type, :result, :output, :error, :indent_level, :javascript + + def self.standard(params = {}) + Output.new type: :standard, result: params[:result], + output: params[:output] || '' + end + + def self.illegal + Output.new type: :illegal + end + + def self.javascript(js) + Output.new type: :javascript, javascript: js + end + + def self.no_output + Output.standard result: nil + end + + def self.line_continuation(level) + Output.new type: :line_continuation, indent_level: level + end + + def self.error(params = {}) + params[:error] ||= StandardError.new('TryRuby Error') + params[:error].message.gsub! /\(eval\):\d*/, '(TryRuby):1' + Output.new type: :error, error: params[:error], + output: params[:output] || '' + end + + def format + case @type + when :line_continuation + ".." * @indent_level + when :error + @output + "\033[1;33m#{@error.class}: #{@error.message}" + when :illegal + "\033[1;33mYou aren't allowed to run that command!" + when :javascript + "\033[1;JSm#{@javascript}\033[m " + else + @output + "=> \033[1;20m#{@result.inspect}" + end + end + + protected + def initialize(values = {}) + values.each do |variable, value| + instance_variable_set("@#{variable}", value) + end + end + end + + + class << self + attr_accessor :session + Tryrubyengine.session = Tryrubyengine::TRSession.new + end + + def calculate_nesting_level(statement) + begin + RubyParser.new.parse(statement) + 0 + rescue Racc::ParseError => e + case e.message + when /parse error on value \"\$end\" \(\$end\)/ then + new_statement = statement + "\n end" + begin + RubyParser.new.parse(new_statement) + return 1 + rescue Racc::ParseError => e + if e.message =~ /parse error on value \"end\" \(kEND\)/ then + new_statement = statement + "\n }" + end + end + begin + 1 + calculate_nesting_level(new_statement) + rescue Racc::ParseError => e + return 1 + end + else + raise e + end + end + end + + def run_line(code,session) + case code.strip + when '!INIT!IRB!' + return Output.no_output + when 'reset' + session.current_statement = '' + return Output.no_output + when 'time' + seconds = (Time.now - session.start_time).ceil + return Output.standard result: + if seconds < 60; "#{seconds} seconds" + elsif seconds < 120; "1 minute" + else; "#{seconds / 60} minutes" + end + end + + # nesting level + level = begin + calculate_nesting_level(session.current_statement + "\n" + code) + rescue Racc::ParseError, SyntaxError + 0 + end + if level > 0 + session.current_statement += "\n" + code + return Output.line_continuation(level) + end + + # run something + FakeFS.activate! + stdout_id = $stdout.to_i + $stdout = StringIO.new + cmd = <<-EOF + #{SetupCode} + $SAFE = 3 + #{session.past_commands} + $stdout = StringIO.new + begin + #{session.current_statement} + #{code} + end + EOF + begin + result = Thread.new { eval cmd, TOPLEVEL_BINDING }.value + rescue SecurityError + return Output.illegal + rescue Exception => e + return Output.error :error => e, :output => get_stdout + ensure + output = get_stdout + $stdout = IO.new(stdout_id) + FakeFS.deactivate! + end + + session.current_statement += "\n" + code + session.past_commands += "\n" + session.current_statement.strip + session.current_statement = '' + + return result if result.is_a? Output and result.type == :javascript + Output.standard result: result, output: output + end + + private + def get_stdout + raise TypeError, "$stdout is a #{$stdout.class}" unless $stdout.is_a? StringIO + $stdout.rewind + $stdout.read + end + + end diff --git a/log/development.log b/log/development.log new file mode 100644 index 0000000..ba62c27 --- /dev/null +++ b/log/development.log @@ -0,0 +1,15 @@ +DEPRECATION WARNING: config.action_view.debug_rjs will be removed in 3.1, from 3.1 onwards you will need to install prototype-rails to continue to use RJS templates . (called from service at /home/administrator/.rvm/rubies/ruby-1.8.7-p352/lib/ruby/1.8/webrick/httpserver.rb:104) + + +Started GET "/" for 127.0.0.1 at Wed Oct 26 11:13:07 +0800 2011 + Processing by TryrubyController#index as HTML +Rendered tryruby/index.html.erb within layouts/tryruby (0.5ms) +Completed 200 OK in 3ms (Views: 2.7ms | ActiveRecord: 0.0ms) + + +Started GET "/tutorials/intro" for 127.0.0.1 at Wed Oct 26 11:13:07 +0800 2011 + +ActionController::RoutingError (No route matches "/tutorials/intro"): + + +Rendered /home/administrator/.rvm/gems/ruby-1.8.7-p352/gems/actionpack-3.0.9/lib/action_dispatch/middleware/templates/rescues/routing_error.erb within rescues/layout (0.9ms) diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..9a48320 --- /dev/null +++ b/public/404.html @@ -0,0 +1,26 @@ + + + + The page you were looking for doesn't exist (404) + + + + + +
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+ + diff --git a/public/422.html b/public/422.html new file mode 100644 index 0000000..83660ab --- /dev/null +++ b/public/422.html @@ -0,0 +1,26 @@ + + + + The change you wanted was rejected (422) + + + + + +
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+ + diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000..b80307f --- /dev/null +++ b/public/500.html @@ -0,0 +1,26 @@ + + + + We're sorry, but something went wrong (500) + + + + + +
+

We're sorry, but something went wrong.

+

We've been notified about this issue and we'll take a look at it shortly.

+
+ + diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/tryruby/public/images/background.png b/public/images/background.png similarity index 100% rename from tryruby/public/images/background.png rename to public/images/background.png diff --git a/tryruby/public/images/footer.png b/public/images/footer.png similarity index 100% rename from tryruby/public/images/footer.png rename to public/images/footer.png diff --git a/public/images/header.png b/public/images/header.png new file mode 100644 index 0000000..d6b9aa0 Binary files /dev/null and b/public/images/header.png differ diff --git a/public/images/index.html b/public/images/index.html new file mode 100755 index 0000000..e933af9 --- /dev/null +++ b/public/images/index.html @@ -0,0 +1,8 @@ + + + + + +

you shouldn't be here.. kicking you out!

+ + diff --git a/tryruby/public/images/rails.png b/public/images/rails.png similarity index 100% rename from tryruby/public/images/rails.png rename to public/images/rails.png diff --git a/tryruby/public/images/tile.png b/public/images/tile.png similarity index 100% rename from tryruby/public/images/tile.png rename to public/images/tile.png diff --git a/tryruby/public/javascripts/application.js b/public/javascripts/application.js similarity index 100% rename from tryruby/public/javascripts/application.js rename to public/javascripts/application.js diff --git a/tryruby/public/javascripts/console.js b/public/javascripts/console.js similarity index 100% rename from tryruby/public/javascripts/console.js rename to public/javascripts/console.js diff --git a/tryruby/public/javascripts/controls.js b/public/javascripts/controls.js similarity index 100% rename from tryruby/public/javascripts/controls.js rename to public/javascripts/controls.js diff --git a/tryruby/public/javascripts/dragdrop.js b/public/javascripts/dragdrop.js similarity index 100% rename from tryruby/public/javascripts/dragdrop.js rename to public/javascripts/dragdrop.js diff --git a/tryruby/public/javascripts/effects.js b/public/javascripts/effects.js similarity index 100% rename from tryruby/public/javascripts/effects.js rename to public/javascripts/effects.js diff --git a/public/javascripts/facebox.js b/public/javascripts/facebox.js new file mode 100755 index 0000000..5ad90ed --- /dev/null +++ b/public/javascripts/facebox.js @@ -0,0 +1,309 @@ +/* + * Facebox (for jQuery) + * version: 1.2 (05/05/2008) + * @requires jQuery v1.2 or later + * + * Examples at http://famspam.com/facebox/ + * + * Licensed under the MIT: + * http://www.opensource.org/licenses/mit-license.php + * + * Copyright 2007, 2008 Chris Wanstrath [ chris@ozmm.org ] + * + * Usage: + * + * jQuery(document).ready(function() { + * jQuery('a[rel*=facebox]').facebox() + * }) + * + * Terms + * Loads the #terms div in the box + * + * Terms + * Loads the terms.html page in the box + * + * Terms + * Loads the terms.png image in the box + * + * + * You can also use it programmatically: + * + * jQuery.facebox('some html') + * jQuery.facebox('some html', 'my-groovy-style') + * + * The above will open a facebox with "some html" as the content. + * + * jQuery.facebox(function($) { + * $.get('blah.html', function(data) { $.facebox(data) }) + * }) + * + * The above will show a loading screen before the passed function is called, + * allowing for a better ajaxy experience. + * + * The facebox function can also display an ajax page, an image, or the contents of a div: + * + * jQuery.facebox({ ajax: 'remote.html' }) + * jQuery.facebox({ ajax: 'remote.html' }, 'my-groovy-style') + * jQuery.facebox({ image: 'stairs.jpg' }) + * jQuery.facebox({ image: 'stairs.jpg' }, 'my-groovy-style') + * jQuery.facebox({ div: '#box' }) + * jQuery.facebox({ div: '#box' }, 'my-groovy-style') + * + * Want to close the facebox? Trigger the 'close.facebox' document event: + * + * jQuery(document).trigger('close.facebox') + * + * Facebox also has a bunch of other hooks: + * + * loading.facebox + * beforeReveal.facebox + * reveal.facebox (aliased as 'afterReveal.facebox') + * init.facebox + * afterClose.facebox + * + * Simply bind a function to any of these hooks: + * + * $(document).bind('reveal.facebox', function() { ...stuff to do after the facebox and contents are revealed... }) + * + */ +(function($) { + $.facebox = function(data, klass) { + $.facebox.loading() + + if (data.ajax) fillFaceboxFromAjax(data.ajax, klass) + else if (data.image) fillFaceboxFromImage(data.image, klass) + else if (data.div) fillFaceboxFromHref(data.div, klass) + else if ($.isFunction(data)) data.call($) + else $.facebox.reveal(data, klass) + } + + /* + * Public, $.facebox methods + */ + + $.extend($.facebox, { + settings: { + opacity : 0.2, + overlay : true, + loadingImage : '/facebox/loading.gif', + closeImage : '/facebox/closelabel.png', + imageTypes : [ 'png', 'jpg', 'jpeg', 'gif' ], + faceboxHtml : '\ + ' + }, + + loading: function() { + init() + if ($('#facebox .loading').length == 1) return true + showOverlay() + + $('#facebox .content').empty() + $('#facebox .body').children().hide().end(). + append('
') + + $('#facebox').css({ + top: getPageScroll()[1] + (getPageHeight() / 10), + left: $(window).width() / 2 - 205 + }).show() + + $(document).bind('keydown.facebox', function(e) { + if (e.keyCode == 27) $.facebox.close() + return true + }) + $(document).trigger('loading.facebox') + }, + + reveal: function(data, klass) { + $(document).trigger('beforeReveal.facebox') + if (klass) $('#facebox .content').addClass(klass) + $('#facebox .content').append(data) + $('#facebox .loading').remove() + $('#facebox .body').children().fadeIn('normal') + $('#facebox').css('left', $(window).width() / 2 - ($('#facebox .popup').width() / 2)) + $(document).trigger('reveal.facebox').trigger('afterReveal.facebox') + }, + + close: function() { + $(document).trigger('close.facebox') + return false + } + }) + + /* + * Public, $.fn methods + */ + + $.fn.facebox = function(settings) { + if ($(this).length == 0) return + + init(settings) + + function clickHandler() { + $.facebox.loading(true) + + // support for rel="facebox.inline_popup" syntax, to add a class + // also supports deprecated "facebox[.inline_popup]" syntax + var klass = this.rel.match(/facebox\[?\.(\w+)\]?/) + if (klass) klass = klass[1] + + fillFaceboxFromHref(this.href, klass) + return false + } + + return this.bind('click.facebox', clickHandler) + } + + /* + * Private methods + */ + + // called one time to setup facebox on this page + function init(settings) { + if ($.facebox.settings.inited) return true + else $.facebox.settings.inited = true + + $(document).trigger('init.facebox') + makeCompatible() + + var imageTypes = $.facebox.settings.imageTypes.join('|') + $.facebox.settings.imageTypesRegexp = new RegExp('\.(' + imageTypes + ')$', 'i') + + if (settings) $.extend($.facebox.settings, settings) + $('body').append($.facebox.settings.faceboxHtml) + + var preload = [ new Image(), new Image() ] + preload[0].src = $.facebox.settings.closeImage + preload[1].src = $.facebox.settings.loadingImage + + $('#facebox').find('.b:first, .bl').each(function() { + preload.push(new Image()) + preload.slice(-1).src = $(this).css('background-image').replace(/url\((.+)\)/, '$1') + }) + + $('#facebox .close').click($.facebox.close) + $('#facebox .close_image').attr('src', $.facebox.settings.closeImage) + } + + // getPageScroll() by quirksmode.com + function getPageScroll() { + var xScroll, yScroll; + if (self.pageYOffset) { + yScroll = self.pageYOffset; + xScroll = self.pageXOffset; + } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict + yScroll = document.documentElement.scrollTop; + xScroll = document.documentElement.scrollLeft; + } else if (document.body) {// all other Explorers + yScroll = document.body.scrollTop; + xScroll = document.body.scrollLeft; + } + return new Array(xScroll,yScroll) + } + + // Adapted from getPageSize() by quirksmode.com + function getPageHeight() { + var windowHeight + if (self.innerHeight) { // all except Explorer + windowHeight = self.innerHeight; + } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode + windowHeight = document.documentElement.clientHeight; + } else if (document.body) { // other Explorers + windowHeight = document.body.clientHeight; + } + return windowHeight + } + + // Backwards compatibility + function makeCompatible() { + var $s = $.facebox.settings + + $s.loadingImage = $s.loading_image || $s.loadingImage + $s.closeImage = $s.close_image || $s.closeImage + $s.imageTypes = $s.image_types || $s.imageTypes + $s.faceboxHtml = $s.facebox_html || $s.faceboxHtml + } + + // Figures out what you want to display and displays it + // formats are: + // div: #id + // image: blah.extension + // ajax: anything else + function fillFaceboxFromHref(href, klass) { + // div + if (href.match(/#/)) { + var url = window.location.href.split('#')[0] + var target = href.replace(url,'') + if (target == '#') return + $.facebox.reveal($(target).html(), klass) + + // image + } else if (href.match($.facebox.settings.imageTypesRegexp)) { + fillFaceboxFromImage(href, klass) + // ajax + } else { + fillFaceboxFromAjax(href, klass) + } + } + + function fillFaceboxFromImage(href, klass) { + var image = new Image() + image.onload = function() { + $.facebox.reveal('
', klass) + } + image.src = href + } + + function fillFaceboxFromAjax(href, klass) { + $.get(href, function(data) { $.facebox.reveal(data, klass) }) + } + + function skipOverlay() { + return $.facebox.settings.overlay == false || $.facebox.settings.opacity === null + } + + function showOverlay() { + if (skipOverlay()) return + + if ($('#facebox_overlay').length == 0) + $("body").append('
') + + $('#facebox_overlay').hide().addClass("facebox_overlayBG") + .css('opacity', $.facebox.settings.opacity) + .click(function() { $(document).trigger('close.facebox') }) + .fadeIn(200) + return false + } + + function hideOverlay() { + if (skipOverlay()) return + + $('#facebox_overlay').fadeOut(200, function(){ + $("#facebox_overlay").removeClass("facebox_overlayBG") + $("#facebox_overlay").addClass("facebox_hide") + $("#facebox_overlay").remove() + }) + + return false + } + + /* + * Bindings + */ + + $(document).bind('close.facebox', function() { + $(document).unbind('keydown.facebox') + $('#facebox').fadeOut(function() { + $('#facebox .content').removeClass().addClass('content') + $('#facebox .loading').remove() + $(document).trigger('afterClose.facebox') + }) + hideOverlay() + }) + +})(jQuery); diff --git a/public/javascripts/index.html b/public/javascripts/index.html new file mode 100755 index 0000000..e933af9 --- /dev/null +++ b/public/javascripts/index.html @@ -0,0 +1,8 @@ + + + + + +

you shouldn't be here.. kicking you out!

+ + diff --git a/tryruby/public/javascripts/irb.js b/public/javascripts/irb.js similarity index 100% rename from tryruby/public/javascripts/irb.js rename to public/javascripts/irb.js diff --git a/public/javascripts/jQuery.irb.js b/public/javascripts/jQuery.irb.js new file mode 100644 index 0000000..765858d --- /dev/null +++ b/public/javascripts/jQuery.irb.js @@ -0,0 +1,1641 @@ +//Try Ruby 1.5 (defacto version Number) +// March 14 2011 + +// large parts of this file are lifted from Try Haskell ( which lifted parts from try ruby without copyright notice >:-/ ) + +// Thus it is fair to include the following banner if/until there is no try haskell code left/ only code that originally existed in try ruby in the +//first place. + + // Try Haskell 1.0.1 + // Tue Feb 23 18:34:48 GMT 2010 + // + // Copyright 2010 Chris Done. All rights reserved. + // + // Redistribution and use in source and binary forms, with or without + // modification, are permitted provided that the following conditions + // are met: + // + // 1. Redistributions of source code must retain the above + // copyright notice, this list of conditions and the following + // disclaimer. + // 2. Redistributions in binary form must reproduce the above + // copyright notice, this list of conditions and the following + // disclaimer in the documentation and/or other materials + // provided with the distribution. + // + // THIS SOFTWARE IS PROVIDED BY CHRIS DONE ``AS IS'' AND ANY EXPRESS + // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + // ARE DISCLAIMED. IN NO EVENT SHALL CHRIS DONE OR CONTRIBUTORS BE + // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + // OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + // DAMAGE. + // The views and conclusions contained in the software and + // documentation are those of the authors and should not be + // interpreted as representing official policies, either expressed or + // implied, of Chris Done. + // + // TESTED ON + // Internet Explorer 6 + // Opera 10.01 + // Chromium 4.0.237.0 (Ubuntu build 31094) + // Firefox 3.5.8 + + //IOS 4.3 (all devices) + +// Temporary fix +function opera() { + return navigator.userAgent.indexOf("Opera") == 0; +} + +function encodeHex(str) { + var result = ""; + for (var i = 0; i < str.length; i++) { + result += "%" + pad(toHex(str.charCodeAt(i) & 0xff), 2, '0'); + } + return result; +} + +//var handleJSON = function(a){ alert('Unassigned JSONP: ' + a); } +var hexv = { + "00": 0, + "01": 1, + "02": 2, + "03": 3, + "04": 4, + "05": 5, + "06": 6, + "07": 7, + "08": 8, + "09": 9, + "0A": 10, + "0B": 11, + "0C": 12, + "0D": 13, + "0E": 14, + "0F": 15, + "10": 16, + "11": 17, + "12": 18, + "13": 19, + "14": 20, + "15": 21, + "16": 22, + "17": 23, + "18": 24, + "19": 25, + "1A": 26, + "1B": 27, + "1C": 28, + "1D": 29, + "1E": 30, + "1F": 31, + "20": 32, + "21": 33, + "22": 34, + "23": 35, + "24": 36, + "25": 37, + "26": 38, + "27": 39, + "28": 40, + "29": 41, + "2A": 42, + "2B": 43, + "2C": 44, + "2D": 45, + "2E": 46, + "2F": 47, + "30": 48, + "31": 49, + "32": 50, + "33": 51, + "34": 52, + "35": 53, + "36": 54, + "37": 55, + "38": 56, + "39": 57, + "3A": 58, + "3B": 59, + "3C": 60, + "3D": 61, + "3E": 62, + "3F": 63, + "40": 64, + "41": 65, + "42": 66, + "43": 67, + "44": 68, + "45": 69, + "46": 70, + "47": 71, + "48": 72, + "49": 73, + "4A": 74, + "4B": 75, + "4C": 76, + "4D": 77, + "4E": 78, + "4F": 79, + "50": 80, + "51": 81, + "52": 82, + "53": 83, + "54": 84, + "55": 85, + "56": 86, + "57": 87, + "58": 88, + "59": 89, + "5A": 90, + "5B": 91, + "5C": 92, + "5D": 93, + "5E": 94, + "5F": 95, + "60": 96, + "61": 97, + "62": 98, + "63": 99, + "64": 100, + "65": 101, + "66": 102, + "67": 103, + "68": 104, + "69": 105, + "6A": 106, + "6B": 107, + "6C": 108, + "6D": 109, + "6E": 110, + "6F": 111, + "70": 112, + "71": 113, + "72": 114, + "73": 115, + "74": 116, + "75": 117, + "76": 118, + "77": 119, + "78": 120, + "79": 121, + "7A": 122, + "7B": 123, + "7C": 124, + "7D": 125, + "7E": 126, + "7F": 127, + "80": 128, + "81": 129, + "82": 130, + "83": 131, + "84": 132, + "85": 133, + "86": 134, + "87": 135, + "88": 136, + "89": 137, + "8A": 138, + "8B": 139, + "8C": 140, + "8D": 141, + "8E": 142, + "8F": 143, + "90": 144, + "91": 145, + "92": 146, + "93": 147, + "94": 148, + "95": 149, + "96": 150, + "97": 151, + "98": 152, + "99": 153, + "9A": 154, + "9B": 155, + "9C": 156, + "9D": 157, + "9E": 158, + "9F": 159, + "A0": 160, + "A1": 161, + "A2": 162, + "A3": 163, + "A4": 164, + "A5": 165, + "A6": 166, + "A7": 167, + "A8": 168, + "A9": 169, + "AA": 170, + "AB": 171, + "AC": 172, + "AD": 173, + "AE": 174, + "AF": 175, + "B0": 176, + "B1": 177, + "B2": 178, + "B3": 179, + "B4": 180, + "B5": 181, + "B6": 182, + "B7": 183, + "B8": 184, + "B9": 185, + "BA": 186, + "BB": 187, + "BC": 188, + "BD": 189, + "BE": 190, + "BF": 191, + "C0": 192, + "C1": 193, + "C2": 194, + "C3": 195, + "C4": 196, + "C5": 197, + "C6": 198, + "C7": 199, + "C8": 200, + "C9": 201, + "CA": 202, + "CB": 203, + "CC": 204, + "CD": 205, + "CE": 206, + "CF": 207, + "D0": 208, + "D1": 209, + "D2": 210, + "D3": 211, + "D4": 212, + "D5": 213, + "D6": 214, + "D7": 215, + "D8": 216, + "D9": 217, + "DA": 218, + "DB": 219, + "DC": 220, + "DD": 221, + "DE": 222, + "DF": 223, + "E0": 224, + "E1": 225, + "E2": 226, + "E3": 227, + "E4": 228, + "E5": 229, + "E6": 230, + "E7": 231, + "E8": 232, + "E9": 233, + "EA": 234, + "EB": 235, + "EC": 236, + "ED": 237, + "EE": 238, + "EF": 239, + "F0": 240, + "F1": 241, + "F2": 242, + "F3": 243, + "F4": 244, + "F5": 245, + "F6": 246, + "F7": 247, + "F8": 248, + "F9": 249, + "FA": 250, + "FB": 251, + "FC": 252, + "FD": 253, + "FE": 254, + "FF": 255 +}; + +function pad(str, len, pad) { + var result = str; + for (var i = str.length; i < len; i++) { + result = pad + result; + } + return result; +} + +var digitArray = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'); + +function toHex(n) { + var result = '' + var start = true; + for (var i = 32; i > 0;) { + i -= 4; + var digit = (n >> i) & 0xf; + if (!start || digit != 0) { + start = false; + result += digitArray[digit]; + } + } + return (result == '' ? '0': result); +} + + (function($) { + var raphaelPaper; + var raphaelObjs; + var tutorialGuide; + + // I don't like this at all... I intend to make this pull via ajax from a key value data store. + var pages = + [ + //////////////////////////////////////////////////////////////////////// + // Lesson 1 + // Simple addition + { + lesson: 1, + title: 'Basics; numbers, strings, etc.', + guide: + '

' + rmsg(['Learning By Numbers', 'Music is Math', 'Back to Basics']) + + '

' + + "

To kick off let's try some maths out. Up there you can" + + " type in Haskell expressions. Try this out: 5 + 7

" + }, + { + guide: function(result) { + if (!result) result = { + expr: '5+7', + result: 12 + }; + var complied = result.expr.replace(/ /g, '') == "5+7"; + var who = complied ? 'we': 'you'; + return '

' + rmsg(['Your first Haskell expression', + "First Time's a Charm"]) + '

' + + '

Well done, you typed it perfect! You got back the number' + + ' ' + result.result + '. Just what ' + who + ' wanted. ' + + "

Let's try something completely different." + + " Type in your name like this:" + + ' "chris"

' + }, + trigger: function(result) { + return result.type == "(Num t) => t" || + result.type == "Integer" || + result.type == "Int"; + } + }, + // Strings & types + { + guide: function(result) { + if (!result) result = { + expr: '"chris"', + result: "\"chris\"" + }; + var n = unString(result.result); + if (n) n = ", " + n; + n += "!"; + return '

' + rmsg(['Types of values', "What's in a name?"]) + + '

' + + '

Hi there' + htmlEncode(n) + + (n != "!" ? " That's a pretty name. Honest.": "") + + " You're getting the hang of this!

" + + "

Note: You can chat to Haskell programmers while learning here, enter chat to start it." + + " You will join the official IRC channel of the Haskell community!

" + + "

Each time, you're getting back the value of the expression. So " + + "far, just a number and a list of characters.

" + + "

You can have lists of other stuff, too. Let's see your " + + " lottery numbers: [42,13,22]

" + }, + trigger: function(result) { + return result.type == "[Char]" + || result.type == "String"; + } + }, + // Overview of lesson 1 + { + guide: function(result) { + if (!result) result = { + result: "[42,13,22]" + }; + return '

' + rmsg(["Lesson 1 done already!"]) + + '

' + + "

Great, you made a list of numbers! If you win we'll split" + + " the winnings, right?

" + + "

Let's see what you've learned so far:

" + + "
    " + + "
  1. How to write maths and lists of things.
  2. " + + "
" + + "

You can do stuff with lists. Maybe you want the lottery " + + "numbers sorted in the right order, try this: " + + "sort " + result.result + "

" + }, + trigger: function(result) { + return result.expr.match(/^[ ]*\[[0-9, ]+\][ ]*$/) && + result.type == "(Num t) => [t]"; + } + }, + /////////////////////////////////////////////////////////////////////// + // Lesson 2 - Functions + // Functions on lists + { + lesson: 2, + title: 'Simple Functions', + guide: function(result) { + if (!result) result = { + result: "[13,23,30]" + }; + return '

' + rmsg(["We put the funk in function"]) + + '

' + + "

Congratulations, you just used a function." + + " They're how you get things done in Haskell." + + "

As you might've guessed, we got back " + + htmlEncode(result.result) + + ".

Ever wanted an evil twin nemesis? Me too. " + + "Luckily, you can sort lists of characters, or " + + "strings" + + ", in the same way as numbers! sort \"chris\"

" + }, + trigger: function(result) { + return result.expr.match(/sort/) && + result.type == "(Num t, Ord t) => [t]"; + } + }, + // Tuples + { + guide: function(result) { + if (!result) result = { + result: "\"chirs\"" + }; + nemesis = htmlEncode(unString(result.result)); + return '

' + + rmsg(["Tuples, because sometimes one value ain't enough!"]) + + '

' + + "

Watch out for " + nemesis + "! You should keep their credentials for the police.

" + + "

My nemesis is 28 years of age: " + + "(28,\"chirs\")

" + }, + trigger: function(result) { + return result.expr.match(/sort/) && + result.type == "[Char]"; + } + }, + // Functions on tuples + { + guide: function(result) { + if (!result) result = { + result: "(28,\"chirs\")" + }; + var age = result.result.match(/^\(([0-9]+)+/); + var villain = htmlEncode(result.result.replace(/\\"/g, '"')); + return '

' + + rmsg(["We'll keep them safe, don't worry about it."]) + + '

' + + "

Is " + (age ? age[1] : "that") + " a normal age for a " + + "super-villain?

" + + "

You just wrote a tuple. It's a way to keep a bunch of values together in Haskell. " + + "You can put as many as you like in there:

" + + "
  • (1,\"hats\",23/35)
  • (\"Shaggy\",\"Daphnie\",\"Velma\")
" + + "

Actually, let's say our villain is " + + "" + villain + "" + + ", how do you get their age?

" + + "fst " + villain + "" + }, + trigger: function(result) { + return result.expr.match(/\([0-9]+,[ ]*"[^"]+"\)/) && + result.type == "(Num t) => (t, [Char])"; + } + }, + // Summary of lesson 2 + { + guide: function(result) { + return '

' + + rmsg(["Lesson 2 done! Wow, great job!", + "Lesson 2 completo!"]) + + '

' + + + "

Good job! You got the age back from the tuple! Didn't " + + " even break a sweat, did you? The fst function " + + "just gets the first value. It's called \"fst\" because " + + "it's used a lot in Haskell so it really needs to be short!

" + + + "

Time to take a rest and see what you learned:

" + + "
    " + + "
  1. Functions can be used on lists of any type.
  2. " + + "
  3. We can stuff values into tuples.
  4. " + + "
  5. Getting the values back from tuples is easy.
  6. " + + "
" + + + "

Now let's say you want " + + " to use a value more than once, how would you do it? " + + "To make our lives easier, we can say:

" + + + "let x = 4 in x * x" + }, + trigger: function(result) { + return result.expr.match(/fst/) && + result.type == "(Num t) => t"; + } + }, + { + guide: function(result) { + return "

Let them eat cake

" + + + "

You just bound a variable. " + + "That is, you bound x to the expression 4, " + + " and then you can write x in some code (the body) and " + + " it will mean the same as if you'd written 4.

" + + + "

It's like this: let var = expression in body

" + + + "The in part just separates the expression from the body.

" + + + "

For example try: " + + "let x = 8 * 10 in x + x

" + + + "

So if we wanted to get the age of our villain, we could do:

" + + + "let villain = (28,\"chirs\") in fst villain" + + }, + trigger: function(result) { + return result.expr.match(/^[ ]*let[ ]+x[ ]*=[ ]*[0-9]+[ ]*in[ ]*x[ ]*\*[ ]*x/) && + result.type == "(Num t) => t"; + } + }, + { + guide: function(result) { + return "

Basics over, let's go!

" + + "

Next, let's take a short detour to learn about " + + "syntactic sugar. " + + "Try typing this out:

" + + "

'a' : []

" + + "

Or skip to lesson4 to learn about functions," + + " the meat of Haskell!"; + }, + trigger: function(result) { + return result.expr.match(/^[ ]*let[ ]+villain[ ]*=[ ]*\([0-9]+,[ ]*"[^"]+"\)[ ]*in[ ]+fst[ ]+villain[ ]*/) && + result.type == "(Num t) => t"; + } + }, + // Lesson 3: Syntactic sugar + { + lesson: 3, + title: 'Syntactic Sugar', + guide: function(result) { + return '

' + + rmsg(["You constructed a list!"]) + + '

' + + "

Well done, that was tricky syntax. You used the (:) " + + "function. It takes two values, some value and a list, and " + + " constructs a new list" + + " out of them. We call it 'cons' for short.

" + + "

'a' is " + + "the character 'a', [] is an empty list. So " + + "tacking 'a' at the start of an empty list just " + + "makes a list ['a']!

" + + "

But thankfully we don't have to type out " + + "'a' : 'b' : [] every time to we want to make a " + + "list of characters; we can use " + + "syntactic sugar and just write" + + " ['a','b']. Don't believe me, check this!

" + + "'a' : 'b' : [] == ['a','b']" + }, + trigger: function(result) { + return result.expr.match(/^[ ]*'a'[ ]*:[ ]*\[\][ ]*/) && + result.type == "[Char]"; + } + }, + // Booleans and string syntactic sugar + { + guide: function(result) { + return '

' + + rmsg(["You're on fire!"]) + + '

' + + "

You're handling this syntax really well, nice!

" + + "

You just got a boolean value back, and it said " + + "True. That means they're equal!

" + + "

One final demonstration on syntactic sugar for now:

" + + "['a','b','c'] == \"abc\"" + }, + trigger: function(result) { + return result.type == "Bool" && + result.expr.replace(/[^':\[\]\=,]/g, '') == "'':'':[]==['','']"; + } + }, + // Summary of syntactic sugar section + { + guide: function(result) { + return '

' + + rmsg(["Lesson 3 over! Syntactic sugar is sweet"]) + + '

' + + "

Let's have a gander at what you learned:

" + + "
    " + + "
  1. In 'a' : [], : is really just " + + " another function, just clever looking.
  2. " + + "
  3. Pretty functions like this are written like (:) when " + + " you talk about them.
  4. " + + "
  5. A list of characters ['a','b'] can just be written " + + "\"ab\". Much easier!
  6. " + + "
" + + "

Phew! You're getting pretty deep! Your arch nemesis, " + + nemesis + ", is gonna try to steal your " + rmsg(['mojo', + 'pizza']) + + "! Let's learn a bit more about functions and passing " + + "them around. Try this:

map (+1) [1..5]

"; + }, + trigger: function(result) { + return result.expr.replace(/[^\]\[',=\"]?/g, '') == "['','','']==\"\"" && + result.type == "Bool"; + } + }, + { + lesson: 4, + title: 'Functions, reloaded; passing, defining, etc.', + guide: function() { + var title = + rmsg(["Functions [of a Geisha]", + "Functions, functors, functoids, funky", + "Functions: Expanded fo' real"]); + return "

" + title + "

" + + + "

Here's where the magic begins!

" + + + "

You just passed the (+1) " + + "function to the map function.

" + + + "

You can try other things like (remember: click to insert them):

" + + + "
    " + + "
  • map (*99) [1..10]
  • " + + "
  • map (/5) [13,24,52,42]
  • " + + "
  • filter (>5) [62,3,25,7,1,9]
  • " + + "
" + + + "

Note that a tuple is different to a list because you can do this:

" + + "(1,\"George\")" + }, + trigger: function(result) { + return result.expr.match(/^[ ]*map[ ]+\(\+1\)[ ]*\[1..5\][ ]*$/) && + result.type == "(Num a, Enum a) => [a]"; + } + }, + { + guide: function(result) { + return "

Lists and Tuples

" + + + "

You can only " + + " have a list of numbers or a list of characters, whereas in a tuple you can throw anything in!

" + + + "

We've also seen that you can make a new list with (:) that joins two values together, like:

" + + "

1 : [2,3]

" + + + "

But we can't do this with tuples! You can only write a tuple and then look at what's inside. You can't make new ones on the fly like a list." + + + "

Let's write our own functions! It's really easy. How about something simple:

" + + "let square x = x * x in square " + rmsg([52, 10, 3]) + "" + + }, + trigger: function(result) { + return result.expr.match(/^[ ]*\(1,"[^"]+"\)[ ]*$/) && + result.type == "(Num t) => (t, [Char])"; + } + }, + { + guide: function(result) { + return "

Let there be functions

" + + "

Nice one! I think you're getting used to the let syntax.

" + + "

You defined a function. You can read it as, as for a given " + + "parameter called x, square of " + + "x is x * x." + + "

Some others you can try are:

" + + "
  • let add1 x = x + 1 in add1 5
  • " + + "
  • let second x = snd x in second (3,4)
  • " + + "
" + + "

Let's go crazy and use our square function with map:

" + + "let square x = x * x in map square [1..10]" + }, + trigger: function(result) { + return result.expr.match(/^[ ]*let[ ]*square[ ]+x[ ]*=[ ]*x[ ]*\*[ ]*x[ ]*in[ ]*square[ ]+[0-9]+/) && + result.type == "(Num t) => t"; + } + }, + { + guide: function(result) { + if (!result || !result.value) result = { + value: "[1,4,9,16,25,36,49,64,81,100]" + }; + return "

Let there be functions

" + + + "

That's so cool! You described a simple function square and then " + + "you just passed it to another function (map) and got back " + + htmlEncode(result.value) + ", exactly what you expected!

" + + + "

Haskell is pretty good at composing things together like this. " + + "Some other things you can try are:

" + + + "
    " + + "
  • let add1 x = x + 1 in map add1 [1,5,7]
  • " + + "
  • let take5s = filter (==5) in take5s [1,5,2,5,3,5]
  • " + + "
  • let take5s = filter (==5) in map take5s [[1,5],[5],[1,1]]
  • " + + "
" + + + "

Did you get back what you expected?

" + + + "

One more example for text; how do you upcase a letter?

" + + + "

toUpper 'a'

" + }, + trigger: function(result) { + return result.expr.match(/^[ ]*let[ ]+square[ ]+x[ ]*=[ ]*x[ ]*\*[ ]*x[ ]*in[ ]+map[ ]+square[ ]*\[1..10\][ ]*$/) && + result.type == "(Num a, Enum a) => [a]"; + } + }, + { + guide: function(result) { + return "

Exercise time!

" + + + "

Easy! Remember: characters are written like 'a' and " + + "strings (lists of characters) are written like \"a\"." + + + "

I need you to use toUpper capitalise my whole name, " + + "\"Chris\". Give it a try." + + " You can do it, I believe in you!

" + + + '

Spoiler: map toUpper "Chris"

' + }, + trigger: function(result) { + return result.expr.match(/^toUpper 'a'$/) && + result.type == "Char"; + } + }, + { + guide: function(result) { + return "

Lesson 4 complete!

" + + + "

Brilliant! You're making excellent progress! " + + "You just passed toUpper to map. No problem.

" + + + "

Let's go over what you've learned in this lesson:

" + + + "
    " + + "
  1. Functions like map take other functions as parameters.
  2. " + + "
  3. Functions like (+1), (>5) and " + + "square can be passed to other functions.
  4. " + + "
  5. Defining functions is just a case of writing what " + + "to do with the parameters.
  6. " + "
" + + + "

Let's check out pattern matching; a way to " + + "get values from other values using patterns. Try this:

" + + "

let (a,b) = (10,12) in a * 2

" + + + "

Or you can skip this section and go to straight to lesson6; types!

" + }, + trigger: function(result) { + return result.type == "[Char]" && + result.expr.match(/^map[ ]+toUpper/); + } + }, + { + lesson: 5, + title: 'Pattern Matching', + guide: function(result) { + var title = + rmsg(["And therefore, patterns emerge in nature.", + "And Then Patterns", + "Pattern matching!"]) + return "

" + title + "

" + + + "

Jolly good show!

" + + "

So you had a value (10,12) and matched " + + "it against a pattern (a,b), then you were able" + + " to do stuff with the a and b!" + + + "

Note: Pattern matching (a,b) against " + + "(1,2) to get the a is the same as" + + " doing fst (1,2), like you did in step7!

" + + + "

A pattern always matches the way the " + + "value was originally constructed. Remember that \"abc\" is " + + "syntactic sugar for 'a' : 'b' : 'c' : [].

" + + + "

So you can get the characters from a string with patterns:

" + + + "let (a:b:c:[]) = \"xyz\" in a" + }, + trigger: function(result) { + return result.expr.match(/^[ ]*let[ ]+\(a,b\)[ ]+=[ ]+\(10,12\)[ ]+in[ ]+a[ ]*\*[ ]*2[ ]*$/) && + result.type == "(Num t) => t"; + } + }, + { + guide: function(result) { + return "

" + rmsg(["Ignorance is bliss", "Ignoring values"]) + "

" + + + "

You're getting into tricky syntax, huh? I know you can handle it!

" + + + "

If you just want some of the values, you can ignore the others with _ (underscore) like this:

" + + + "

let (a:_:_:_) = \"xyz\" in a

" + + + "

In fact, (a:b:c:d) is short-hand for " + + "(a:(b:(c:d))), so you can just ignore the rest in one go:

" + + + "let (a:_) = \"xyz\" in a" + }, + trigger: function(result) { + return result.expr.match(/^[ ]*let[ ]+\(a:b:c:\[\]\)[ ]*=[ ]*\"xyz\"[ ]*in[ ]+a[ ]*$/) && + result.type == "Char"; + } + }, + { + guide: function(result) { + return "

" + rmsg(["Exercise!", "Show me the money!"]) + "

" + + + "

Try to get the 'a' value from this value using pattern matching:

" + + "

(10,\"abc\")

" + + + "

Spoiler: let (_,(a:_)) = (10,\"abc\") in a

" + }, + trigger: function(result) { + return result.expr.match(/^[ ]*let[ ]*\(a:_\)[ ]*=[ ]*"xyz"[ ]*in[ ]*a[ ]*$/) && + result.type == "Char"; + } + }, + { + guide: function(result) { + return "

" + rmsg(["Well done!", "Brilliant!", "Perfetto!"]) + "

" + + + "

Wizard! I think you've got pattern-matching down.

" + + + "

If you're still a bit unsure, here are some other things you can try:

" + + + "
    " + + "
  • let _:_:c:_ = \"abcd\" in c
  • " + + "
  • let [a,b,c] = \"cat\" in (a,b,c)
  • " + + "
" + + + "

You can also grab a whole value and pattern match on it (have your cake and eat it too):

" + + + "let abc@(a,b,c) = (10,20,30) in (abc,a,b,c)" + }, + trigger: function(result) { + return result.expr.match(/^[ ]*let[ ]*\(_,\(?a:_\)?\)[ ]*=[ ]*\(10,\"abc\"\)[ ]*in[ ]*a[ ]*$/) && + result.type == "Char"; + } + }, + { + guide: function(result) { + return "

" + rmsg(["And that's the end of that chapter"]) + "

" + + + "

That was easy, right?

" + + + "

Let's go over what you've learned in this lesson:

" + + + "
    " + + "
  1. Values are pattern matched, or deconstructed, by writing however they were constructed.
  2. " + + "
  3. Patterns let you use the values that you match.
  4. " + + "
  5. You can ignore whichever values you want.
  6. " + + "
  7. You can pattern match and keep hold of the original value too.
  8. " + + "
" + + + "

Now we get to the Deep Ones. Types!

" + + + "

Consider the following value: 'a'

" + + }, + trigger: function(result) { + return result.type == "(Num t, Num t1, Num t2) => ((t, t1, t2), t, t1, t2)"; + } + }, + { + lesson: 6, + title: 'Types', + guide: function(result) { + showTypes = true; + return "

" + rmsg(["Types", "What's in a Type?", "Types & Values"]) + "

" + + "

What's this? Something new!

" + + + "

In Haskell there are types of values. Every value belongs to a type. To demonstrate this fact, I've sneakily enabled types to be " + + "shown of every value in the console from now on.

" + + + "

The type of the value 'a' is Char (short for 'character', but you guessed that, right?).

" + + + "

You've seen the type of a character, now what about" + + " a list of characters?

" + + "\"Spartacus\"" + }, + trigger: function(result) { + return result.type == 'Char'; + } + }, + { + guide: function(result) { + showTypes = true; + return "

" + rmsg(["Lists of stuff, types"]) + "

" + + + "

I'm Spartacus!

" + + + "

Okay, so a list of characters has type [Char].

" + + + "

Notice that when we write a :: X it means the value a has type X. It's just a short-hand called a signature.

" + + + "

If you just want the type of a value, without actually evaluating it, you can just type:

" + + ":t toUpper" + }, + trigger: function(result) { + return result.expr.match(/"[^"]+"/) && + result.type == '[Char]'; + } + }, + { + guide: function(result) { + showTypes = true; + return "

" + rmsg(["Function types"]) + "

" + + + "

Woah! Hold your blinkin' 'orses! The type of toUpper reads: Char -> Char

" + + + "

It's pretty easy; a -> b means function from a to b. " + + "So

toUpper :: Char -> Char means: for a" + + " given character (Char value) a, toUpper a has type Char.

" + + + "

Some other things you can try are:

" + + + "
  • :t words
  • " + + "
  • :t unwords
  • " + + "
  • :t True
  • " + + "
  • :t not
  • " + + "
" + + + "

The words function is pretty handy. Want to get a list of words from a sentence?

" + + "words \"There's jam in my pants.\"" + }, + trigger: function(result) { + return result.type == 'Char -> Char'; + } + }, + { + guide: function(result) { + showTypes = true; + return "

" + rmsg(["Mid-way review"]) + "

" + + + "

The type of words was String -> [String]. You got a list of strings back! Just what you expected, right?

" + + + "

Let's take a rest in the middle of this lesson and go over what we've learned:

" + + + "
    " + + "
  1. All values in Haskell have a type. We describe the types of values with signatures, like True :: Bool.
  2. " + + "
  3. Functions are values too, and they have types, notated a -> b.
  4. " + + "
  5. Functions can be defined for any type to any other type.
  6. " + + "
  7. Humble reader has a thing for jammy pants.
  8. " + + "
" + + + "

But what if you have a type that can contain values of any type, like a tuple?

" + + ":t fst" + + }, + trigger: function(result) { + return result.expr.match(/^[ ]*words[ ]*\"[^"]+\"[ ]*$/) && + result.type == '[String]'; + } + }, + { + guide: function(result) { + showTypes = true; + return "

" + rmsg(["Polymorphic functions"]) + "

" + + + "

Remember this one? I know you do! fst (1,2) is 1, right?

" + + "

We read its type

" + + "

fst :: (a, b) -> a

" + + "

as: for all types a and b, the fst has type (a,b) to a. So the fst " + + "function works on a pair of values of any types! We call such a function polymorphic." + + "

" + + "

Remember the drop function? Maybe you don't. I don't! Let's check out its type:

" + + "

:t drop

" + }, + trigger: function(result) { + return result.type == '(a, b) -> a'; + } + }, + { + guide: function(result) { + showTypes = true; + return "

" + rmsg(["Multi parameter functions"]) + "

" + + + "

So the drop function has type

Int -> [a] -> [a].

" + + + "

This is something new. You've got two arrows! Relax. You can read

" + + "

a -> b -> c as a -> (b -> c)

" + + + "

In other words, drop is a function from integers (Int values) to functions of lists to lists ([a] -> [a] values). Drop is a function to another function.

" + + + "

Check for yourself! :t drop 3

" + }, + trigger: function(result) { + return result.type == 'Int -> [a] -> [a]'; + } + }, + { + guide: function(result) { + showTypes = true; + return "

" + rmsg(["Partial application"]) + "

" + + + "

You've got a function of type [a] -> [a]! The drop function is considered a multi-parameter function. Remember the map function? Its parameters were a function and a list. Just another multi-parameter function.

" + + + "

You can add another parameter and, hey presto, you get a list!

" + + + "drop 3 \"hello!\"" + + }, + trigger: function(result) { + return result.type == '[a] -> [a]'; + } + }, + { + guide: function(result) { + showTypes = true; + return "

" + rmsg(["Higher order functions"]) + "

" + + + "

'Lo bob! You've already used the map function loads. I wonder if you can guess its type?

" + + + "

map :: (a -> b) -> [a] -> [b] (spoiler)

" + + + "

It's okay to peek! Have a go at guessing these: filter, take

" + + + "

Tip: You can use parantheses to use more than one function. You want to double all the numbers over five? Psch!

" + + "map (*2) (filter (>5) [10,2,16,9,4])" + }, + trigger: function(result) { + return result.expr.match(/^[ ]*drop[ ]*[0-9]+[ ]*"[^"]+"[ ]*$/) && + result.type == '[Char]'; + } + }, + { + guide: function(result) { + showTypes = true; + return "

" + rmsg(["Phew! Rest time!"]) + "

" + + + "

Wow! You're doing so great! Have a look at what you know now!

" + + + "
    " + + "
  1. Function parameters can be polymorphic; any type!
  2. " + + "
  3. Functions can have multiple parameters by returning more functions.
  4. " + + "
  5. You can wrap expressions in parentheses and apply functions to them as a whole value.
  6. " + + "
" + + + "

You're really making great progress. Don't hesitate to sit and play in the console between chapters to get a good feel of it!

" + + + "

Stay tuned for more chapters on type classes and the meaning of :t 1, :t (*), etc.

" + + learnMore + }, + trigger: function(result) { + return result.type == '(Num a, Ord a) => [a]'; + } + } + ]; + // + // var webchat; + // + // function runWebchat() { + // if (!webchat) { + // // Create webchat frame + // var webchat = + // $(''); + // webchat.attr('width', 635); + // webchat.attr('height', 500); + // webchat.css('float', 'left'); + // webchat.css('webkit-border-radius', '3px'); + // webchat.css('moz-border-radius', '3px'); + // webchat.css('border-radius', '3px'); + // webchat.css('border', '5px solid #eeeeee'); + // + // // Extend page wrap to fit console and chat + // $('.page-wrap').css({ + // width: '1250px' + // }); + // $('.primary-content').css('margin-left', 0); + // $('.page-wrap').append(webchat.css('margin-left', '5px')); + // } + // } + + var pageTrigger = -1; + var notices = []; + var controller; + // Console controller + var learnMore; + + //////////////////////////////////////////////////////////////////////// + // Unshow a string + function unString(str) { + return str.replace(/^"(.*)"$/, "$1").replace(/\\"/, '"'); + } + + //////////////////////////////////////////////////////////////////////// + // Random message from a list of messages + function rmsg(choices) { + return choices[Math.floor((Math.random() * 100) % choices.length)]; + } + + // Simple HTML encoding + // Simply replace '<', '>' and '&' + // TODO: Use jQuery's .html() trick, or grab a proper, fast + // HTML encoder. + function htmlEncode(text, shy) { + return ( + ('' + text).replace(/&/g, '&') + .replace(/'); + handleJSON = function(r) { + script.remove(); + func(r); + }; + script.attr('src', url); + $('body').append(script); + } + + + var console = $('.console'); + controller = console.console({ + promptLabel: '> ', + continuedPromptLabel: ' .. ', /*not quiet right but good enough*/ + cancelHandle: function() { + controller.commandRef.ignore = true; + controller.finishCommand(); + controller.report(); + }, + + commandHandle: function(line, report) { + + controller.ajaxloader = $('

Loading...

'); + var commandRef = {}; + controller.currentLine = line; + controller.commandRef = commandRef; + controller.report = report; + if (tellAboutRet) tellAboutRet.fadeOut(function() { + $(this).remove(); + }); + + if (libTrigger(line, report)) return; + controller.inner.append(controller.ajaxloader); + controller.scrollToBottom(); + + jsonp("http://localhost:3030/tryruby/run/?cmd=" + encodeHex(line), + function(resp) { + if (commandRef.ignore) { + return; + } + controller.finishCommand(); + var result = resp; + if (pageTrigger > -1 && result.expr) { + triggerTutorialPage(pageTrigger, result); + } + if (result.type) { + if (pageTrigger == 24) showTypes = false; + handleSuccess(report, result); + } else if (result.error) { + report( + [{ + msg: result.error, + className: "jquery-console-message-error jquery-console-message-compile-error" + }] + ); + notice('compile-error', + "A compile-time error! " + + "It just means the expression wasn't quite right. " + + "Try again.", + 'prompt'); + } else if (result.exception) { + var err = limitsError(result.exception); + report( + [{ + msg: err, + className: "jquery-console-message-error jquery-console-message-exception" + }] + ); + if (err == result.exception) { + notice('compile-error', + "A run-time error! The expression was right but the" + + " result didn't make sense. Check your expression and try again.", + 'prompt'); + } + } else if (result.internal) { + report( + [{ + msg: limitsError(result.internal), + className: "jquery-console-message-error jquery-console-message-internal" + }] + ); + } else if (result.bind) { + report(); + } else if (result.result) { + if (result.expr.match(/^:modules/)) { + report( + [{ + msg: result.result.replace(/[\["\]]/g, '') + .replace(/,/g, ', '), + className: "jquery-console-message-type" + }]); + } + } + }); + + }, + + charInsertTrigger: function() { + var t = notice('tellaboutreturn', + "Hit Return when you're " + + "finished typing your expression."); + if (t) tellAboutRet = t; + return true; + }, + autofocus: true, + promptHistory: true, + historyPreserveColumn: true, + welcomeMessage: 'Interactive ruby ready' + }); + + controller.finishCommand = function() { + controller.ajaxloader.remove(); + $('.jquery-console-prompt :last').each(function() { + lastLine = controller.currentLine; + if (!$(this).hasClass('prompt-done')) { + $(this).addClass('prompt-done'); + $(this).click(function() { + controller.promptText(controller.currentLine); + }); + } + }); + } + + makeGuidSamplesClickable(); + + var match = window.location.href.match(/#([0-9]+)$/); + if (match) { + pageTrigger = match[1] - 1; + setTutorialPage(undefined, match[1] - 1); + } + + var match = window.location.href.match(/\?input=([^&]+)/); + if (match) { + controller.promptText(urlDecode(match[1])); + controller.inner.click(); + controller.typer.consoleControl(13); + } + }); + + function urlDecode(encodedString) { + var output = encodedString; + var binVal, + thisString; + var myregexp = /(%[^%]{2})/; + while ((match = myregexp.exec(output)) != null + && match.length > 1 + && match[1] != '') { + binVal = parseInt(match[1].substr(1), 16); + thisString = String.fromCharCode(binVal); + output = output.replace(match[1], thisString); + } + return output; + } + + function makeGuidSamplesClickable() { + $('.chapmark code').each(function() { + $(this).css('cursor', 'pointer'); + $(this).attr('title', 'Click me to insert "' + + $(this).text() + '" into the console.'); + $(this).click(function() { + controller.promptText($(this).text()); + controller.inner.click(); + }); + }); + } + + String.prototype.trim = function() { + return this.replace(/^[\t ]*(.*)[\t ]*$/, '$1'); + }; + + //////////////////////////////////////////////////////////////////////// + // Trigger console commands + function libTrigger(line, report) { + switch (line.trim()) { + case 'help': + { + setTutorialPage(undefined, 0); + report(); + pageTrigger = 0; + return true; + } + case 'back': + { + if (pageTrigger > 0) { + setTutorialPage(undefined, pageTrigger - 1); + pageTrigger--; + report(); + return true; + } + break; + } + case 'lessons': + { + var lessons = $('
    '); + for (var i = 0; i < pages.length; i++) { + if (pages[i].lesson) { + lessons.append($('
  1. '). + html('lesson' + pages[i].lesson + ' - ' + + pages[i].title)); + } + } + var lessonsList = '

    Lessons

    ' + lessons.html(); + tutorialGuide.animate({ + opacity: 0, + height: 0 + }, + 'fast', + function() { + tutorialGuide.html(lessonsList); + tutorialGuide.css({ + height: 'auto' + }); + tutorialGuide.animate({ + opacity: 1 + }, + 'fast'); + makeGuidSamplesClickable(); + }); + report(); + return true; + } + default: + { + if (line.trim() == 'chat') { + notice('irc', + 'Enter your nick on the right hand side and hit Connect!', + 'prompt'); + report(); + runWebchat(); + return true; + } + + var m = line.trim().match(/^link(.*)/); + if (m) { + var data; + if (m[1]) data = m[1].trim(); + else if (lastLine) data = lastLine; + if (data) { + var addr = '?input=' + encodeHex(data); + report([{ + msg: '', + className: 'latest-link' + }]); + var link = $(''). + text('link for ' + data).click(function() { + window.location.href = $(this).attr('href'); + return false; + }); + $('.latest-link').html(link).removeClass('latest-link'); + return true; + } + } + + var m = line.trim().match(/^step([0-9]+)/); + if (m) { + if ((m[1] * 1) <= pages.length) { + setTutorialPage(undefined, m[1] - 1); + report(); + pageTrigger = m[1] - 1; + return true; + } + } + var m = line.trim().match(/^help ([0-9]+)/); + if (m) { + for (var i = 0; i < pages.length; i++) { + if (pages[i].lesson == m[1] * 1) { + setTutorialPage(undefined, i); + report(); + pageTrigger = i; + return true; + } + } + } + } + }; + }; + + //////////////////////////////////////////////////////////////////////// + // Change the tutorial page + function setTutorialPage(result, n) { + if (pages[n]) { + window.location.href = '#' + (1 * n + 1); + tutorialGuide.find('#helpstone').remove(); + tutorialGuide.animate({ + opacity: 0, + height: 0 + }, + 'fast', + function() { + if (typeof(pages[n].guide) == 'function') + tutorialGuide.html(pages[n].guide(result)); + else + tutorialGuide.html(pages[n].guide); + var back = ''; + if (pageTrigger > 0) + back = 'You\'re at step' + (n + 1) + + '. Type back to go back.'; + else + back = 'You\'re at step' + (n + 1) + '. Type step' + (n + 1) + + ' to return here.'; + if (true) tutorialGuide + .append('
    ' + back + '
    ') + .append('
    Lesson: ' + + searchLessonBack(n) + + '
    '); + tutorialGuide.css({ + height: 'auto' + }); + tutorialGuide.animate({ + opacity: 1 + }, + 'fast'); + makeGuidSamplesClickable(); + }); + } + }; + + function searchLessonBack(page) { + for (var i = page; i >= 0; i--) { + if (pages[i].lesson) return pages[i].lesson; + } + return "1"; + } + + //////////////////////////////////////////////////////////////////////// + // Trigger a page according to a result + function triggerTutorialPage(n, result) { + n++; + if (pages[n] && (typeof(pages[n].trigger) == 'function') + && pages[n].trigger(result)) { + pageTrigger++; + setTutorialPage(result, n); + } + }; + + //////////////////////////////////////////////////////////////////////// + // Trigger various libraries after JSONRPC returned + function handleSuccess(report, result) { + if (result.type.match(/^Graphics\.Raphael\.Raphael[\r\n ]/)) { + runRaphael(result.result); + report(); + } + if (result.type.match(/standard/)) { + var hashrocket = ''; + var msg_value = ' => '; + if (result.result == null) { + hashrocket = ' => ' + msg_value = ''; + } + report( + [{ + msg: msg_value + result.output + hashrocket + result.result, + className: "jquery-console-message-value" + }]); + } + + if (result.type.match(/error/)) { + + report( + [{ + msg: result.error, + className: "jquery-console-message-value" + }]); + } + + if (result.type.match(/illegal/)) { + + report( + [{ + msg: 'You are not allowed to run that command!', + className: "jquery-console-message-value" + }]); + } + + + if (result.type.match(/line_continuation/)) { + report( + [{ + msg: '..', + className: "jquery-console-prompt-label" + }]); + } + + }; + + //////////////////////////////////////////////////////////////////////// + // Raphael support + function runRaphael(expr) { + raphaelPaper.clear(); + $('#raphael').parent().parent().slideDown(function() { + var exprs = expr.split(/\n/g); + for (var i = 0; i < exprs.length; i++) + raphaelRunExpr(exprs[i]); + }); + } + function raphaelRunExpr(expr) { + var expr = expr.split(/ /g); + switch (expr[0]) { + case 'new': + { + switch (expr[2]) { + case 'circle': + { + var x = expr[3], + y = expr[4], + radius = expr[5]; + var circle = raphaelPaper.circle(x * 1, y * 1, radius * 1); + circle.attr("fill", "#7360a4"); + break; + } + } + } + } + } + + function notice(name, msg, style) { + if (opera()) return; + if (!notices[name]) { + notices[name] = name; + return controller.notice(msg, style); + } + } + + function limitsError(str) { + if (str == "Terminated!") { + notice('terminated', + "This error means it took to long to work" + + " out on the server.", + 'fadeout'); + return "Terminated!"; + } else if (str == "Time limit exceeded.") { + notice('exceeded', + "This error means it took to long to work out on the server. " + + "Try again.", + 'fadeout'); + return "Terminated! Try again."; + } + return str; + } + +})(jQuery); diff --git a/tryruby/public/javascripts/jquery-1.3.2.min.js b/public/javascripts/jquery-1.3.2.min.js similarity index 100% rename from tryruby/public/javascripts/jquery-1.3.2.min.js rename to public/javascripts/jquery-1.3.2.min.js diff --git a/tryruby/public/javascripts/jquery-1.4.2.min.js b/public/javascripts/jquery-1.4.2.min.js similarity index 100% rename from tryruby/public/javascripts/jquery-1.4.2.min.js rename to public/javascripts/jquery-1.4.2.min.js diff --git a/tryruby/public/javascripts/jquery.console.js b/public/javascripts/jquery.console.js similarity index 68% rename from tryruby/public/javascripts/jquery.console.js rename to public/javascripts/jquery.console.js index 651da47..d45390d 100644 --- a/tryruby/public/javascripts/jquery.console.js +++ b/public/javascripts/jquery.console.js @@ -1,7 +1,7 @@ // JQuery Console 1.0 // Sun Feb 21 20:28:47 GMT 2010 // -// Copyright 2010 Chris Done. All rights reserved. +// Copyright 2010 Chris Done, Simon David Pratt. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions @@ -10,43 +10,86 @@ // 1. Redistributions of source code must retain the above // copyright notice, this list of conditions and the following // disclaimer. - +// // 2. Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials // provided with the distribution. // -// THIS SOFTWARE IS PROVIDED BY CHRIS DONE ``AS IS'' AND ANY EXPRESS -// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL CHRIS DONE OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR -// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -// DAMAGE. - -// The views and conclusions contained in the software and -// documentation are those of the authors and should not be -// interpreted as representing official policies, either expressed or -// implied, of Chris Done. -// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + // TESTED ON // Internet Explorer 6 // Opera 10.01 // Chromium 4.0.237.0 (Ubuntu build 31094) -// Firefox 3.5.8 +// Firefox 3.5.8, 3.6.2 (Mac) +// Safari 4.0.5 (6531.22.7) (Mac) +// Google Chrome 5.0.375.55 (Mac) (function($){ $.fn.console = function(config){ //////////////////////////////////////////////////////////////////////// // Constants // Some are enums, data types, others just for optimisation - var keyCodes = { left:37,right:39,up:38,down:40,back:8,del:46, - end:35,start:36,ret:13 }; + var keyCodes = { + // left + 37: moveBackward, + // right + 39: moveForward, + // up + 38: previousHistory, + // down + 40: nextHistory, + // backspace + 8: backDelete, + // delete + 46: forwardDelete, + // end + 35: moveToEnd, + // start + 36: moveToStart, + // return + 13: commandTrigger, + // tab + 18: doNothing + }; + var ctrlCodes = { + // C-a + 65: moveToStart, + // C-e + 69: moveToEnd, + // C-d + 68: forwardDelete, + // C-n + 78: nextHistory, + // C-p + 80: previousHistory, + // C-b + 66: moveBackward, + // C-f + 70: moveForward, + // C-k + 75: deleteUntilEnd + }; + var altCodes = { + // M-f + 70: moveToNextWord, + // M-b + 66: moveToPreviousWord, + // M-d + 68: deleteNextWord + }; var cursor = ' '; // Opera only works with this character, not or ­, // but IE6 displays this character, which is bad, so just use @@ -76,6 +119,10 @@ // variable below to ignore the keypress event if the keydown // event succeeds. var cancelKeyPress = 0; + // When this value is false, the prompt will not respond to input + var acceptInput = true; + // When this value is true, the command has been canceled + var cancelCommand = false; // External exports object var extern = {}; @@ -98,6 +145,7 @@ },100); } extern.inner = inner; + extern.typer = typer; extern.scrollToBottom = scrollToBottom; })(); @@ -152,6 +200,8 @@ function newPromptBox() { column = 0; promptText = ''; + ringn = 0; // Reset the position of the history ring + enableInput(); promptBox = $('
    '); var label = $(''); promptBox.append(label.text(promptLabel).show()); @@ -185,93 +235,50 @@ typer.keydown(function(e){ cancelKeyPress = 0; var keyCode = e.keyCode; - if (isControlCharacter(keyCode)) { - cancelKeyPress = keyCode; - if (!typer.consoleControl(keyCode)) { - return false; - } - } + // C-c: cancel the execution + if(e.ctrlKey && keyCode == 67) { + cancelKeyPress = keyCode; + cancelExecution(); + return false; + } + if (acceptInput) { + if (keyCode in keyCodes) { + cancelKeyPress = keyCode; + (keyCodes[keyCode])(); + return false; + } else if (e.ctrlKey && keyCode in ctrlCodes) { + cancelKeyPress = keyCode; + (ctrlCodes[keyCode])(); + return false; + } else if (e.altKey && keyCode in altCodes) { + cancelKeyPress = keyCode; + (altCodes[keyCode])(); + return false; + } + } }); //////////////////////////////////////////////////////////////////////// // Handle key press typer.keypress(function(e){ var keyCode = e.keyCode || e.which; - if (cancelKeyPress != keyCode && keyCode >= 32){ + if (isIgnorableKey(e)) { + return false; + } + if (acceptInput && cancelKeyPress != keyCode && keyCode >= 32){ if (cancelKeyPress) return false; - if (typeof config.charInsertTrigger == 'function' && - config.charInsertTrigger(keyCode)) + if (typeof config.charInsertTrigger == 'undefined' || + (typeof config.charInsertTrigger == 'function' && + config.charInsertTrigger(keyCode,promptText))) typer.consoleInsert(keyCode); } if ($.browser.webkit) return false; }); - // Is a keycode a contorl character? - // E.g. up, down, left, right, backspc, return, etc. - function isControlCharacter(keyCode){ - // TODO: Make more precise/fast. - return ( - (keyCode >= keyCodes.left && keyCode <= keyCodes.down) - || keyCode == keyCodes.back || keyCode == keyCodes.del - || keyCode == keyCodes.end || keyCode == keyCodes.start - || keyCode == keyCodes.ret - ); - }; - - //////////////////////////////////////////////////////////////////////// - // Handle console control keys - // E.g. up, down, left, right, backspc, return, etc. - typer.consoleControl = function(keyCode){ - switch (keyCode){ - case keyCodes.left:{ - moveColumn(-1); - updatePromptDisplay(); - return false; - break; - } - case keyCodes.right:{ - moveColumn(1); - updatePromptDisplay(); - return false; - break; - } - case keyCodes.back:{ - if (moveColumn(-1)){ - deleteCharAtPos(); - updatePromptDisplay(); - } - return false; - break; - } - case keyCodes.del:{ - if (deleteCharAtPos()) - updatePromptDisplay(); - return false; - break; - } - case keyCodes.end:{ - if (moveColumn(promptText.length-column)) - updatePromptDisplay(); - return false; - break; - } - case keyCodes.start:{ - if (moveColumn(-column)) - updatePromptDisplay(); - return false; - break; - } - case keyCodes.ret:{ - commandTrigger(); return false; - } - case keyCodes.up:{ - rotateHistory(-1); return false; - } - case keyCodes.down:{ - rotateHistory(1); return false; - } - default: //alert("Unknown control character: " + keyCode); - } + function isIgnorableKey(e) { + // for now just filter alt+tab that we receive on some platforms when + // user switches windows (goes away from the browser) + return ((e.keyCode == keyCodes.tab || e.keyCode == 192) && e.altKey); }; //////////////////////////////////////////////////////////////////////// @@ -301,6 +308,14 @@ updatePromptDisplay(); }; + function previousHistory() { + rotateHistory(-1); + }; + + function nextHistory() { + rotateHistory(1); + }; + // Add something to the history ring function addToHistory(line){ history.push(line); @@ -309,7 +324,7 @@ // Delete the character at the current position function deleteCharAtPos(){ - if (promptText != ''){ + if (column < promptText.length){ promptText = promptText.substring(0,column) + promptText.substring(column+1); @@ -318,6 +333,41 @@ } else return false; }; + function backDelete() { + if (moveColumn(-1)){ + deleteCharAtPos(); + updatePromptDisplay(); + } + }; + + function forwardDelete() { + if (deleteCharAtPos()) + updatePromptDisplay(); + }; + + function deleteUntilEnd() { + while(deleteCharAtPos()) { + updatePromptDisplay(); + } + }; + + function deleteNextWord() { + // A word is defined within this context as a series of alphanumeric + // characters. + // Delete up to the next alphanumeric character + while(column < promptText.length && + !isCharAlphanumeric(promptText[column])) { + deleteCharAtPos(); + updatePromptDisplay(); + } + // Then, delete until the next non-alphanumeric character + while(column < promptText.length && + isCharAlphanumeric(promptText[column])) { + deleteCharAtPos(); + updatePromptDisplay(); + } + }; + //////////////////////////////////////////////////////////////////////// // Validate command and trigger it if valid, or show a validation error function commandTrigger() { @@ -341,35 +391,48 @@ inner.attr({ scrollTop: inner.attr("scrollHeight") });; }; + function cancelExecution() { + if(typeof config.cancelHandle == 'function') { + config.cancelHandle(); + } + } + //////////////////////////////////////////////////////////////////////// // Handle a command function handleCommand() { if (typeof config.commandHandle == 'function') { + disableInput(); + addToHistory(promptText); var ret = config.commandHandle(promptText,function(msgs){ commandResult(msgs); }); if (typeof ret == 'boolean') { if (ret) { // Command succeeded without a result. - addToHistory(promptText); commandResult(); } else { - addToHistory(promptText); commandResult('Command failed.', "jquery-console-message-error"); } } else if (typeof ret == "string") { - addToHistory(promptText); commandResult(ret,"jquery-console-message-success"); - } else if (typeof ret == 'undefined') { - addToHistory(promptText); - } else if (ret.length) { - addToHistory(promptText); + } else if (typeof ret == 'object' && ret.length) { commandResult(ret); } } }; + //////////////////////////////////////////////////////////////////////// + // Disable input + function disableInput() { + acceptInput = false; + }; + + // Enable input + function enableInput() { + acceptInput = true; + } + //////////////////////////////////////////////////////////////////////// // Reset the prompt in invalid command function commandResult(msg,className) { @@ -419,6 +482,78 @@ } else return false; }; + function moveForward() { + if(moveColumn(1)) { + updatePromptDisplay(); + return true; + } + return false; + }; + + function moveBackward() { + if(moveColumn(-1)) { + updatePromptDisplay(); + return true; + } + return false; + }; + + function moveToStart() { + if (moveColumn(-column)) + updatePromptDisplay(); + }; + + function moveToEnd() { + if (moveColumn(promptText.length-column)) + updatePromptDisplay(); + }; + + function moveToNextWord() { + while(column < promptText.length && + !isCharAlphanumeric(promptText[column]) && + moveForward()) { + } + while(column < promptText.length && + isCharAlphanumeric(promptText[column]) && + moveForward()) { + } + }; + + function moveToPreviousWord() { + // Move backward until we find the first alphanumeric + while(column -1 >= 0 && + !isCharAlphanumeric(promptText[column-1]) && + moveBackward()) { + } + // Move until we find the first non-alphanumeric + while(column -1 >= 0 && + isCharAlphanumeric(promptText[column-1]) && + moveBackward()) { + } + }; + + function isCharAlphanumeric(charToTest) { + if(typeof charToTest == 'string') { + var code = charToTest.charCodeAt(); + return (code >= 'A'.charCodeAt() && code <= 'Z'.charCodeAt()) || + (code >= 'a'.charCodeAt() && code <= 'z'.charCodeAt()) || + (code >= '0'.charCodeAt() && code <= '9'.charCodeAt()); + } + return false; + }; + + function doNothing() {}; + + extern.promptText = function(text){ + if (text) { + promptText = text; + if (column > promptText.length) + column = promptText.length; + updatePromptDisplay(); + } + return promptText; + }; + //////////////////////////////////////////////////////////////////////// // Update the prompt display function updatePromptDisplay(){ diff --git a/tryruby/public/javascripts/jquery.console.min.js b/public/javascripts/jquery.console.min.js similarity index 100% rename from tryruby/public/javascripts/jquery.console.min.js rename to public/javascripts/jquery.console.min.js diff --git a/tryruby/public/javascripts/jquery.js b/public/javascripts/jquery.js similarity index 100% rename from tryruby/public/javascripts/jquery.js rename to public/javascripts/jquery.js diff --git a/tryruby/public/javascripts/json2.js b/public/javascripts/json2.js similarity index 100% rename from tryruby/public/javascripts/json2.js rename to public/javascripts/json2.js diff --git a/tryruby/public/javascripts/lib.min.js b/public/javascripts/lib.min.js similarity index 100% rename from tryruby/public/javascripts/lib.min.js rename to public/javascripts/lib.min.js diff --git a/tryruby/public/javascripts/mouseapp_2.js b/public/javascripts/mouseapp_2.js similarity index 100% rename from tryruby/public/javascripts/mouseapp_2.js rename to public/javascripts/mouseapp_2.js diff --git a/tryruby/public/javascripts/mouseirb_2.js b/public/javascripts/mouseirb_2.js similarity index 100% rename from tryruby/public/javascripts/mouseirb_2.js rename to public/javascripts/mouseirb_2.js diff --git a/public/javascripts/prototype.js b/public/javascripts/prototype.js new file mode 100644 index 0000000..06249a6 --- /dev/null +++ b/public/javascripts/prototype.js @@ -0,0 +1,6001 @@ +/* Prototype JavaScript framework, version 1.7_rc2 + * (c) 2005-2010 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * + *--------------------------------------------------------------------------*/ + +var Prototype = { + + Version: '1.7_rc2', + + Browser: (function(){ + var ua = navigator.userAgent; + var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; + return { + IE: !!window.attachEvent && !isOpera, + Opera: isOpera, + WebKit: ua.indexOf('AppleWebKit/') > -1, + Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, + MobileSafari: /Apple.*Mobile/.test(ua) + } + })(), + + BrowserFeatures: { + XPath: !!document.evaluate, + + SelectorsAPI: !!document.querySelector, + + ElementExtensions: (function() { + var constructor = window.Element || window.HTMLElement; + return !!(constructor && constructor.prototype); + })(), + SpecificElementExtensions: (function() { + if (typeof window.HTMLDivElement !== 'undefined') + return true; + + var div = document.createElement('div'), + form = document.createElement('form'), + isSupported = false; + + if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { + isSupported = true; + } + + div = form = null; + + return isSupported; + })() + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; + + +var Abstract = { }; + + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + +/* Based on Alex Arnell's inheritance implementation. */ + +var Class = (function() { + + var IS_DONTENUM_BUGGY = (function(){ + for (var p in { toString: 1 }) { + if (p === 'toString') return false; + } + return true; + })(); + + function subclass() {}; + function create() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0, length = properties.length; i < length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + return klass; + } + + function addMethods(source) { + var ancestor = this.superclass && this.superclass.prototype, + properties = Object.keys(source); + + if (IS_DONTENUM_BUGGY) { + if (source.toString != Object.prototype.toString) + properties.push("toString"); + if (source.valueOf != Object.prototype.valueOf) + properties.push("valueOf"); + } + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames()[0] == "$super") { + var method = value; + value = (function(m) { + return function() { return ancestor[m].apply(this, arguments); }; + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); + } + this.prototype[property] = value; + } + + return this; + } + + return { + create: create, + Methods: { + addMethods: addMethods + } + }; +})(); +(function() { + + var _toString = Object.prototype.toString, + NULL_TYPE = 'Null', + UNDEFINED_TYPE = 'Undefined', + BOOLEAN_TYPE = 'Boolean', + NUMBER_TYPE = 'Number', + STRING_TYPE = 'String', + OBJECT_TYPE = 'Object', + BOOLEAN_CLASS = '[object Boolean]', + NUMBER_CLASS = '[object Number]', + STRING_CLASS = '[object String]', + ARRAY_CLASS = '[object Array]', + NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON && + typeof JSON.stringify === 'function' && + JSON.stringify(0) === '0' && + typeof JSON.stringify(Prototype.K) === 'undefined'; + + function Type(o) { + switch(o) { + case null: return NULL_TYPE; + case (void 0): return UNDEFINED_TYPE; + } + var type = typeof o; + switch(type) { + case 'boolean': return BOOLEAN_TYPE; + case 'number': return NUMBER_TYPE; + case 'string': return STRING_TYPE; + } + return OBJECT_TYPE; + } + + function extend(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; + } + + function inspect(object) { + try { + if (isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : String(object); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + } + + function toJSON(value) { + return Str('', { '': value }, []); + } + + function Str(key, holder, stack) { + var value = holder[key], + type = typeof value; + + if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + + var _class = _toString.call(value); + + switch (_class) { + case NUMBER_CLASS: + case BOOLEAN_CLASS: + case STRING_CLASS: + value = value.valueOf(); + } + + switch (value) { + case null: return 'null'; + case true: return 'true'; + case false: return 'false'; + } + + type = typeof value; + switch (type) { + case 'string': + return value.inspect(true); + case 'number': + return isFinite(value) ? String(value) : 'null'; + case 'object': + + for (var i = 0, length = stack.length; i < length; i++) { + if (stack[i] === value) { throw new TypeError(); } + } + stack.push(value); + + var partial = []; + if (_class === ARRAY_CLASS) { + for (var i = 0, length = value.length; i < length; i++) { + var str = Str(i, value, stack); + partial.push(typeof str === 'undefined' ? 'null' : str); + } + partial = '[' + partial.join(',') + ']'; + } else { + var keys = Object.keys(value); + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i], str = Str(key, value, stack); + if (typeof str !== "undefined") { + partial.push(key.inspect(true)+ ':' + str); + } + } + partial = '{' + partial.join(',') + '}'; + } + stack.pop(); + return partial; + } + } + + function stringify(object) { + return JSON.stringify(object); + } + + function toQueryString(object) { + return $H(object).toQueryString(); + } + + function toHTML(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + } + + function keys(object) { + if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } + var results = []; + for (var property in object) { + if (object.hasOwnProperty(property)) { + results.push(property); + } + } + return results; + } + + function values(object) { + var results = []; + for (var property in object) + results.push(object[property]); + return results; + } + + function clone(object) { + return extend({ }, object); + } + + function isElement(object) { + return !!(object && object.nodeType == 1); + } + + function isArray(object) { + return _toString.call(object) === ARRAY_CLASS; + } + + var hasNativeIsArray = (typeof Array.isArray == 'function') + && Array.isArray([]) && !Array.isArray({}); + + if (hasNativeIsArray) { + isArray = Array.isArray; + } + + function isHash(object) { + return object instanceof Hash; + } + + function isFunction(object) { + return typeof object === "function"; + } + + function isString(object) { + return _toString.call(object) === STRING_CLASS; + } + + function isNumber(object) { + return _toString.call(object) === NUMBER_CLASS; + } + + function isUndefined(object) { + return typeof object === "undefined"; + } + + extend(Object, { + extend: extend, + inspect: inspect, + toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON, + toQueryString: toQueryString, + toHTML: toHTML, + keys: Object.keys || keys, + values: values, + clone: clone, + isElement: isElement, + isArray: isArray, + isHash: isHash, + isFunction: isFunction, + isString: isString, + isNumber: isNumber, + isUndefined: isUndefined + }); +})(); +Object.extend(Function.prototype, (function() { + var slice = Array.prototype.slice; + + function update(array, args) { + var arrayLength = array.length, length = args.length; + while (length--) array[arrayLength + length] = args[length]; + return array; + } + + function merge(array, args) { + array = slice.call(array, 0); + return update(array, args); + } + + function argumentNames() { + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] + .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') + .replace(/\s+/g, '').split(','); + return names.length == 1 && !names[0] ? [] : names; + } + + function bind(context) { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = slice.call(arguments, 1); + return function() { + var a = merge(args, arguments); + return __method.apply(context, a); + } + } + + function bindAsEventListener(context) { + var __method = this, args = slice.call(arguments, 1); + return function(event) { + var a = update([event || window.event], args); + return __method.apply(context, a); + } + } + + function curry() { + if (!arguments.length) return this; + var __method = this, args = slice.call(arguments, 0); + return function() { + var a = merge(args, arguments); + return __method.apply(this, a); + } + } + + function delay(timeout) { + var __method = this, args = slice.call(arguments, 1); + timeout = timeout * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + } + + function defer() { + var args = update([0.01], arguments); + return this.delay.apply(this, args); + } + + function wrap(wrapper) { + var __method = this; + return function() { + var a = update([__method.bind(this)], arguments); + return wrapper.apply(this, a); + } + } + + function methodize() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + var a = update([this], arguments); + return __method.apply(null, a); + }; + } + + return { + argumentNames: argumentNames, + bind: bind, + bindAsEventListener: bindAsEventListener, + curry: curry, + delay: delay, + defer: defer, + wrap: wrap, + methodize: methodize + } +})()); + + + +(function(proto) { + + + function toISOString() { + return this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z'; + } + + + function toJSON() { + return this.toISOString(); + } + + if (!proto.toISOString) proto.toISOString = toISOString; + if (!proto.toJSON) proto.toJSON = toJSON; + +})(Date.prototype); + + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; +var PeriodicalExecuter = Class.create({ + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + this.currentlyExecuting = false; + } catch(e) { + this.currentlyExecuting = false; + throw e; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, (function() { + var NATIVE_JSON_PARSE_SUPPORT = window.JSON && + typeof JSON.parse === 'function' && + JSON.parse('{"test": true}').test; + + function prepareReplacement(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; + } + + function gsub(pattern, replacement) { + var result = '', source = this, match; + replacement = prepareReplacement(replacement); + + if (Object.isString(pattern)) + pattern = RegExp.escape(pattern); + + if (!(pattern.length || pattern.source)) { + replacement = replacement(''); + return replacement + source.split('').join(replacement) + replacement; + } + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + } + + function sub(pattern, replacement, count) { + replacement = prepareReplacement(replacement); + count = Object.isUndefined(count) ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + } + + function scan(pattern, iterator) { + this.gsub(pattern, iterator); + return String(this); + } + + function truncate(length, truncation) { + length = length || 30; + truncation = Object.isUndefined(truncation) ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : String(this); + } + + function strip() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + } + + function stripTags() { + return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); + } + + function stripScripts() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + } + + function extractScripts() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'), + matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + } + + function evalScripts() { + return this.extractScripts().map(function(script) { return eval(script) }); + } + + function escapeHTML() { + return this.replace(/&/g,'&').replace(//g,'>'); + } + + function unescapeHTML() { + return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); + } + + + function toQueryParams(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return { }; + + return match[1].split(separator || '&').inject({ }, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()), + value = pair.length > 1 ? pair.join('=') : pair[0]; + + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + } + + function toArray() { + return this.split(''); + } + + function succ() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + } + + function times(count) { + return count < 1 ? '' : new Array(count + 1).join(this); + } + + function camelize() { + return this.replace(/-+(.)?/g, function(match, chr) { + return chr ? chr.toUpperCase() : ''; + }); + } + + function capitalize() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + } + + function underscore() { + return this.replace(/::/g, '/') + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') + .replace(/([a-z\d])([A-Z])/g, '$1_$2') + .replace(/-/g, '_') + .toLowerCase(); + } + + function dasherize() { + return this.replace(/_/g, '-'); + } + + function inspect(useDoubleQuotes) { + var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { + if (character in String.specialChar) { + return String.specialChar[character]; + } + return '\\u00' + character.charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + } + + function unfilterJSON(filter) { + return this.replace(filter || Prototype.JSONFilter, '$1'); + } + + function isJSON() { + var str = this; + if (str.blank()) return false; + str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'); + str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); + str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + return (/^[\],:{}\s]*$/).test(str); + } + + function evalJSON(sanitize) { + var json = this.unfilterJSON(), + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + if (cx.test(json)) { + json = json.replace(cx, function (a) { + return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + } + + function parseJSON() { + var json = this.unfilterJSON(); + return JSON.parse(json); + } + + function include(pattern) { + return this.indexOf(pattern) > -1; + } + + function startsWith(pattern) { + return this.lastIndexOf(pattern, 0) === 0; + } + + function endsWith(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.indexOf(pattern, d) === d; + } + + function empty() { + return this == ''; + } + + function blank() { + return /^\s*$/.test(this); + } + + function interpolate(object, pattern) { + return new Template(this, pattern).evaluate(object); + } + + return { + gsub: gsub, + sub: sub, + scan: scan, + truncate: truncate, + strip: String.prototype.trim || strip, + stripTags: stripTags, + stripScripts: stripScripts, + extractScripts: extractScripts, + evalScripts: evalScripts, + escapeHTML: escapeHTML, + unescapeHTML: unescapeHTML, + toQueryParams: toQueryParams, + parseQuery: toQueryParams, + toArray: toArray, + succ: succ, + times: times, + camelize: camelize, + capitalize: capitalize, + underscore: underscore, + dasherize: dasherize, + inspect: inspect, + unfilterJSON: unfilterJSON, + isJSON: isJSON, + evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON, + include: include, + startsWith: startsWith, + endsWith: endsWith, + empty: empty, + blank: blank, + interpolate: interpolate + }; +})()); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (object && Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return (match[1] + ''); + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3], + pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = (function() { + function each(iterator, context) { + var index = 0; + try { + this._each(function(value) { + iterator.call(context, value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + } + + function eachSlice(number, iterator, context) { + var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.collect(iterator, context); + } + + function all(iterator, context) { + iterator = iterator || Prototype.K; + var result = true; + this.each(function(value, index) { + result = result && !!iterator.call(context, value, index); + if (!result) throw $break; + }); + return result; + } + + function any(iterator, context) { + iterator = iterator || Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator.call(context, value, index)) + throw $break; + }); + return result; + } + + function collect(iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator.call(context, value, index)); + }); + return results; + } + + function detect(iterator, context) { + var result; + this.each(function(value, index) { + if (iterator.call(context, value, index)) { + result = value; + throw $break; + } + }); + return result; + } + + function findAll(iterator, context) { + var results = []; + this.each(function(value, index) { + if (iterator.call(context, value, index)) + results.push(value); + }); + return results; + } + + function grep(filter, iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(RegExp.escape(filter)); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator.call(context, value, index)); + }); + return results; + } + + function include(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + } + + function inGroupsOf(number, fillWith) { + fillWith = Object.isUndefined(fillWith) ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + } + + function inject(memo, iterator, context) { + this.each(function(value, index) { + memo = iterator.call(context, memo, value, index); + }); + return memo; + } + + function invoke(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + } + + function max(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value >= result) + result = value; + }); + return result; + } + + function min(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value < result) + result = value; + }); + return result; + } + + function partition(iterator, context) { + iterator = iterator || Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator.call(context, value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + } + + function pluck(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + } + + function reject(iterator, context) { + var results = []; + this.each(function(value, index) { + if (!iterator.call(context, value, index)) + results.push(value); + }); + return results; + } + + function sortBy(iterator, context) { + return this.map(function(value, index) { + return { + value: value, + criteria: iterator.call(context, value, index) + }; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + } + + function toArray() { + return this.map(); + } + + function zip() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + } + + function size() { + return this.toArray().length; + } + + function inspect() { + return '#'; + } + + + + + + + + + + return { + each: each, + eachSlice: eachSlice, + all: all, + every: all, + any: any, + some: any, + collect: collect, + map: collect, + detect: detect, + findAll: findAll, + select: findAll, + filter: findAll, + grep: grep, + include: include, + member: include, + inGroupsOf: inGroupsOf, + inject: inject, + invoke: invoke, + max: max, + min: min, + partition: partition, + pluck: pluck, + reject: reject, + sortBy: sortBy, + toArray: toArray, + entries: toArray, + zip: zip, + size: size, + inspect: inspect, + find: detect + }; +})(); + +function $A(iterable) { + if (!iterable) return []; + if ('toArray' in Object(iterable)) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +Array.from = $A; + + +(function() { + var arrayProto = Array.prototype, + slice = arrayProto.slice, + _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available + + function each(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + } + if (!_each) _each = each; + + function clear() { + this.length = 0; + return this; + } + + function first() { + return this[0]; + } + + function last() { + return this[this.length - 1]; + } + + function compact() { + return this.select(function(value) { + return value != null; + }); + } + + function flatten() { + return this.inject([], function(array, value) { + if (Object.isArray(value)) + return array.concat(value.flatten()); + array.push(value); + return array; + }); + } + + function without() { + var values = slice.call(arguments, 0); + return this.select(function(value) { + return !values.include(value); + }); + } + + function reverse(inline) { + return (inline === false ? this.toArray() : this)._reverse(); + } + + function uniq(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + } + + function intersect(array) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + } + + + function clone() { + return slice.call(this, 0); + } + + function size() { + return this.length; + } + + function inspect() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } + + function indexOf(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; + } + + function lastIndexOf(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; + } + + function concat() { + var array = slice.call(this, 0), item; + for (var i = 0, length = arguments.length; i < length; i++) { + item = arguments[i]; + if (Object.isArray(item) && !('callee' in item)) { + for (var j = 0, arrayLength = item.length; j < arrayLength; j++) + array.push(item[j]); + } else { + array.push(item); + } + } + return array; + } + + Object.extend(arrayProto, Enumerable); + + if (!arrayProto._reverse) + arrayProto._reverse = arrayProto.reverse; + + Object.extend(arrayProto, { + _each: _each, + clear: clear, + first: first, + last: last, + compact: compact, + flatten: flatten, + without: without, + reverse: reverse, + uniq: uniq, + intersect: intersect, + clone: clone, + toArray: clone, + size: size, + inspect: inspect + }); + + var CONCAT_ARGUMENTS_BUGGY = (function() { + return [].concat(arguments)[0][0] !== 1; + })(1,2) + + if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; + + if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; + if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; +})(); +function $H(object) { + return new Hash(object); +}; + +var Hash = Class.create(Enumerable, (function() { + function initialize(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + } + + + function _each(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + } + + function set(key, value) { + return this._object[key] = value; + } + + function get(key) { + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; + } + + function unset(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + } + + function toObject() { + return Object.clone(this._object); + } + + + + function keys() { + return this.pluck('key'); + } + + function values() { + return this.pluck('value'); + } + + function index(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + } + + function merge(object) { + return this.clone().update(object); + } + + function update(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + } + + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } + + function toQueryString() { + return this.inject([], function(results, pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return results.concat(values.map(toQueryPair.curry(key))); + } else results.push(toQueryPair(key, values)); + return results; + }).join('&'); + } + + function inspect() { + return '#'; + } + + function clone() { + return new Hash(this); + } + + return { + initialize: initialize, + _each: _each, + set: set, + get: get, + unset: unset, + toObject: toObject, + toTemplateReplacements: toObject, + keys: keys, + values: values, + index: index, + merge: merge, + update: update, + toQueryString: toQueryString, + inspect: inspect, + toJSON: toObject, + clone: clone + }; +})()); + +Hash.from = $H; +Object.extend(Number.prototype, (function() { + function toColorPart() { + return this.toPaddedString(2, 16); + } + + function succ() { + return this + 1; + } + + function times(iterator, context) { + $R(0, this, true).each(iterator, context); + return this; + } + + function toPaddedString(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + } + + function abs() { + return Math.abs(this); + } + + function round() { + return Math.round(this); + } + + function ceil() { + return Math.ceil(this); + } + + function floor() { + return Math.floor(this); + } + + return { + toColorPart: toColorPart, + succ: succ, + times: times, + toPaddedString: toPaddedString, + abs: abs, + round: round, + ceil: ceil, + floor: floor + }; +})()); + +function $R(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var ObjectRange = Class.create(Enumerable, (function() { + function initialize(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + } + + function _each(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + } + + function include(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } + + return { + initialize: initialize, + _each: _each, + include: include + }; +})()); + + + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isString(this.options.parameters)) + this.options.parameters = this.options.parameters.toQueryParams(); + else if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Object.toQueryString(params)) { + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && this.isSameOrigin() && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name) || null; + } catch (e) { return null; } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + + + + + + + + +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if (readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); + + +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(Element.extend(query.snapshotItem(i))); + return results; + }; +} + +/*--------------------------------------------------------------------------*/ + +if (!Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} + + + +(function(global) { + + var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){ + try { + var el = document.createElement(''); + return el.tagName.toLowerCase() === 'input' && el.name === 'x'; + } + catch(err) { + return false; + } + })(); + + var element = global.Element; + + global.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + }; + + Object.extend(global.Element, element || { }); + if (element) global.Element.prototype = element.prototype; + +})(this); + +Element.idCounter = 1; +Element.cache = { }; + +function purgeElement(element) { + var uid = element._prototypeUID; + if (uid) { + Element.stopObserving(element); + element._prototypeUID = void 0; + delete Element.Storage[uid]; + } +} + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + element = $(element); + element.style.display = 'none'; + return element; + }, + + show: function(element) { + element = $(element); + element.style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: (function(){ + + var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ + var el = document.createElement("select"), + isBuggy = true; + el.innerHTML = ""; + if (el.options && el.options[0]) { + isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; + } + el = null; + return isBuggy; + })(); + + var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ + try { + var el = document.createElement("table"); + if (el && el.tBodies) { + el.innerHTML = "test"; + var isBuggy = typeof el.tBodies[0] == "undefined"; + el = null; + return isBuggy; + } + } catch (e) { + return true; + } + })(); + + var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { + var s = document.createElement("script"), + isBuggy = false; + try { + s.appendChild(document.createTextNode("")); + isBuggy = !s.firstChild || + s.firstChild && s.firstChild.nodeType !== 3; + } catch (e) { + isBuggy = true; + } + s = null; + return isBuggy; + })(); + + function update(element, content) { + element = $(element); + + var descendants = element.getElementsByTagName('*'), + i = descendants.length; + while (i--) purgeElement(descendants[i]); + + if (content && content.toElement) + content = content.toElement(); + + if (Object.isElement(content)) + return element.update().insert(content); + + content = Object.toHTML(content); + + var tagName = element.tagName.toUpperCase(); + + if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { + element.text = content; + return element; + } + + if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) { + if (tagName in Element._insertionTranslations.tags) { + while (element.firstChild) { + element.removeChild(element.firstChild); + } + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { + element.appendChild(node) + }); + } + else { + element.innerHTML = content.stripScripts(); + } + } + else { + element.innerHTML = content.stripScripts(); + } + + content.evalScripts.bind(content).defer(); + return element; + } + + return update; + })(), + + replace: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = {bottom:insertions}; + + var content, insert, tagName, childNodes; + + for (var position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + insert = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + insert(element, content); + continue; + } + + content = Object.toHTML(content); + + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), + attribute = pair.last(), + value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property, maximumLength) { + element = $(element); + maximumLength = maximumLength || -1; + var elements = []; + + while (element = element[property]) { + if (element.nodeType == 1) + elements.push(Element.extend(element)); + if (elements.length == maximumLength) + break; + } + + return elements; + }, + + ancestors: function(element) { + return Element.recursivelyCollect(element, 'parentNode'); + }, + + descendants: function(element) { + return Element.select(element, "*"); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + var results = [], child = $(element).firstChild; + while (child) { + if (child.nodeType === 1) { + results.push(Element.extend(child)); + } + child = child.nextSibling; + } + return results; + }, + + previousSiblings: function(element, maximumLength) { + return Element.recursivelyCollect(element, 'previousSibling'); + }, + + nextSiblings: function(element) { + return Element.recursivelyCollect(element, 'nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return Element.previousSiblings(element).reverse() + .concat(Element.nextSiblings(element)); + }, + + match: function(element, selector) { + element = $(element); + if (Object.isString(selector)) + return Prototype.Selector.match(element, selector); + return selector.match(element); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = Element.ancestors(element); + return Object.isNumber(expression) ? ancestors[expression] : + Prototype.Selector.find(ancestors, expression, index); + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return Element.firstDescendant(element); + return Object.isNumber(expression) ? Element.descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (Object.isNumber(expression)) index = expression, expression = false; + if (!Object.isNumber(index)) index = 0; + + if (expression) { + return Prototype.Selector.find(element.previousSiblings(), expression, index); + } else { + return element.recursivelyCollect("previousSibling", index + 1)[index]; + } + }, + + next: function(element, expression, index) { + element = $(element); + if (Object.isNumber(expression)) index = expression, expression = false; + if (!Object.isNumber(index)) index = 0; + + if (expression) { + return Prototype.Selector.find(element.nextSiblings(), expression, index); + } else { + var maximumLength = Object.isNumber(index) ? index + 1 : 1; + return element.recursivelyCollect("nextSibling", index + 1)[index]; + } + }, + + + select: function(element) { + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element); + }, + + adjacent: function(element) { + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element.parentNode).without(element); + }, + + identify: function(element) { + element = $(element); + var id = Element.readAttribute(element, 'id'); + if (id) return id; + do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); + Element.writeAttribute(element, 'id', id); + return id; + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return Element.getDimensions(element).height; + }, + + getWidth: function(element) { + return Element.getDimensions(element).width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + if (!Element.hasClassName(element, className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + return Element[Element.hasClassName(element, className) ? + 'removeClassName' : 'addClassName'](element, className); + }, + + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + + if (element.compareDocumentPosition) + return (element.compareDocumentPosition(ancestor) & 8) === 8; + + if (ancestor.contains) + return ancestor.contains(element) && ancestor !== element; + + while (element = element.parentNode) + if (element == ancestor) return true; + + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Element.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value || value == 'auto') { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles) { + element = $(element); + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]); + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + if (Prototype.Browser.Opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + if (element.parentNode) { + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + } + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName.toUpperCase() == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (Element.getStyle(element, 'position') == 'absolute') return element; + + var offsets = Element.positionedOffset(element), + top = offsets[1], + left = offsets[0], + width = element.clientWidth, + height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (Element.getStyle(element, 'position') == 'relative') return element; + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0), + left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element == document.body) return $(element); + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return $(element); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, + valueL = 0, + element = forElement; + + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + source = $(source); + var p = Element.viewportOffset(source), delta = [0, 0], parent = null; + + element = $(element); + + if (Element.getStyle(element, 'position') == 'absolute') { + parent = Element.getOffsetParent(element); + delta = Element.viewportOffset(parent); + } + + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Object.extend(Element.Methods, { + getElementsBySelector: Element.Methods.select, + + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + +if (Prototype.Browser.Opera) { + Element.Methods.getStyle = Element.Methods.getStyle.wrap( + function(proceed, element, style) { + switch (style) { + case 'left': case 'top': case 'right': case 'bottom': + if (proceed(element, 'position') === 'static') return null; + case 'height': case 'width': + if (!Element.visible(element)) return null; + + var dim = parseInt(proceed(element, style), 10); + + if (dim !== element['offset' + style.capitalize()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } + } + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); +} + +else if (Prototype.Browser.IE) { + Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + if (!element.parentNode) return $(document.body); + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + $w('positionedOffset viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + if (!element.parentNode) return Element._returnOffset(0, 0); + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + offsetParent.setStyle({ zoom: 1 }); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + }); + + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + Element._attributeTranslations = (function(){ + + var classProp = 'className', + forProp = 'for', + el = document.createElement('div'); + + el.setAttribute(classProp, 'x'); + + if (el.className !== 'x') { + el.setAttribute('class', 'x'); + if (el.className === 'x') { + classProp = 'class'; + } + } + el = null; + + el = document.createElement('label'); + el.setAttribute(forProp, 'x'); + if (el.htmlFor !== 'x') { + el.setAttribute('htmlFor', 'x'); + if (el.htmlFor === 'x') { + forProp = 'htmlFor'; + } + } + el = null; + + return { + read: { + names: { + 'class': classProp, + 'className': classProp, + 'for': forProp, + 'htmlFor': forProp + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute); + }, + _getAttr2: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: (function(){ + + var el = document.createElement('div'), f; + el.onclick = Prototype.emptyFunction; + var value = el.getAttribute('onclick'); + + if (String(value).indexOf('{') > -1) { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + attribute = attribute.toString(); + attribute = attribute.split('{')[1]; + attribute = attribute.split('}')[0]; + return attribute.strip(); + }; + } + else if (value === '') { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + return attribute.strip(); + }; + } + el = null; + return f; + })(), + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } + } + } + } + })(); + + Element._attributeTranslations.write = { + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; + Element._attributeTranslations.has[attr.toLowerCase()] = attr; + }); + + (function(v) { + Object.extend(v, { + href: v._getAttr2, + src: v._getAttr2, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); + + if (Prototype.BrowserFeatures.ElementExtensions) { + (function() { + function _descendants(element) { + var nodes = element.getElementsByTagName('*'), results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName !== "!") // Filter out comment nodes. + results.push(node); + return results; + } + + Element.Methods.down = function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + return Object.isNumber(expression) ? _descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + } + })(); + } + +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if (element.tagName.toUpperCase() == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + return element; + }; + + Element.Methods.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +if ('outerHTML' in document.documentElement) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(), + fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html) { + var div = new Element('div'), + t = Element._insertionTranslations.tags[tagName]; + if (t) { + div.innerHTML = t[0] + html + t[1]; + for (var i = t[2]; i--; ) { + div = div.firstChild; + } + } + else { + div.innerHTML = html; + } + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + top: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + bottom: function(element, node) { + element.appendChild(node); + }, + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + tags: { + TABLE: ['', '
    ', 1], + TBODY: ['', '
    ', 2], + TR: ['', '
    ', 3], + TD: ['
    ', '
    ', 4], + SELECT: ['', 1] + } +}; + +(function() { + var tags = Element._insertionTranslations.tags; + Object.extend(tags, { + THEAD: tags.TBODY, + TFOOT: tags.TBODY, + TH: tags.TD + }); +})(); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + attribute = Element._attributeTranslations.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); + return !!(node && node.specified); + } +}; + +Element.Methods.ByTag = { }; + +Object.extend(Element, Element.Methods); + +(function(div) { + + if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) { + window.HTMLElement = { }; + window.HTMLElement.prototype = div['__proto__']; + Prototype.BrowserFeatures.ElementExtensions = true; + } + + div = null; + +})(document.createElement('div')); + +Element.extend = (function() { + + function checkDeficiency(tagName) { + if (typeof window.Element != 'undefined') { + var proto = window.Element.prototype; + if (proto) { + var id = '_' + (Math.random()+'').slice(2), + el = document.createElement(tagName); + proto[id] = 'x'; + var isBuggy = (el[id] !== 'x'); + delete proto[id]; + el = null; + return isBuggy; + } + } + return false; + } + + function extendElementWith(element, methods) { + for (var property in methods) { + var value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + } + + var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); + + if (Prototype.BrowserFeatures.SpecificElementExtensions) { + if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { + return function(element) { + if (element && typeof element._extendedByPrototype == 'undefined') { + var t = element.tagName; + if (t && (/^(?:object|applet|embed)$/i.test(t))) { + extendElementWith(element, Element.Methods); + extendElementWith(element, Element.Methods.Simulated); + extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); + } + } + return element; + } + } + return Prototype.K; + } + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || typeof element._extendedByPrototype != 'undefined' || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName.toUpperCase(); + + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + extendElementWith(element, methods); + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + +if (document.documentElement.hasAttribute) { + Element.hasAttribute = function(element, attribute) { + return element.hasAttribute(attribute); + }; +} +else { + Element.hasAttribute = Element.Methods.Simulated.hasAttribute; +} + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || { }); + else { + if (Object.isArray(tagName)) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = { }; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + var element = document.createElement(tagName), + proto = element['__proto__'] || element.constructor.prototype; + + element = null; + return proto; + } + + var elementPrototype = window.HTMLElement ? HTMLElement.prototype : + Element.prototype; + + if (F.ElementExtensions) { + copy(Element.Methods, elementPrototype); + copy(Element.Methods.Simulated, elementPrototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; + + +document.viewport = { + + getDimensions: function() { + return { width: this.getWidth(), height: this.getHeight() }; + }, + + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + } +}; + +(function(viewport) { + var B = Prototype.Browser, doc = document, element, property = {}; + + function getRootElement() { + if (B.WebKit && !doc.evaluate) + return document; + + if (B.Opera && window.parseFloat(window.opera.version()) < 9.5) + return document.body; + + return document.documentElement; + } + + function define(D) { + if (!element) element = getRootElement(); + + property[D] = 'client' + D; + + viewport['get' + D] = function() { return element[property[D]] }; + return viewport['get' + D](); + } + + viewport.getWidth = define.curry('Width'); + + viewport.getHeight = define.curry('Height'); +})(document.viewport); + + +Element.Storage = { + UID: 1 +}; + +Element.addMethods({ + getStorage: function(element) { + if (!(element = $(element))) return; + + var uid; + if (element === window) { + uid = 0; + } else { + if (typeof element._prototypeUID === "undefined") + element._prototypeUID = Element.Storage.UID++; + uid = element._prototypeUID; + } + + if (!Element.Storage[uid]) + Element.Storage[uid] = $H(); + + return Element.Storage[uid]; + }, + + store: function(element, key, value) { + if (!(element = $(element))) return; + + if (arguments.length === 2) { + Element.getStorage(element).update(key); + } else { + Element.getStorage(element).set(key, value); + } + + return element; + }, + + retrieve: function(element, key, defaultValue) { + if (!(element = $(element))) return; + var hash = Element.getStorage(element), value = hash.get(key); + + if (Object.isUndefined(value)) { + hash.set(key, defaultValue); + value = defaultValue; + } + + return value; + }, + + clone: function(element, deep) { + if (!(element = $(element))) return; + var clone = element.cloneNode(deep); + clone._prototypeUID = void 0; + if (deep) { + var descendants = Element.select(clone, '*'), + i = descendants.length; + while (i--) { + descendants[i]._prototypeUID = void 0; + } + } + return Element.extend(clone); + }, + + purge: function(element) { + if (!(element = $(element))) return; + purgeElement(element); + + var descendants = element.getElementsByTagName('*'), + i = descendants.length; + + while (i--) purgeElement(descendants[i]); + + return null; + } +}); + +(function() { + + function toDecimal(pctString) { + var match = pctString.match(/^(\d+)%?$/i); + if (!match) return null; + return (Number(match[1]) / 100); + } + + function getPixelValue(value, property) { + if (Object.isElement(value)) { + element = value; + value = element.getStyle(property); + } + if (value === null) { + return null; + } + + if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) { + return window.parseFloat(value); + } + + if (/\d/.test(value) && element.runtimeStyle) { + var style = element.style.left, rStyle = element.runtimeStyle.left; + element.runtimeStyle.left = element.currentStyle.left; + element.style.left = value || 0; + value = element.style.pixelLeft; + element.style.left = style; + element.runtimeStyle.left = rStyle; + + return value; + } + + if (value.include('%')) { + var decimal = toDecimal(value); + var whole; + if (property.include('left') || property.include('right') || + property.include('width')) { + whole = $(element.parentNode).measure('width'); + } else if (property.include('top') || property.include('bottom') || + property.include('height')) { + whole = $(element.parentNode).measure('height'); + } + + return whole * decimal; + } + + return 0; + } + + function toCSSPixels(number) { + if (Object.isString(number) && number.endsWith('px')) { + return number; + } + return number + 'px'; + } + + function isDisplayed(element) { + var originalElement = element; + while (element && element.parentNode) { + var display = element.getStyle('display'); + if (display === 'none') { + return false; + } + element = $(element.parentNode); + } + return true; + } + + var hasLayout = Prototype.K; + if ('currentStyle' in document.documentElement) { + hasLayout = function(element) { + if (!element.currentStyle.hasLayout) { + element.style.zoom = 1; + } + return element; + }; + } + + function cssNameFor(key) { + if (key.include('border')) key = key + '-width'; + return key.camelize(); + } + + Element.Layout = Class.create(Hash, { + initialize: function($super, element, preCompute) { + $super(); + this.element = $(element); + + Element.Layout.PROPERTIES.each( function(property) { + this._set(property, null); + }, this); + + if (preCompute) { + this._preComputing = true; + this._begin(); + Element.Layout.PROPERTIES.each( this._compute, this ); + this._end(); + this._preComputing = false; + } + }, + + _set: function(property, value) { + return Hash.prototype.set.call(this, property, value); + }, + + set: function(property, value) { + throw "Properties of Element.Layout are read-only."; + }, + + get: function($super, property) { + var value = $super(property); + return value === null ? this._compute(property) : value; + }, + + _begin: function() { + if (this._prepared) return; + + var element = this.element; + if (isDisplayed(element)) { + this._prepared = true; + return; + } + + var originalStyles = { + position: element.style.position || '', + width: element.style.width || '', + visibility: element.style.visibility || '', + display: element.style.display || '' + }; + + element.store('prototype_original_styles', originalStyles); + + var position = element.getStyle('position'), + width = element.getStyle('width'); + + element.setStyle({ + position: 'absolute', + visibility: 'hidden', + display: 'block' + }); + + var positionedWidth = element.getStyle('width'); + + var newWidth; + if (width && (positionedWidth === width)) { + newWidth = getPixelValue(width); + } else if (width && (position === 'absolute' || position === 'fixed')) { + newWidth = getPixelValue(width); + } else { + var parent = element.parentNode, pLayout = $(parent).getLayout(); + + newWidth = pLayout.get('width') - + this.get('margin-left') - + this.get('border-left') - + this.get('padding-left') - + this.get('padding-right') - + this.get('border-right') - + this.get('margin-right'); + } + + element.setStyle({ width: newWidth + 'px' }); + + this._prepared = true; + }, + + _end: function() { + var element = this.element; + var originalStyles = element.retrieve('prototype_original_styles'); + element.store('prototype_original_styles', null); + element.setStyle(originalStyles); + this._prepared = false; + }, + + _compute: function(property) { + var COMPUTATIONS = Element.Layout.COMPUTATIONS; + if (!(property in COMPUTATIONS)) { + throw "Property not found."; + } + return this._set(property, COMPUTATIONS[property].call(this, this.element)); + }, + + toObject: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var obj = {}; + keys.each( function(key) { + if (!Element.Layout.PROPERTIES.include(key)) return; + var value = this.get(key); + if (value != null) obj[key] = value; + }, this); + return obj; + }, + + toHash: function() { + var obj = this.toObject.apply(this, arguments); + return new Hash(obj); + }, + + toCSS: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var css = {}; + + keys.each( function(key) { + if (!Element.Layout.PROPERTIES.include(key)) return; + if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return; + + var value = this.get(key); + if (value != null) css[cssNameFor(key)] = value + 'px'; + }, this); + return css; + }, + + inspect: function() { + return "#"; + } + }); + + Object.extend(Element.Layout, { + PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'), + + COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'), + + COMPUTATIONS: { + 'height': function(element) { + if (!this._preComputing) this._begin(); + + var bHeight = this.get('border-box-height'); + if (bHeight <= 0) return 0; + + var bTop = this.get('border-top'), + bBottom = this.get('border-bottom'); + + var pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + if (!this._preComputing) this._end(); + + return bHeight - bTop - bBottom - pTop - pBottom; + }, + + 'width': function(element) { + if (!this._preComputing) this._begin(); + + var bWidth = this.get('border-box-width'); + if (bWidth <= 0) return 0; + + var bLeft = this.get('border-left'), + bRight = this.get('border-right'); + + var pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + if (!this._preComputing) this._end(); + + return bWidth - bLeft - bRight - pLeft - pRight; + }, + + 'padding-box-height': function(element) { + var height = this.get('height'), + pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + return height + pTop + pBottom; + }, + + 'padding-box-width': function(element) { + var width = this.get('width'), + pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + return width + pLeft + pRight; + }, + + 'border-box-height': function(element) { + return element.offsetHeight; + }, + + 'border-box-width': function(element) { + return element.offsetWidth; + }, + + 'margin-box-height': function(element) { + var bHeight = this.get('border-box-height'), + mTop = this.get('margin-top'), + mBottom = this.get('margin-bottom'); + + if (bHeight <= 0) return 0; + + return bHeight + mTop + mBottom; + }, + + 'margin-box-width': function(element) { + var bWidth = this.get('border-box-width'), + mLeft = this.get('margin-left'), + mRight = this.get('margin-right'); + + if (bWidth <= 0) return 0; + + return bWidth + mLeft + mRight; + }, + + 'top': function(element) { + var offset = element.positionedOffset(); + return offset.top; + }, + + 'bottom': function(element) { + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pHeight = parent.measure('height'); + + var mHeight = this.get('border-box-height'); + + return pHeight - mHeight - offset.top; + }, + + 'left': function(element) { + var offset = element.positionedOffset(); + return offset.left; + }, + + 'right': function(element) { + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pWidth = parent.measure('width'); + + var mWidth = this.get('border-box-width'); + + return pWidth - mWidth - offset.left; + }, + + 'padding-top': function(element) { + return getPixelValue(element, 'paddingTop'); + }, + + 'padding-bottom': function(element) { + return getPixelValue(element, 'paddingBottom'); + }, + + 'padding-left': function(element) { + return getPixelValue(element, 'paddingLeft'); + }, + + 'padding-right': function(element) { + return getPixelValue(element, 'paddingRight'); + }, + + 'border-top': function(element) { + return Object.isNumber(element.clientTop) ? element.clientTop : + getPixelValue(element, 'borderTopWidth'); + }, + + 'border-bottom': function(element) { + return Object.isNumber(element.clientBottom) ? element.clientBottom : + getPixelValue(element, 'borderBottomWidth'); + }, + + 'border-left': function(element) { + return Object.isNumber(element.clientLeft) ? element.clientLeft : + getPixelValue(element, 'borderLeftWidth'); + }, + + 'border-right': function(element) { + return Object.isNumber(element.clientRight) ? element.clientRight : + getPixelValue(element, 'borderRightWidth'); + }, + + 'margin-top': function(element) { + return getPixelValue(element, 'marginTop'); + }, + + 'margin-bottom': function(element) { + return getPixelValue(element, 'marginBottom'); + }, + + 'margin-left': function(element) { + return getPixelValue(element, 'marginLeft'); + }, + + 'margin-right': function(element) { + return getPixelValue(element, 'marginRight'); + } + } + }); + + if ('getBoundingClientRect' in document.documentElement) { + Object.extend(Element.Layout.COMPUTATIONS, { + 'right': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.right - rect.right).round(); + }, + + 'bottom': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.bottom - rect.bottom).round(); + } + }); + } + + Element.Offset = Class.create({ + initialize: function(left, top) { + this.left = left.round(); + this.top = top.round(); + + this[0] = this.left; + this[1] = this.top; + }, + + relativeTo: function(offset) { + return new Element.Offset( + this.left - offset.left, + this.top - offset.top + ); + }, + + inspect: function() { + return "#".interpolate(this); + }, + + toString: function() { + return "[#{left}, #{top}]".interpolate(this); + }, + + toArray: function() { + return [this.left, this.top]; + } + }); + + function getLayout(element, preCompute) { + return new Element.Layout(element, preCompute); + } + + function measure(element, property) { + return $(element).getLayout().get(property); + } + + function getDimensions(element) { + var layout = $(element).getLayout(); + return { + width: layout.get('width'), + height: layout.get('height') + }; + } + + function getOffsetParent(element) { + if (isDetached(element)) return $(document.body); + + var isInline = (Element.getStyle(element, 'display') === 'inline'); + if (!isInline && element.offsetParent) return $(element.offsetParent); + if (element === document.body) return $(element); + + while ((element = element.parentNode) && element !== document.body) { + if (Element.getStyle(element, 'position') !== 'static') { + return (element.nodeName === 'HTML') ? $(document.body) : $(element); + } + } + + return $(document.body); + } + + + function cumulativeOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return new Element.Offset(valueL, valueT); + } + + function positionedOffset(element) { + var layout = element.getLayout(); + + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (isBody(element)) break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + + valueL -= layout.get('margin-top'); + valueT -= layout.get('margin-left'); + + return new Element.Offset(valueL, valueT); + } + + function cumulativeScrollOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return new Element.Offset(valueL, valueT); + } + + function viewportOffset(forElement) { + var valueT = 0, valueL = 0, docBody = document.body; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == docBody && + Element.getStyle(element, 'position') == 'absolute') break; + } while (element = element.offsetParent); + + element = forElement; + do { + if (element != docBody) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + return new Element.Offset(valueL, valueT); + } + + function absolutize(element) { + element = $(element); + + if (Element.getStyle(element, 'position') === 'absolute') { + return element; + } + + var offsetParent = getOffsetParent(element); + var eOffset = element.viewportOffset(), + pOffset = offsetParent.viewportOffset(); + + var offset = eOffset.relativeTo(pOffset); + var layout = element.getLayout(); + + element.store('prototype_absolutize_original_styles', { + left: element.getStyle('left'), + top: element.getStyle('top'), + width: element.getStyle('width'), + height: element.getStyle('height') + }); + + element.setStyle({ + position: 'absolute', + top: offset.top + 'px', + left: offset.left + 'px', + width: layout.get('width') + 'px', + height: layout.get('height') + 'px' + }); + + return element; + } + + function relativize(element) { + element = $(element); + if (Element.getStyle(element, 'position') === 'relative') { + return element; + } + + var originalStyles = + element.retrieve('prototype_absolutize_original_styles'); + + if (originalStyles) element.setStyle(originalStyles); + return element; + } + + Element.addMethods({ + getLayout: getLayout, + measure: measure, + getDimensions: getDimensions, + getOffsetParent: getOffsetParent, + cumulativeOffset: cumulativeOffset, + positionedOffset: positionedOffset, + cumulativeScrollOffset: cumulativeScrollOffset, + viewportOffset: viewportOffset, + absolutize: absolutize, + relativize: relativize + }); + + function isBody(element) { + return element.nodeName.toUpperCase() === 'BODY'; + } + + function isDetached(element) { + return element !== document.body && + !Element.descendantOf(element, document.body); + } + + if ('getBoundingClientRect' in document.documentElement) { + Element.addMethods({ + viewportOffset: function(element) { + element = $(element); + if (isDetached(element)) return new Element.Offset(0, 0); + + var rect = element.getBoundingClientRect(), + docEl = document.documentElement; + return new Element.Offset(rect.left - docEl.clientLeft, + rect.top - docEl.clientTop); + }, + + positionedOffset: function(element) { + element = $(element); + var parent = element.getOffsetParent(); + if (isDetached(element)) return new Element.Offset(0, 0); + + if (element.offsetParent && + element.offsetParent.nodeName.toUpperCase() === 'HTML') { + return positionedOffset(element); + } + + var eOffset = element.viewportOffset(), + pOffset = isBody(parent) ? viewportOffset(parent) : + parent.viewportOffset(); + var retOffset = eOffset.relativeTo(pOffset); + + var layout = element.getLayout(); + var top = retOffset.top - layout.get('margin-top'); + var left = retOffset.left - layout.get('margin-left'); + + return new Element.Offset(left, top); + } + }); + } +})(); +window.$$ = function() { + var expression = $A(arguments).join(', '); + return Prototype.Selector.select(expression, document); +}; + +Prototype.Selector = (function() { + + function select() { + throw new Error('Method "Prototype.Selector.select" must be defined.'); + } + + function match() { + throw new Error('Method "Prototype.Selector.match" must be defined.'); + } + + function find(elements, expression, index) { + index = index || 0; + var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i; + + for (i = 0; i < length; i++) { + if (match(elements[i], expression) && index == matchIndex++) { + return Element.extend(elements[i]); + } + } + } + + function extendElements(elements) { + for (var i = 0, length = elements.length; i < length; i++) { + Element.extend(elements[i]); + } + return elements; + } + + + var K = Prototype.K; + + return { + select: select, + match: match, + find: find, + extendElements: (Element.extend === K) ? K : extendElements, + extendElement: Element.extend + }; +})(); +Prototype._original_property = window.Sizzle; +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) + selector += parts.shift(); + + set = posProcess( selector, set ); + } + } + } else { + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + throw "Syntax error, unrecognized expression: " + (cur || selector); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.match[ type ].exec( expr )) != null ) { + var filter = Expr.filter[ type ], found, item; + anyFound = false; + + if ( curLoop == result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + if ( expr == old ) { + if ( anyFound == null ) { + throw "Syntax error, unrecognized expression: " + expr; + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag && !isXML ) { + part = part.toUpperCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = isXML ? part : part.toUpperCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context, isXML){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { + if ( !inplace ) + result.push( elem ); + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + for ( var i = 0; curLoop[i] === false; i++ ){} + return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + }, + CHILD: function(match){ + if ( match[1] == "nth" ) { + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 == i; + }, + eq: function(elem, i, match){ + return match[3] - 0 == i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) return false; + } + if ( type == 'first') return true; + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) return false; + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first == 1 && last == 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first == 0 ) { + return diff == 0; + } else { + return ( diff % first == 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value != check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source ); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 ); + +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +(function(){ + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + if ( !!document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

    "; + + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE +})(); + +if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ + var div = document.createElement("div"); + div.innerHTML = "
    "; + + if ( div.getElementsByClassName("e").length === 0 ) + return; + + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) + return; + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ){ + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ) { + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return a.compareDocumentPosition(b) & 16; +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + + +window.Sizzle = Sizzle; + +})(); + +;(function(engine) { + var extendElements = Prototype.Selector.extendElements; + + function select(selector, scope) { + return extendElements(engine(selector, scope || document)); + } + + function match(element, selector) { + return engine.matches(selector, [element]).length == 1; + } + + Prototype.Selector.engine = engine; + Prototype.Selector.select = select; + Prototype.Selector.match = match; +})(Sizzle); + +window.Sizzle = Prototype._original_property; +delete Prototype._original_property; + +var Form = { + reset: function(form) { + form = $(form); + form.reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + var data = elements.inject({ }, function(result, element) { + if (!element.disabled && element.name) { + key = element.name; value = $(element).getValue(); + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + if (key in result) { + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return options.hash ? data : Object.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + getElements: function(form) { + var elements = $(form).getElementsByTagName('*'), + element, + arr = [ ], + serializers = Form.Element.Serializers; + for (var i = 0; element = elements[i]; i++) { + arr.push(element); + } + return arr.inject([], function(elements, child) { + if (serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + }) + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return /^(?:input|select|textarea)$/i.test(element.tagName); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !(/^(?:button|reset|submit)$/i.test(element.type)))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; + +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element, value); + default: + return Form.Element.Serializers.textarea(element, value); + } + }, + + inputSelector: function(element, value) { + if (Object.isUndefined(value)) return element.checked ? element.value : null; + else element.checked = !!value; + }, + + textarea: function(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + }, + + select: function(element, value) { + if (Object.isUndefined(value)) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, currentValue, single = !Object.isArray(value); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + currentValue = this.optionValue(opt); + if (single) { + if (currentValue == value) { + opt.selected = true; + return; + } + } + else opt.selected = value.include(currentValue); + } + } + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +}; + +/*--------------------------------------------------------------------------*/ + + +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); +(function() { + + var Event = { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + cache: {} + }; + + var docEl = document.documentElement; + var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl + && 'onmouseleave' in docEl; + + var _isButton; + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + _isButton = function(event, code) { + return event.button === buttonMap[code]; + }; + } else if (Prototype.Browser.WebKit) { + _isButton = function(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 1 && event.metaKey; + default: return false; + } + }; + } else { + _isButton = function(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + }; + } + + function isLeftClick(event) { return _isButton(event, 0) } + + function isMiddleClick(event) { return _isButton(event, 1) } + + function isRightClick(event) { return _isButton(event, 2) } + + function element(event) { + event = Event.extend(event); + + var node = event.target, type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + + if (node.nodeType == Node.TEXT_NODE) + node = node.parentNode; + + return Element.extend(node); + } + + function findElement(event, expression) { + var element = Event.element(event); + if (!expression) return element; + while (element) { + if (Object.isElement(element) && Prototype.Selector.match(element, expression)) { + return Element.extend(element); + } + element = element.parentNode; + } + } + + function pointer(event) { + return { x: pointerX(event), y: pointerY(event) }; + } + + function pointerX(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0 }; + + return event.pageX || (event.clientX + + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)); + } + + function pointerY(event) { + var docElement = document.documentElement, + body = document.body || { scrollTop: 0 }; + + return event.pageY || (event.clientY + + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)); + } + + + function stop(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + + event.stopped = true; + } + + Event.Methods = { + isLeftClick: isLeftClick, + isMiddleClick: isMiddleClick, + isRightClick: isRightClick, + + element: element, + findElement: findElement, + + pointer: pointer, + pointerX: pointerX, + pointerY: pointerY, + + stop: stop + }; + + + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (Prototype.Browser.IE) { + function _relatedTarget(event) { + var element; + switch (event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; + } + return Element.extend(element); + } + + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return '[object Event]' } + }); + + Event.extend = function(event, element) { + if (!event) return false; + if (event._extendedByPrototype) return event; + + event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); + + Object.extend(event, { + target: event.srcElement || element, + relatedTarget: _relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + + return Object.extend(event, methods); + }; + } else { + Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; + Object.extend(Event.prototype, methods); + Event.extend = Prototype.K; + } + + function _createResponder(element, eventName, handler) { + var registry = Element.retrieve(element, 'prototype_event_registry'); + + if (Object.isUndefined(registry)) { + CACHE.push(element); + registry = Element.retrieve(element, 'prototype_event_registry', $H()); + } + + var respondersForEvent = registry.get(eventName); + if (Object.isUndefined(respondersForEvent)) { + respondersForEvent = []; + registry.set(eventName, respondersForEvent); + } + + if (respondersForEvent.pluck('handler').include(handler)) return false; + + var responder; + if (eventName.include(":")) { + responder = function(event) { + if (Object.isUndefined(event.eventName)) + return false; + + if (event.eventName !== eventName) + return false; + + Event.extend(event, element); + handler.call(element, event); + }; + } else { + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && + (eventName === "mouseenter" || eventName === "mouseleave")) { + if (eventName === "mouseenter" || eventName === "mouseleave") { + responder = function(event) { + Event.extend(event, element); + + var parent = event.relatedTarget; + while (parent && parent !== element) { + try { parent = parent.parentNode; } + catch(e) { parent = element; } + } + + if (parent === element) return; + + handler.call(element, event); + }; + } + } else { + responder = function(event) { + Event.extend(event, element); + handler.call(element, event); + }; + } + } + + responder.handler = handler; + respondersForEvent.push(responder); + return responder; + } + + function _destroyCache() { + for (var i = 0, length = CACHE.length; i < length; i++) { + Event.stopObserving(CACHE[i]); + CACHE[i] = null; + } + } + + var CACHE = []; + + if (Prototype.Browser.IE) + window.attachEvent('onunload', _destroyCache); + + if (Prototype.Browser.WebKit) + window.addEventListener('unload', Prototype.emptyFunction, false); + + + var _getDOMEventName = Prototype.K, + translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; + + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { + _getDOMEventName = function(eventName) { + return (translations[eventName] || eventName); + }; + } + + function observe(element, eventName, handler) { + element = $(element); + + var responder = _createResponder(element, eventName, handler); + + if (!responder) return element; + + if (eventName.include(':')) { + if (element.addEventListener) + element.addEventListener("dataavailable", responder, false); + else { + element.attachEvent("ondataavailable", responder); + element.attachEvent("onfilterchange", responder); + } + } else { + var actualEventName = _getDOMEventName(eventName); + + if (element.addEventListener) + element.addEventListener(actualEventName, responder, false); + else + element.attachEvent("on" + actualEventName, responder); + } + + return element; + } + + function stopObserving(element, eventName, handler) { + element = $(element); + + var registry = Element.retrieve(element, 'prototype_event_registry'); + if (!registry) return element; + + if (!eventName) { + registry.each( function(pair) { + var eventName = pair.key; + stopObserving(element, eventName); + }); + return element; + } + + var responders = registry.get(eventName); + if (!responders) return element; + + if (!handler) { + responders.each(function(r) { + stopObserving(element, eventName, r.handler); + }); + return element; + } + + var responder = responders.find( function(r) { return r.handler === handler; }); + if (!responder) return element; + + if (eventName.include(':')) { + if (element.removeEventListener) + element.removeEventListener("dataavailable", responder, false); + else { + element.detachEvent("ondataavailable", responder); + element.detachEvent("onfilterchange", responder); + } + } else { + var actualEventName = _getDOMEventName(eventName); + if (element.removeEventListener) + element.removeEventListener(actualEventName, responder, false); + else + element.detachEvent('on' + actualEventName, responder); + } + + registry.set(eventName, responders.without(responder)); + + return element; + } + + function fire(element, eventName, memo, bubble) { + element = $(element); + + if (Object.isUndefined(bubble)) + bubble = true; + + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + var event; + if (document.createEvent) { + event = document.createEvent('HTMLEvents'); + event.initEvent('dataavailable', true, true); + } else { + event = document.createEventObject(); + event.eventType = bubble ? 'ondataavailable' : 'onfilterchange'; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) + element.dispatchEvent(event); + else + element.fireEvent(event.eventType, event); + + return Event.extend(event); + } + + Event.Handler = Class.create({ + initialize: function(element, eventName, selector, callback) { + this.element = $(element); + this.eventName = eventName; + this.selector = selector; + this.callback = callback; + this.handler = this.handleEvent.bind(this); + }, + + start: function() { + Event.observe(this.element, this.eventName, this.handler); + return this; + }, + + stop: function() { + Event.stopObserving(this.element, this.eventName, this.handler); + return this; + }, + + handleEvent: function(event) { + var element = event.findElement(this.selector); + if (element) this.callback.call(this.element, event, element); + } + }); + + function on(element, eventName, selector, callback) { + element = $(element); + if (Object.isFunction(selector) && Object.isUndefined(callback)) { + callback = selector, selector = null; + } + + return new Event.Handler(element, eventName, selector, callback).start(); + } + + Object.extend(Event, Event.Methods); + + Object.extend(Event, { + fire: fire, + observe: observe, + stopObserving: stopObserving, + on: on + }); + + Element.addMethods({ + fire: fire, + + observe: observe, + + stopObserving: stopObserving, + + on: on + }); + + Object.extend(document, { + fire: fire.methodize(), + + observe: observe.methodize(), + + stopObserving: stopObserving.methodize(), + + on: on.methodize(), + + loaded: false + }); + + if (window.Event) Object.extend(window.Event, Event); + else window.Event = Event; +})(); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ + + var timer; + + function fireContentLoadedEvent() { + if (document.loaded) return; + if (timer) window.clearTimeout(timer); + document.loaded = true; + document.fire('dom:loaded'); + } + + function checkReadyState() { + if (document.readyState === 'complete') { + document.stopObserving('readystatechange', checkReadyState); + fireContentLoadedEvent(); + } + } + + function pollDoScroll() { + try { document.documentElement.doScroll('left'); } + catch(e) { + timer = pollDoScroll.defer(); + return; + } + fireContentLoadedEvent(); + } + + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); + } else { + document.observe('readystatechange', checkReadyState); + if (window == top) + timer = pollDoScroll.defer(); + } + + Event.observe(window, 'load', fireContentLoadedEvent); +})(); + +Element.addMethods(); + +/*------------------------------- DEPRECATED -------------------------------*/ + +Hash.toQueryString = Object.toQueryString; + +var Toggle = { display: Element.toggle }; + +Element.Methods.childOf = Element.Methods.descendantOf; + +var Insertion = { + Before: function(element, content) { + return Element.insert(element, {before:content}); + }, + + Top: function(element, content) { + return Element.insert(element, {top:content}); + }, + + Bottom: function(element, content) { + return Element.insert(element, {bottom:content}); + }, + + After: function(element, content) { + return Element.insert(element, {after:content}); + } +}; + +var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); + +var Position = { + includeScrollOffsets: false, + + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = Element.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = Element.cumulativeScrollOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = Element.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + + cumulativeOffset: Element.Methods.cumulativeOffset, + + positionedOffset: Element.Methods.positionedOffset, + + absolutize: function(element) { + Position.prepare(); + return Element.absolutize(element); + }, + + relativize: function(element) { + Position.prepare(); + return Element.relativize(element); + }, + + realOffset: Element.Methods.cumulativeScrollOffset, + + offsetParent: Element.Methods.getOffsetParent, + + page: Element.Methods.viewportOffset, + + clone: function(source, target, options) { + options = options || { }; + return Element.clonePosition(target, source, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){ + function iter(name) { + return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]"; + } + + instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ? + function(element, className) { + className = className.toString().strip(); + var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className); + return cond ? document._getElementsByXPath('.//*' + cond, element) : []; + } : function(element, className) { + className = className.toString().strip(); + var elements = [], classNames = (/\s/.test(className) ? $w(className) : null); + if (!classNames && !className) return elements; + + var nodes = $(element).getElementsByTagName('*'); + className = ' ' + className + ' '; + + for (var i = 0, child, cn; child = nodes[i]; i++) { + if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) || + (classNames && classNames.all(function(name) { + return !name.toString().blank() && cn.include(' ' + name + ' '); + })))) + elements.push(Element.extend(child)); + } + return elements; + }; + + return function(className, parentElement) { + return $(parentElement || document.body).getElementsByClassName(className); + }; +}(Element.Methods); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); + +/*--------------------------------------------------------------------------*/ + +(function() { + window.Selector = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + }, + + findElements: function(rootElement) { + return Prototype.Selector.select(this.expression, rootElement); + }, + + match: function(element) { + return Prototype.Selector.match(element, this.expression); + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } + }); + + Object.extend(Selector, { + matchElements: function(elements, expression) { + var match = Prototype.Selector.match, + results = []; + + for (var i = 0, length = elements.length; i < length; i++) { + var element = elements[i]; + if (match(element, expression)) { + results.push(Element.extend(element)); + } + } + return results; + }, + + findElement: function(elements, expression, index) { + index = index || 0; + var matchIndex = 0, element; + for (var i = 0, length = elements.length; i < length; i++) { + element = elements[i]; + if (Prototype.Selector.match(element, expression) && index === matchIndex++) { + return Element.extend(element); + } + } + }, + + findChildElements: function(element, expressions) { + var selector = expressions.toArray().join(', '); + return Prototype.Selector.select(selector, element || document); + } + }); +})(); diff --git a/public/javascripts/rails.js b/public/javascripts/rails.js new file mode 100644 index 0000000..aed6aed --- /dev/null +++ b/public/javascripts/rails.js @@ -0,0 +1,191 @@ +(function() { + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + function isEventSupported(eventName) { + var el = document.createElement('div'); + eventName = 'on' + eventName; + var isSupported = (eventName in el); + if (!isSupported) { + el.setAttribute(eventName, 'return;'); + isSupported = typeof el[eventName] == 'function'; + } + el = null; + return isSupported; + } + + function isForm(element) { + return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM' + } + + function isInput(element) { + if (Object.isElement(element)) { + var name = element.nodeName.toUpperCase() + return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA' + } + else return false + } + + var submitBubbles = isEventSupported('submit'), + changeBubbles = isEventSupported('change') + + if (!submitBubbles || !changeBubbles) { + // augment the Event.Handler class to observe custom events when needed + Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap( + function(init, element, eventName, selector, callback) { + init(element, eventName, selector, callback) + // is the handler being attached to an element that doesn't support this event? + if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) || + (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) { + // "submit" => "emulated:submit" + this.eventName = 'emulated:' + this.eventName + } + } + ) + } + + if (!submitBubbles) { + // discover forms on the page by observing focus events which always bubble + document.on('focusin', 'form', function(focusEvent, form) { + // special handler for the real "submit" event (one-time operation) + if (!form.retrieve('emulated:submit')) { + form.on('submit', function(submitEvent) { + var emulated = form.fire('emulated:submit', submitEvent, true) + // if custom event received preventDefault, cancel the real one too + if (emulated.returnValue === false) submitEvent.preventDefault() + }) + form.store('emulated:submit', true) + } + }) + } + + if (!changeBubbles) { + // discover form inputs on the page + document.on('focusin', 'input, select, texarea', function(focusEvent, input) { + // special handler for real "change" events + if (!input.retrieve('emulated:change')) { + input.on('change', function(changeEvent) { + input.fire('emulated:change', changeEvent, true) + }) + input.store('emulated:change', true) + } + }) + } + + function handleRemote(element) { + var method, url, params; + + var event = element.fire("ajax:before"); + if (event.stopped) return false; + + if (element.tagName.toLowerCase() === 'form') { + method = element.readAttribute('method') || 'post'; + url = element.readAttribute('action'); + params = element.serialize(); + } else { + method = element.readAttribute('data-method') || 'get'; + url = element.readAttribute('href'); + params = {}; + } + + new Ajax.Request(url, { + method: method, + parameters: params, + evalScripts: true, + + onComplete: function(request) { element.fire("ajax:complete", request); }, + onSuccess: function(request) { element.fire("ajax:success", request); }, + onFailure: function(request) { element.fire("ajax:failure", request); } + }); + + element.fire("ajax:after"); + } + + function handleMethod(element) { + var method = element.readAttribute('data-method'), + url = element.readAttribute('href'), + csrf_param = $$('meta[name=csrf-param]')[0], + csrf_token = $$('meta[name=csrf-token]')[0]; + + var form = new Element('form', { method: "POST", action: url, style: "display: none;" }); + element.parentNode.insert(form); + + if (method !== 'post') { + var field = new Element('input', { type: 'hidden', name: '_method', value: method }); + form.insert(field); + } + + if (csrf_param) { + var param = csrf_param.readAttribute('content'), + token = csrf_token.readAttribute('content'), + field = new Element('input', { type: 'hidden', name: param, value: token }); + form.insert(field); + } + + form.submit(); + } + + + document.on("click", "*[data-confirm]", function(event, element) { + var message = element.readAttribute('data-confirm'); + if (!confirm(message)) event.stop(); + }); + + document.on("click", "a[data-remote]", function(event, element) { + if (event.stopped) return; + handleRemote(element); + event.stop(); + }); + + document.on("click", "a[data-method]", function(event, element) { + if (event.stopped) return; + handleMethod(element); + event.stop(); + }); + + document.on("submit", function(event) { + var element = event.findElement(), + message = element.readAttribute('data-confirm'); + if (message && !confirm(message)) { + event.stop(); + return false; + } + + var inputs = element.select("input[type=submit][data-disable-with]"); + inputs.each(function(input) { + input.disabled = true; + input.writeAttribute('data-original-value', input.value); + input.value = input.readAttribute('data-disable-with'); + }); + + var element = event.findElement("form[data-remote]"); + if (element) { + handleRemote(element); + event.stop(); + } + }); + + document.on("ajax:after", "form", function(event, element) { + var inputs = element.select("input[type=submit][disabled=true][data-disable-with]"); + inputs.each(function(input) { + input.value = input.readAttribute('data-original-value'); + input.removeAttribute('data-original-value'); + input.disabled = false; + }); + }); + + Ajax.Responders.register({ + onCreate: function(request) { + var csrf_meta_tag = $$('meta[name=csrf-token]')[0]; + + if (csrf_meta_tag) { + var header = 'X-CSRF-Token', + token = csrf_meta_tag.readAttribute('content'); + + if (!request.options.requestHeaders) { + request.options.requestHeaders = {}; + } + request.options.requestHeaders[header] = token; + } + } + }); +})(); diff --git a/public/javascripts/raphael-min.js b/public/javascripts/raphael-min.js new file mode 100644 index 0000000..4a99e3e --- /dev/null +++ b/public/javascripts/raphael-min.js @@ -0,0 +1,113 @@ +/* + * Raphael 1.4.3 - JavaScript Vector Library + * + * Copyright (c) 2010 Dmitry Baranovskiy (http://raphaeljs.com) + * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. + */ +Raphael=function(){function m(){if(m.is(arguments[0],U)){for(var a=arguments[0],b=Aa[K](m,a.splice(0,3+m.is(a[0],O))),c=b.set(),d=0,f=a[o];d

    ";if(ha.childNodes[o]!=2)return m.type=null;ha=null}m.svg=!(m.vml=m.type=="VML");G[p]=m[p];m._id=0;m._oid=0;m.fn={};m.is=function(a,b){b=ca.call(b);return b=="object"&&a===Object(a)||b=="undefined"&&typeof a==b||b=="null"&&a==null||ca.call(ob.call(a).slice(8,-1))==b};m.setWindow=function(a){X=a;C=X.document};function ra(a){if(m.vml){var b=/^\s+|\s+$/g;ra=T(function(d){var f;d=(d+s)[I](b, +s);try{var e=new X.ActiveXObject("htmlfile");e.write("");e.close();f=e.body}catch(g){f=X.createPopup().document.body}e=f.createTextRange();try{f.style.color=d;var h=e.queryCommandValue("ForeColor");h=(h&255)<<16|h&65280|(h&16711680)>>>16;return"#"+("000000"+h[N](16)).slice(-6)}catch(i){return"none"}})}else{var c=C.createElement("i");c.title="Rapha\u00ebl Colour Picker";c.style.display="none";C.body[y](c);ra=T(function(d){c.style.color=d;return C.defaultView.getComputedStyle(c,s).getPropertyValue("color")})}return ra(a)} +function qb(){return"hsb("+[this.h,this.s,this.b]+")"}function rb(){return this.hex}m.hsb2rgb=T(function(a,b,c){if(m.is(a,"object")&&"h"in a&&"s"in a&&"b"in a){c=a.b;b=a.s;a=a.h}var d;if(c==0)return{r:0,g:0,b:0,hex:"#000"};if(a>1||b>1||c>1){a/=255;b/=255;c/=255}d=~~(a*6);a=a*6-d;var f=c*(1-b),e=c*(1-b*a),g=c*(1-b*(1-a));a=[c,e,f,f,g,c,c][d];b=[g,c,c,e,f,f,g][d];d=[f,f,g,c,c,e,f][d];a*=255;b*=255;d*=255;c={r:a,g:b,b:d,toString:rb};a=(~~a)[N](16);b=(~~b)[N](16);d=(~~d)[N](16);a=a[I](ga,"0");b=b[I](ga, +"0");d=d[I](ga,"0");c.hex="#"+a+b+d;return c},m);m.rgb2hsb=T(function(a,b,c){if(m.is(a,"object")&&"r"in a&&"g"in a&&"b"in a){c=a.b;b=a.g;a=a.r}if(m.is(a,ea)){var d=m.getRGB(a);a=d.r;b=d.g;c=d.b}if(a>1||b>1||c>1){a/=255;b/=255;c/=255}var f=Y(a,b,c),e=$(a,b,c);d=f;if(e==f)return{h:0,s:0,b:f};else{var g=f-e;e=g/f;a=a==f?(b-c)/g:b==f?2+(c-a)/g:4+(a-b)/g;a/=6;a<0&&a++;a>1&&a--}return{h:a,s:e,b:d,toString:qb}},m);var sb=/,?([achlmqrstvxz]),?/gi,sa=/\s*,\s*/,tb={hs:1,rg:1};m._path2string=function(){return this.join(",")[I](sb, +"$1")};function T(a,b,c){function d(){var f=Array[p].slice.call(arguments,0),e=f[Q]("\u25ba"),g=d.cache=d.cache||{},h=d.count=d.count||[];if(g[z](e))return c?c(g[e]):g[e];h[o]>=1000&&delete g[h.shift()];h[E](e);g[e]=a[K](b,f);return c?c(g[e]):g[e]}return d}m.getRGB=T(function(a){if(!a||(a+=s).indexOf("-")+1)return{r:-1,g:-1,b:-1,hex:"none",error:1};if(a=="none")return{r:-1,g:-1,b:-1,hex:"none"};!(tb[z](a.substring(0,2))||a.charAt()=="#")&&(a=ra(a));var b,c,d,f,e;if(a=a.match(pb)){if(a[2]){d=da(a[2].substring(5), +16);c=da(a[2].substring(3,5),16);b=da(a[2].substring(1,3),16)}if(a[3]){d=da((e=a[3].charAt(3))+e,16);c=da((e=a[3].charAt(2))+e,16);b=da((e=a[3].charAt(1))+e,16)}if(a[4]){a=a[4][H](sa);b=A(a[0]);c=A(a[1]);d=A(a[2]);f=A(a[3])}if(a[5]){a=a[5][H](sa);b=A(a[0])*2.55;c=A(a[1])*2.55;d=A(a[2])*2.55;f=A(a[3])}if(a[6]){a=a[6][H](sa);b=A(a[0]);c=A(a[1]);d=A(a[2]);return m.hsb2rgb(b,c,d)}if(a[7]){a=a[7][H](sa);b=A(a[0])*2.55;c=A(a[1])*2.55;d=A(a[2])*2.55;return m.hsb2rgb(b,c,d)}a={r:b,g:c,b:d};b=(~~b)[N](16); +c=(~~c)[N](16);d=(~~d)[N](16);b=b[I](ga,"0");c=c[I](ga,"0");d=d[I](ga,"0");a.hex="#"+b+c+d;isFinite(A(f))&&(a.o=f);return a}return{r:-1,g:-1,b:-1,hex:"none",error:1}},m);m.getColor=function(a){a=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||0.75};var b=this.hsb2rgb(a.h,a.s,a.b);a.h+=0.075;if(a.h>1){a.h=0;a.s-=0.2;a.s<=0&&(this.getColor.start={h:0,s:1,b:a.b})}return b.hex};m.getColor.reset=function(){delete this.start};var ub=/([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig, +vb=/(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig;m.parsePathString=T(function(a){if(!a)return null;var b={a:7,c:6,h:1,l:2,m:2,q:4,s:4,t:2,v:1,z:0},c=[];if(m.is(a,U)&&m.is(a[0],U))c=ta(a);c[o]||(a+s)[I](ub,function(d,f,e){var g=[];d=ca.call(f);e[I](vb,function(h,i){i&&g[E](+i)});if(d=="m"&&g[o]>2){c[E]([f][M](g.splice(0,2)));d="l";f=f=="m"?"l":"L"}for(;g[o]>=b[d];){c[E]([f][M](g.splice(0,b[d])));if(!b[d])break}});c[N]=m._path2string;return c});m.findDotsAtSegment=function(a,b,c,d,f,e,g,h,i){var j=1-i,l= +D(j,3)*a+D(j,2)*3*i*c+j*3*i*i*f+D(i,3)*g;j=D(j,3)*b+D(j,2)*3*i*d+j*3*i*i*e+D(i,3)*h;var n=a+2*i*(c-a)+i*i*(f-2*c+a),r=b+2*i*(d-b)+i*i*(e-2*d+b),q=c+2*i*(f-c)+i*i*(g-2*f+c),k=d+2*i*(e-d)+i*i*(h-2*e+d);a=(1-i)*a+i*c;b=(1-i)*b+i*d;f=(1-i)*f+i*g;e=(1-i)*e+i*h;h=90-w.atan((n-q)/(r-k))*180/w.PI;(n>q||r1){B=w.sqrt(B);c=B*c;d=B*d}B=c*c;var L=d*d;B=(e==g?-1:1)*w.sqrt(w.abs((B*L-B*x*x-L*k*k)/(B*x*x+L*k*k)));e=B*c*x/d+(a+h)/2;var B= +B*-d*k/c+(b+i)/2,x=w.asin(((b-B)/d).toFixed(7));k=w.asin(((i-B)/d).toFixed(7));x=ak)x-=l*2;if(!g&&k>x)k-=l*2}l=k-x;if(w.abs(l)>n){q=k;l=h;L=i;k=x+n*(g&&k>x?1:-1);h=e+c*w.cos(k);i=B+d*w.sin(k);q=Qa(h,i,c,d,f,0,g,l,L,[k,q,e,B])}l=k-x;f=w.cos(x);e=w.sin(x);g=w.cos(k);k=w.sin(k);l=w.tan(l/4);c=4/3*c*l;l=4/3*d*l;d=[a,b];a=[a+c*e,b-l*f];b=[h+c*k,i-l*g];h=[h,i];a[0]=2*d[0]-a[0];a[1]=2*d[1]-a[1];if(j)return[a,b,h][M](q);else{q=[a,b,h][M](q)[Q]()[H](","); +j=[];h=0;for(i=q[o];h1000000000000&&(n=0.5);w.abs(i)>1000000000000&&(i=0.5);if(n>0&&n<1){n=la(a,b,c,d,f,e,g,h,n);q[E](n.x);r[E](n.y)}if(i> +0&&i<1){n=la(a,b,c,d,f,e,g,h,i);q[E](n.x);r[E](n.y)}i=e-2*d+b-(h-2*e+d);j=2*(d-b)-2*(e-d);l=b-d;n=(-j+w.sqrt(j*j-4*i*l))/2/i;i=(-j-w.sqrt(j*j-4*i*l))/2/i;w.abs(n)>1000000000000&&(n=0.5);w.abs(i)>1000000000000&&(i=0.5);if(n>0&&n<1){n=la(a,b,c,d,f,e,g,h,n);q[E](n.x);r[E](n.y)}if(i>0&&i<1){n=la(a,b,c,d,f,e,g,h,i);q[E](n.x);r[E](n.y)}return{min:{x:$[K](0,q),y:$[K](0,r)},max:{x:Y[K](0,q),y:Y[K](0,r)}}}),ua=T(function(a,b){var c=ka(a),d=b&&ka(b);a={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null};b={x:0,y:0, +bx:0,by:0,X:0,Y:0,qx:null,qy:null};function f(q,k){var t;if(!q)return["C",k.x,k.y,k.x,k.y,k.x,k.y];!(q[0]in{T:1,Q:1})&&(k.qx=k.qy=null);switch(q[0]){case "M":k.X=q[1];k.Y=q[2];break;case "A":q=["C"][M](Qa[K](0,[k.x,k.y][M](q.slice(1))));break;case "S":t=k.x+(k.x-(k.bx||k.x));k=k.y+(k.y-(k.by||k.y));q=["C",t,k][M](q.slice(1));break;case "T":k.qx=k.x+(k.x-(k.qx||k.x));k.qy=k.y+(k.y-(k.qy||k.y));q=["C"][M](Pa(k.x,k.y,k.qx,k.qy,q[1],q[2]));break;case "Q":k.qx=q[1];k.qy=q[2];q=["C"][M](Pa(k.x,k.y,q[1], +q[2],q[3],q[4]));break;case "L":q=["C"][M](wa(k.x,k.y,q[1],q[2]));break;case "H":q=["C"][M](wa(k.x,k.y,q[1],k.y));break;case "V":q=["C"][M](wa(k.x,k.y,k.x,q[1]));break;case "Z":q=["C"][M](wa(k.x,k.y,k.X,k.Y));break}return q}function e(q,k){if(q[k][o]>7){q[k].shift();for(var t=q[k];t[o];)q.splice(k++,0,["C"][M](t.splice(0,6)));q.splice(k,1);i=Y(c[o],d&&d[o]||0)}}function g(q,k,t,L,B){if(q&&k&&q[B][0]=="M"&&k[B][0]!="M"){k.splice(B,0,["M",L.x,L.y]);t.bx=0;t.by=0;t.x=q[B][1];t.y=q[B][2];i=Y(c[o],d&& +d[o]||0)}}for(var h=0,i=Y(c[o],d&&d[o]||0);h0.5)*2-1;D(f-0.5,2)+D(e-0.5,2)>0.25&&(e=w.sqrt(0.25-D(f-0.5,2))*l+0.5)&&e!=0.5&&(e=e.toFixed(5)-1.0E-5*l)}return s});b=b[H](/\s*\-\s*/);if(d=="linear"){var h=b.shift();h=-A(h);if(isNaN(h))return null;h=[0,0,w.cos(h*w.PI/180),w.sin(h*w.PI/180)];var i=1/(Y(w.abs(h[2]),w.abs(h[3]))||1);h[2]*=i;h[3]*=i;if(h[2]<0){h[0]=-h[2];h[2]=0}if(h[3]<0){h[1]=-h[3];h[3]=0}}b=Ra(b);if(!b)return null; +i=a.getAttribute(aa);(i=i.match(/^url\(#(.*)\)$/))&&c.defs.removeChild(C.getElementById(i[1]));i=v(d+"Gradient");i.id="r"+(m._id++)[N](36);v(i,d=="radial"?{fx:f,fy:e}:{x1:h[0],y1:h[1],x2:h[2],y2:h[3]});c.defs[y](i);c=0;for(h=b[o];cb.height&&(b.height=e.y+e.height-b.y);e.x+e.width-b.x>b.width&&(b.width=e.x+e.width-b.x)}}a&&this.hide();return b};u[p].attr=function(a,b){if(this.removed)return this;if(a==null){a={};for(var c in this.attrs)if(this.attrs[z](c))a[c]=this.attrs[c];this._.rt.deg&&(a.rotation=this.rotate());(this._.sx!=1||this._.sy!= +1)&&(a.scale=this.scale());a.gradient&&a.fill=="none"&&(a.fill=a.gradient)&&delete a.gradient;return a}if(b==null&&m.is(a,ea)){if(a=="translation")return ya.call(this);if(a=="rotation")return this.rotate();if(a=="scale")return this.scale();if(a==aa&&this.attrs.fill=="none"&&this.attrs.gradient)return this.attrs.gradient;return this.attrs[a]}if(b==null&&m.is(a,U)){b={};c=0;for(var d=a.length;c1&&(a=1);f.opacity=a}b.fill&&(f.on=true);if(f.on==null||b.fill=="none")f.on=false;if(f.on&&b.fill)if(a=b.fill.match(Na)){f.src=a[1];f.type="tile"}else{f.color=m.getRGB(b.fill).hex;f.src= +s;f.type="solid";if(m.getRGB(b.fill).error&&(g.type in{circle:1,ellipse:1}||(b.fill+s).charAt()!="r")&&ma(g,b.fill)){d.fill="none";d.gradient=b.fill}}e&&c[y](f);f=c.getElementsByTagName("stroke")&&c.getElementsByTagName("stroke")[0];e=false;!f&&(e=f=R("stroke"));if(b.stroke&&b.stroke!="none"||b["stroke-width"]||b["stroke-opacity"]!=null||b["stroke-dasharray"]||b["stroke-miterlimit"]||b["stroke-linejoin"]||b["stroke-linecap"])f.on=true;(b.stroke=="none"||f.on==null||b.stroke==0||b["stroke-width"]== +0)&&(f.on=false);a=m.getRGB(b.stroke);f.on&&b.stroke&&(f.color=a.hex);a=((+d["stroke-opacity"]+1||2)-1)*((+d.opacity+1||2)-1)*((+a.o+1||2)-1);h=(A(b["stroke-width"])||1)*0.75;a<0&&(a=0);a>1&&(a=1);b["stroke-width"]==null&&(h=d["stroke-width"]);b["stroke-width"]&&(f.weight=h);h&&h<1&&(a*=h)&&(f.weight=1);f.opacity=a;b["stroke-linejoin"]&&(f.joinstyle=b["stroke-linejoin"]||"miter");f.miterlimit=b["stroke-miterlimit"]||8;b["stroke-linecap"]&&(f.endcap=b["stroke-linecap"]=="butt"?"flat":b["stroke-linecap"]== +"square"?"square":"round");if(b["stroke-dasharray"]){a={"-":"shortdash",".":"shortdot","-.":"shortdashdot","-..":"shortdashdotdot",". ":"dot","- ":"dash","--":"longdash","- .":"dashdot","--.":"longdashdot","--..":"longdashdotdot"};f.dashstyle=a[z](b["stroke-dasharray"])?a[b["stroke-dasharray"]]:s}e&&c[y](f)}if(g.type=="text"){f=g.paper.span.style;d.font&&(f.font=d.font);d["font-family"]&&(f.fontFamily=d["font-family"]);d["font-size"]&&(f.fontSize=d["font-size"]);d["font-weight"]&&(f.fontWeight=d["font-weight"]); +d["font-style"]&&(f.fontStyle=d["font-style"]);g.node.string&&(g.paper.span.innerHTML=(g.node.string+s)[I](/"));g.W=d.w=g.paper.span.offsetWidth;g.H=d.h=g.paper.span.offsetHeight;g.X=d.x;g.Y=d.y+F(g.H/2);switch(d["text-anchor"]){case "start":g.node.style["v-text-align"]="left";g.bbx=F(g.W/2);break;case "end":g.node.style["v-text-align"]="right";g.bbx=-F(g.W/2);break;default:g.node.style["v-text-align"]="center";break}}};ma=function(a,b){a.attrs=a.attrs|| +{};var c="linear",d=".5 .5";a.attrs.gradient=b;b=(b+s)[I](Ya,function(i,j,l){c="radial";if(j&&l){j=A(j);l=A(l);D(j-0.5,2)+D(l-0.5,2)>0.25&&(l=w.sqrt(0.25-D(j-0.5,2))*((l>0.5)*2-1)+0.5);d=j+P+l}return s});b=b[H](/\s*\-\s*/);if(c=="linear"){var f=b.shift();f=-A(f);if(isNaN(f))return null}var e=Ra(b);if(!e)return null;a=a.shape||a.node;b=a.getElementsByTagName(aa)[0]||R(aa);!b.parentNode&&a.appendChild(b);if(e[o]){b.on=true;b.method="none";b.color=e[0].color;b.color2=e[e[o]-1].color;a=[];for(var g=0, +h=e[o];g')}}catch(Kb){R=function(a){return C.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}Aa=function(){var a=Sa[K](0,arguments),b=a.container,c=a.height,d=a.width,f=a.x;a=a.y;if(!b)throw new Error("VML container not found.");var e=new G,g=e.canvas=C.createElement("div"),h=g.style;f=f||0;a=a||0;d=d||512; +c=c||342;d==+d&&(d+="px");c==+c&&(c+="px");e.width=1000;e.height=1000;e.coordsize=ja*1000+P+ja*1000;e.coordorigin="0 0";e.span=C.createElement("span");e.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";g[y](e.span);h.cssText=m.format("width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",d,c);if(b==1){C.body[y](g);h.left=f+"px";h.top=a+"px";h.position="absolute"}else b.firstChild?b.insertBefore(g, +b.firstChild):b[y](g);Fa.call(e,e,m.fn);return e};G[p].clear=function(){this.canvas.innerHTML=s;this.span=C.createElement("span");this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";this.canvas[y](this.span);this.bottom=this.top=null};G[p].remove=function(){this.canvas.parentNode.removeChild(this.canvas);for(var a in this)this[a]=Xa(a);return true}}G[p].safari=/^Apple|^Google/.test(X.navigator.vendor)&&(!(X.navigator.userAgent.indexOf("Version/4.0")+ +1)||X.navigator.platform.slice(0,2)=="iP")?function(){var a=this.rect(-99,-99,this.width+99,this.height+99);X.setTimeout(function(){a.remove()})}:function(){};function Db(){this.returnValue=false}function Eb(){return this.originalEvent.preventDefault()}function Fb(){this.cancelBubble=true}function Gb(){return this.originalEvent.stopPropagation()}var Hb=function(){if(C.addEventListener)return function(a,b,c,d){var f=Ba&&Ca[b]?Ca[b]:b;function e(g){if(Ba&&Ca[z](b))for(var h=0,i=g.targetTouches&&g.targetTouches.length;h< +i;h++)if(g.targetTouches[h].target==a){i=g;g=g.targetTouches[h];g.originalEvent=i;g.preventDefault=Eb;g.stopPropagation=Gb;break}return c.call(d,g)}a.addEventListener(f,e,false);return function(){a.removeEventListener(f,e,false);return true}};else if(C.attachEvent)return function(a,b,c,d){function f(g){g=g||X.event;g.preventDefault=g.preventDefault||Db;g.stopPropagation=g.stopPropagation||Fb;return c.call(d,g)}a.attachEvent("on"+b,f);function e(){a.detachEvent("on"+b,f);return true}return e}}();for(ha= +Ma[o];ha--;)(function(a){m[a]=u[p][a]=function(b){if(m.is(b,"function")){this.events=this.events||[];this.events.push({name:a,f:b,unbind:Hb(this.shape||this.node||C,a,b,this)})}return this};m["un"+a]=u[p]["un"+a]=function(b){for(var c=this.events,d=c[o];d--;)if(c[d].name==a&&c[d].f==b){c[d].unbind();c.splice(d,1);!c.length&&delete this.events;return this}return this}})(Ma[ha]);u[p].hover=function(a,b){return this.mouseover(a).mouseout(b)};u[p].unhover=function(a,b){return this.unmouseover(a).unmouseout(b)}; +u[p].drag=function(a,b,c){this._drag={};var d=this.mousedown(function(g){(g.originalEvent?g.originalEvent:g).preventDefault();this._drag.x=g.clientX;this._drag.y=g.clientY;this._drag.id=g.identifier;b&&b.call(this,g.clientX,g.clientY);Raphael.mousemove(f).mouseup(e)});function f(g){var h=g.clientX,i=g.clientY;if(Ba)for(var j=g.touches.length,l;j--;){l=g.touches[j];if(l.identifier==d._drag.id){h=l.clientX;i=l.clientY;(g.originalEvent?g.originalEvent:g).preventDefault();break}}else g.preventDefault(); +a&&a.call(d,h-d._drag.x,i-d._drag.y,h,i)}function e(){d._drag={};Raphael.unmousemove(f).unmouseup(e);c&&c.call(d)}return this};G[p].circle=function(a,b,c){return ab(this,a||0,b||0,c||0)};G[p].rect=function(a,b,c,d,f){return bb(this,a||0,b||0,c||0,d||0,f||0)};G[p].ellipse=function(a,b,c,d){return cb(this,a||0,b||0,c||0,d||0)};G[p].path=function(a){a&&!m.is(a,ea)&&!m.is(a[0],U)&&(a+=s);return Za(m.format[K](m,arguments),this)};G[p].image=function(a,b,c,d,f){return db(this,a||"about:blank",b||0,c||0, +d||0,f||0)};G[p].text=function(a,b,c){return eb(this,a||0,b||0,c||s)};G[p].set=function(a){arguments[o]>1&&(a=Array[p].splice.call(arguments,0,arguments[o]));return new Z(a)};G[p].setSize=fb;G[p].top=G[p].bottom=null;G[p].raphael=m;function ib(){return this.x+P+this.y}u[p].resetScale=function(){if(this.removed)return this;this._.sx=1;this._.sy=1;this.attrs.scale="1 1"};u[p].scale=function(a,b,c,d){if(this.removed)return this;if(a==null&&b==null)return{x:this._.sx,y:this._.sy,toString:ib};b=b||a;!+b&& +(b=a);var f,e,g=this.attrs;if(a!=0){var h=this.getBBox(),i=h.x+h.width/2,j=h.y+h.height/2;f=a/this._.sx;e=b/this._.sy;c=+c||c==0?c:i;d=+d||d==0?d:j;h=~~(a/w.abs(a));var l=~~(b/w.abs(b)),n=this.node.style,r=c+(i-c)*f;j=d+(j-d)*e;switch(this.type){case "rect":case "image":var q=g.width*h*f,k=g.height*l*e;this.attr({height:k,r:g.r*$(h*f,l*e),width:q,x:r-q/2,y:j-k/2});break;case "circle":case "ellipse":this.attr({rx:g.rx*h*f,ry:g.ry*l*e,r:g.r*$(h*f,l*e),cx:r,cy:j});break;case "text":this.attr({x:r,y:j}); +break;case "path":i=Oa(g.path);for(var t=true,L=0,B=i[o];L=i)return r;l=r}});function Ha(a,b){return function(c,d,f){c=ua(c); +for(var e,g,h,i,j="",l={},n=0,r=0,q=c.length;rd){if(b&&!l.start){e=jb(e,g,h[1],h[2],h[3],h[4],h[5],h[6],d-n);j+=["C",e.start.x,e.start.y,e.m.x,e.m.y,e.x,e.y];if(f)return j;l.start=j;j=["M",e.x,e.y+"C",e.n.x,e.n.y,e.end.x,e.end.y,h[5],h[6]][Q]();n+=i;e=+h[5];g=+h[6];continue}if(!a&&!b){e=jb(e,g,h[1],h[2],h[3],h[4],h[5],h[6],d-n);return{x:e.x,y:e.y,alpha:e.alpha}}}n+=i;e=+h[5];g=+h[6]}j+=h}l.end=j;e=a?n: +b?l:m.findDotsAtSegment(e,g,h[1],h[2],h[3],h[4],h[5],h[6],1);e.alpha&&(e={x:e.x,y:e.y,alpha:e.alpha});return e}}var Ib=T(function(a,b,c,d,f,e,g,h){for(var i={x:0,y:0},j=0,l=0;l<1.01;l+=0.01){var n=la(a,b,c,d,f,e,g,h,l);l&&(j+=D(D(i.x-n.x,2)+D(i.y-n.y,2),0.5));i=n}return j}),kb=Ha(1),za=Ha(),Ia=Ha(0,1);u[p].getTotalLength=function(){if(this.type=="path"){if(this.node.getTotalLength)return this.node.getTotalLength();return kb(this.attrs.path)}};u[p].getPointAtLength=function(a){if(this.type=="path")return za(this.attrs.path, +a)};u[p].getSubpath=function(a,b){if(this.type=="path"){if(w.abs(this.getTotalLength()-b)<1.0E-6)return Ia(this.attrs.path,a).end;b=Ia(this.attrs.path,b,1);return a?Ia(b,a).end:b}};m.easing_formulas={linear:function(a){return a},"<":function(a){return D(a,3)},">":function(a){return D(a-1,3)+1},"<>":function(a){a*=2;if(a<1)return D(a,3)/2;a-=2;return(D(a,3)+2)/2},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a-=1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){if(a== +0||a==1)return a;var b=0.3,c=b/4;return D(2,-10*a)*w.sin((a-c)*2*w.PI/b)+1},bounce:function(a){var b=7.5625,c=2.75;if(a<1/c)a=b*a*a;else if(a<2/c){a-=1.5/c;a=b*a*a+0.75}else if(a<2.5/c){a-=2.25/c;a=b*a*a+0.9375}else{a-=2.625/c;a=b*a*a+0.984375}return a}};var S={length:0};function lb(){var a=+new Date;for(var b in S)if(b!="length"&&S[z](b)){var c=S[b];if(c.stop||c.el.removed){delete S[b];S[o]--}else{var d=a-c.start,f=c.ms,e=c.easing,g=c.from,h=c.diff,i=c.to,j=c.t,l=c.prev||0,n=c.el,r=c.callback,q= +{},k;if(d p:first-child{ + margin-top:0; +} +#facebox .content > p:last-child{ + margin-bottom:0; +} + +#facebox .close{ + position:absolute; + top:5px; + right:5px; + padding:2px; + background:#fff; +} +#facebox .close img{ + opacity:0.3; +} +#facebox .close:hover img{ + opacity:1.0; +} + +#facebox .loading { + text-align: center; +} + +#facebox .image { + text-align: center; +} + +#facebox img { + border: 0; + margin: 0; +} + +#facebox_overlay { + position: fixed; + top: 0px; + left: 0px; + height:100%; + width:100%; +} + +.facebox_hide { + z-index:-100; +} + +.facebox_overlayBG { + background-color: #000; + z-index: 99; +} +.facebox-footnote{ + margin-top:40px; +} \ No newline at end of file diff --git a/public/stylesheets/reset.css b/public/stylesheets/reset.css new file mode 100644 index 0000000..d709810 --- /dev/null +++ b/public/stylesheets/reset.css @@ -0,0 +1,46 @@ +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, font, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td { + margin: 0; + padding: 0; + border: 0; + outline: 0; + font-weight: inherit; + font-style: inherit; + font-size: 100%; + font-family: inherit; + vertical-align: baseline; +} +/* remember to define focus styles! */ +:focus { + outline: 0; +} +body { + line-height: 1; + color: black; + background: white; +} +ol, ul { + list-style: none; +} +/* tables still need 'cellspacing="0"' in the markup */ +table { + border-collapse: separate; + border-spacing: 0; +} +caption, th, td { + text-align: left; + font-weight: normal; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ""; +} +blockquote, q { + quotes: "" ""; +} diff --git a/public/stylesheets/scaffold.css b/public/stylesheets/scaffold.css new file mode 100644 index 0000000..1ae7000 --- /dev/null +++ b/public/stylesheets/scaffold.css @@ -0,0 +1,56 @@ +body { background-color: #fff; color: #333; } + +body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +a { color: #000; } +a:visited { color: #666; } +a:hover { color: #fff; background-color:#000; } + +div.field, div.actions { + margin-bottom: 10px; +} + +#notice { + color: green; +} + +.field_with_errors { + padding: 2px; + background-color: red; + display: table; +} + +#error_explanation { + width: 450px; + border: 2px solid red; + padding: 7px; + padding-bottom: 0; + margin-bottom: 20px; + background-color: #f0f0f0; +} + +#error_explanation h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + margin-bottom: 0px; + background-color: #c00; + color: #fff; +} + +#error_explanation ul li { + font-size: 12px; + list-style: square; +} diff --git a/public/stylesheets/site.css b/public/stylesheets/site.css new file mode 100755 index 0000000..62b1526 --- /dev/null +++ b/public/stylesheets/site.css @@ -0,0 +1,378 @@ +body { + font-family: verdana, arial, sans-serif; + font-size: 14px; + text-align: center; +} +h1, h2, h3, h4 { + font-family: georgia, serif; + margin: 10px 45px; padding: 0; +} +h1 { + color: #333; + font-size: 48px; + font-weight: normal; + margin: 10px 0px; +} +h3 { + color: white; + font-size: 21px; + font-weight: normal; +} +a,a:link,a:visited { + text-decoration: none; + color: #57ad11; +} +a:hover { + text-decoration: underline; + color: #57ad11; +} +a:active { + text-decoration: underline; + color: #dddddd; +} +input.keyboard-selector-input { + position: fixed; + top: 0; + _position: absolute; + _top: expression(eval(document.body.scrollTop)); + left: -300px; +} +.container { + width: 678px; + margin: 0 auto; + text-align: left; +} +.content { + width: 712px; + background: url(/images/tile.png) repeat-y; +} +#lilBrowser { + position: absolute; + background-color: white; + top: -540px; + left: -440px; + width: 510px; + height: 430px; + padding: 5px; + border: solid 1px #444; + z-index: 100; +} +h3#lbTitle { + color: #444; + font-family: verdana, arial, sans-serif; + font-size: small; + float:left; + line-height: 90%; + margin: 0; padding: 3px; +} +p#lbClose { + font-size: small; + float: right; + margin: 0; padding: 3px; +} +p#lbClose a { + display: inline; +} +.shellwin { + width: 712px; + padding-left: 12px; + background: url(/images/background.png) no-repeat; +} + +/* tutorial panes */ +.stretcher { + color: #f1f1ff; + display: none; + margin: 0; + padding: 0; + padding-left: 12px; + background: url(/images/tile.png) repeat-y; +} +.stretcher a, .stretcher a:link, .stretcher a:visited, .stretcher a:active { + text-decoration: none; + color: #a7ed91; +} +.stretcher a:hover { + text-decoration: underline; + color: #b7fd91; +} +.stretcher p { + margin: 10px 16px; +} +.stretcher dl, .stretcher ul { + background-color: white; + color: #333; + padding: 4px 8px; + font-size: 12px; + margin-left: 16px; + list-style: none; +} +.stretcher li { + margin: 6px; +} +.stretcher p code { + background-color: #874a20; + color: #fedeec; + padding: 1px 4px; +} +.stretcher p code.cmd { + background-color: #eeeeec; + color: #204a87; +} +.stretcher dt { + font-weight: bold; +} + +.chapmark { + padding: 6px 0; + margin-left: 12px; + margin-right: 22px; + color: #553; + background: #efefe1; +} +.chapmark h3 { + color: #335; +} +.chapmark a, .chapmark a:link, .chapmark a:visited, .chapmark a:active { + text-decoration: none; + color: #372d61; +} +.chapmark a:hover { + text-decoration: underline; + color: #477d51; +} +.note { color: #ddc; text-align: center; font-size: xx-small; } +ul li strong { color: #286; border-bottom: solid 2px #cca; } +ul li code { background-color: #f1f1f1; padding: 1px 3px; border-bottom: solid 2px #ddd; } +ul li code.faded { color: #899; } +code strong { background-color: #dcffb9; padding: 1px 3px; } +ul.commands li strong { display: block; float: left; width: 60px; border: none; } + +/* irb terminal */ +.terminal { + background-color: #ffffff; + border: solid 1px #204a87; + width: 678px; + height: 240px; + overflow: auto; +} +.console { + padding: 4px; margin-left: -50px; + font-family: "Andale Mono", courier, fixed, monospace; + font-size: 14px; + line-height: 16px; + color: #204a87; + text-align: left; + width: 664px; + height:220px; +} + +.console div b { + background-color: #874a20; + color: #fedeac; +} +div.answer, div.stdout, div.no_answer, div.load { + display: none; +} + +/* terminal escape colors */ +span.fore_black { color: #2e3436; } +span.fore_dark_gray { color: #888a85; } +span.fore_gray { color: #babdb6; } +span.fore_white { color: #eeeeec; } +span.fore_blue { color: #204a87; } +span.fore_lt_blue { color: #729fcf; } +span.fore_green { color: #788600; font-weight: bold; } +span.fore_lt_green { color: #cbe134; } +span.fore_cyan { color: #c4a000; } /* using cyan for yellows */ +span.fore_lt_cyan { color: #fc994f; } +span.fore_red { color: #a40000; } +span.fore_lt_red { color: #ef2929; font-weight: bold; } +span.fore_purple { color: #5c3566; } +span.fore_lt_purple { color: #ad7fa8; } +span.fore_brown { color: #8f5972; } +span.fore_lt_brown { color: #b9b9de; } +span.back_black { background-color: #2e3436; } +span.back_dark_gray { background-color: #888a85; } +span.back_gray { background-color: #babdb6; } +span.back_white { background-color: #eeeeec; } +span.back_blue { background-color: #204a87; } +span.back_lt_blue { background-color: #729fcf; } +span.back_green { background-color: #788600; } +span.back_lt_green { background-color: #cbe134; } +span.back_cyan { background-color: #c4a000; } /* using cyan for yellows */ +span.back_lt_cyan { background-color: #fce94f; } +span.back_red { background-color: #a40000; } +span.back_lt_red { background-color: #ef2929; } +span.back_purple { background-color: #5c3566; } +span.back_lt_purple { background-color: #ad7fa8; } +span.back_brown { background-color: #8f5902; } +span.back_lt_brown { background-color: #b9b96e; } + +/** no ways***/ + +div.main-wrapper-bottom { + height:4px;background-position: 0px -80px; background-repeat: no-repeat; + font-size:0 /* IE6, go figure */ +} +div.main-wrapper-borders { + background-position: -1731px 0; padding:15px; + background-repeat:repeat-y; +} +div.console-wrapper { + margin:0px auto ;width:566px; + cursor:text; + font-family:monospace; +} +div.console-wrapper-top { + height:3px;background-position: -50px -48px; background-repeat: no-repeat; + font-size:0 /* IE6, go figure */ +} +div.console-wrapper-bottom { + height:3px;background-position: -50px -51px; background-repeat: no-repeat; + font-size:0 /* IE6, go figure */ +} +div.console-wrapper-borders { + background-position: 0px 0px; padding:1px; + background-repeat:repeat-y; +} +div.guide-wrapper { + color:#fff; width:566px;margin-left:2px +} +div.guide-wrapper-top { + height:4px;background-position: 0 -65px; + font-size:0 /* IE6, go figure */ +} +div.guide-wrapper-bottom { + height:4px;background-position: 0 -70px; + font-size:0 /* IE6, go figure */ +} +div.guide-wrapper-borders { + background-position: -1166px 0px;padding:15px; + background-repeat:repeat-y; +} + +div.footer-wrapper-borders { + background-position: -566px 0px; padding:10px; + background-repeat:repeat-y; + font-size:12px +} +h1.main-header { + text-indent:-9999px; background-position: -49px 0px; + background-repeat: no-repeat; + width:318px; height:48px; margin-bottom:20px; + float:left +} + +div.footer { + line-height: 1.3em; +} + div.console div.jquery-console-inner +{ height:100%; overflow:auto; background:white} +div.console div.jquery-console-prompt-box +{ color:#437375; font-family:monospace; margin-top:0.5em; } +div.console div.jquery-console-prompt-box .prompt-done +{ cursor: pointer } +div.console div.jquery-console-prompt-box .prompt-done:hover +{ background:#453D5B; color: white; } +div.console div.jquery-console-focus span.jquery-console-cursor +{ background:#666; color:#fff; } +div.console div.jquery-console-message-error { + color:#ef0505; font-family:sans-serif; font-weight:bold; + padding-top:0.25em +} +div.console div.jquery-console-message-value +{ color:#000; font-family:monospace;padding-top:0.25em; font-weight: bold; } +div.console div.jquery-console-message-type +{ color:#382567; font-family:monospace;padding-left:0em;padding-top:0.25em; font-size:.9em } +div.console span.jquery-console-prompt-label { font-weight:bold } +div.console div.jquery-console-welcome { font-family:"DejaVu Sans",sans-serif; } + +div.share-wrapper { font-size:12px;padding:10px 0em 0em 10px } +div.share-wrapper strong { font-weight: bold } + +.clearfix:after { content:"."; display:block; height:0; clear:both; visibility:hidden } +div.menu { + float:right; + margin-right:2px; + margin-top:40px; + margin-bottom:5px +} +a.reset-btn { + float:left; + display:block; + width:59px; + height:24px; + background-position: -427px -18px; background-repeat: no-repeat; +} +a.reset-btn span { display:none } + +div.clear { clear:both } + + +div.console-wrapper .notice { + position:absolute; + bottom:0;right:0; + margin:1px; + background:#eee; + color:black; + padding:10px; + font-size:12px; + font-family:sans-serif; + font-weight:bold; +} +p.ajax-loader { background:url(../images/ajax-loader.gif); width:16px; height:16px;text-indent:-9999px } + +.notice a { padding:3px;background:#333;color:white} +.notice .action { text-align: right } + +/* Support Try Ruby! */ + + +a.trigger{ +position: absolute; +text-decoration: none; +top: 80px; right: 0; +font-size: 14px; +letter-spacing:-1px; +font-family: verdana, helvetica, arial, sans-serif; +color:#fff; +padding: 10px 10px 10px 10px; +font-weight: 500; +background:#333333 url(images/plus.png) 15% 55% no-repeat; +border:1px solid #444444; +-moz-border-radius-topleft: 20px; +-webkit-border-top-left-radius: 20px; +-moz-border-radius-bottomleft: 20px; +-webkit-border-bottom-left-radius: 20px; +-moz-border-radius-bottomright: 0px; +-webkit-border-bottom-right-radius: 0px; +display: block; +} + +a.trigger:hover{ +position: absolute; +text-decoration: none; +top: 80px; right: 0; +font-size: 14px; +letter-spacing:-1px; +font-family: verdana, helvetica, arial, sans-serif; +color:#fff; +padding: 10px 10px 10px 10px; +font-weight: 500; +background: green; +border:1px solid #444444; +-moz-border-radius-topleft: 20px; +-webkit-border-top-left-radius: 20px; +-moz-border-radius-bottomleft: 20px; +-webkit-border-bottom-left-radius: 20px; +-moz-border-radius-bottomright: 0px; +-webkit-border-bottom-right-radius: 0px; +display: block; +} + +a.active.trigger { +background:#222222 url(images/minus.png) 15% 55% no-repeat; +} + + \ No newline at end of file diff --git a/script/rails b/script/rails new file mode 100755 index 0000000..f8da2cf --- /dev/null +++ b/script/rails @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. + +APP_PATH = File.expand_path('../../config/application', __FILE__) +require File.expand_path('../../config/boot', __FILE__) +require 'rails/commands' diff --git a/test/functional/index_controller_test.rb b/test/functional/index_controller_test.rb new file mode 100644 index 0000000..005acb0 --- /dev/null +++ b/test/functional/index_controller_test.rb @@ -0,0 +1,9 @@ +require 'test_helper' + +class IndexControllerTest < ActionController::TestCase + test "should get terminal" do + get :terminal + assert_response :success + end + +end diff --git a/tryruby/test/functional/tutorials_controller_test.rb b/test/functional/tutorials_controller_test.rb similarity index 54% rename from tryruby/test/functional/tutorials_controller_test.rb rename to test/functional/tutorials_controller_test.rb index a2e7246..937e313 100644 --- a/tryruby/test/functional/tutorials_controller_test.rb +++ b/test/functional/tutorials_controller_test.rb @@ -1,8 +1,9 @@ require 'test_helper' class TutorialsControllerTest < ActionController::TestCase - # Replace this with your real tests. - test "the truth" do - assert true + test "should get intro" do + get :intro + assert_response :success end + end diff --git a/tryruby/test/performance/browsing_test.rb b/test/performance/browsing_test.rb similarity index 60% rename from tryruby/test/performance/browsing_test.rb rename to test/performance/browsing_test.rb index 4b60558..867fc8c 100644 --- a/tryruby/test/performance/browsing_test.rb +++ b/test/performance/browsing_test.rb @@ -1,8 +1,8 @@ require 'test_helper' -require 'performance_test_help' +require 'rails/performance_test_help' # Profiling results for each test method are written to tmp/performance. -class BrowsingTest < ActionController::PerformanceTest +class BrowsingTest < ActionDispatch::PerformanceTest def test_homepage get '/' end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..8bf1192 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,13 @@ +ENV["RAILS_ENV"] = "test" +require File.expand_path('../../config/environment', __FILE__) +require 'rails/test_help' + +class ActiveSupport::TestCase + # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. + # + # Note: You'll currently still have to declare fixtures explicitly in integration tests + # -- they do not yet inherit this setting + fixtures :all + + # Add more helper methods to be used by all tests here... +end diff --git a/test/unit/helpers/index_helper_test.rb b/test/unit/helpers/index_helper_test.rb new file mode 100644 index 0000000..104f529 --- /dev/null +++ b/test/unit/helpers/index_helper_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class IndexHelperTest < ActionView::TestCase +end diff --git a/test/unit/helpers/tutorials_helper_test.rb b/test/unit/helpers/tutorials_helper_test.rb new file mode 100644 index 0000000..dae016c --- /dev/null +++ b/test/unit/helpers/tutorials_helper_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class TutorialsHelperTest < ActionView::TestCase +end diff --git a/tmp/pids/server.pid b/tmp/pids/server.pid new file mode 100644 index 0000000..9738fa5 --- /dev/null +++ b/tmp/pids/server.pid @@ -0,0 +1 @@ +47518 \ No newline at end of file diff --git a/tryruby/Gemfile b/tryruby/Gemfile deleted file mode 100644 index 649c6bb..0000000 --- a/tryruby/Gemfile +++ /dev/null @@ -1,16 +0,0 @@ -source :rubygems - -gem "rails", '2.3.5' - -# Specify your favourite web server (only one). -#gem 'unicorn', :group => :development -#gem 'mongrel', :group => :development - -gem 'ruby_parser' - -group :test do - gem 'test-unit', '2.1.1' - gem 'fakefs', '0.2.1', :git => "http://github.com/defunkt/fakefs.git", :ref => "aa0cb96b8ebc81287a2e", :require => 'fakefs/safe' - gem 'hpricot' - gem 'ruby-debug19' -end \ No newline at end of file diff --git a/tryruby/README b/tryruby/README deleted file mode 100644 index 79bc20e..0000000 --- a/tryruby/README +++ /dev/null @@ -1,21 +0,0 @@ -=README - -This is a highly experimental version of tryruby -I realize putting try ruby is rails is going to be controversial. -My origional idea was to use sinatra. Maybe I will use both. - -What is driving this decision is: -* the desire to move away from explictly needing to state where the ruby interpreter is -* an admission that I need to become rack based, rails is a cheap way out -* more people know rails within the ruby world then not (sorry its true) -* I need to be able to extend this eaiser, if I tried to add even 30 lessons in its current form, it would not be as fun as inside rails -* rspec-rails I understand way better than rspec some random script. - -so why am I not using camping, simple, I am not sure how maintained camping is. -What about merb? I admit, I don't know merb and with rails 3.0 won't I be getting the benefits of merb anyway? - -I will contact everyone who has a fork of this project to notify them of if and when I plan on making the architectual switch before hand. - -Thanks. - -In the meanwhile, ignore this branch. diff --git a/tryruby/app/controllers/tryruby_controller.rb b/tryruby/app/controllers/tryruby_controller.rb deleted file mode 100644 index 88f6513..0000000 --- a/tryruby/app/controllers/tryruby_controller.rb +++ /dev/null @@ -1,23 +0,0 @@ -require './lib/tryruby' -class TryrubyController < ApplicationController - layout 'tryruby' - attr_accessor :past_commands, :current_statement, :start_time - - def run - render :text => run_script(params[:cmd]) - end - - private - - def run_script(command) - # output = begin - # eval(command) - # rescue StandardError => e - # e.message + ". On the " - - TryRuby.run_line(command).format - # end - - # return "=> #{output}" + ", says yoda" - end -end diff --git a/tryruby/app/helpers/application_helper.rb b/tryruby/app/helpers/application_helper.rb deleted file mode 100644 index 22a7940..0000000 --- a/tryruby/app/helpers/application_helper.rb +++ /dev/null @@ -1,3 +0,0 @@ -# Methods added to this helper will be available to all templates in the application. -module ApplicationHelper -end diff --git a/tryruby/app/views/tryruby/index.rhtml b/tryruby/app/views/tryruby/index.rhtml deleted file mode 100644 index 091d348..0000000 --- a/tryruby/app/views/tryruby/index.rhtml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tryruby/config/boot.rb b/tryruby/config/boot.rb deleted file mode 100644 index 9ee835a..0000000 --- a/tryruby/config/boot.rb +++ /dev/null @@ -1,124 +0,0 @@ -# Don't change this file! -# Configure your app in config/environment.rb and config/environments/*.rb - -RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) - -module Rails - class << self - def boot! - unless booted? - preinitialize - pick_boot.run - end - end - - def booted? - defined? Rails::Initializer - end - - def pick_boot - (vendor_rails? ? VendorBoot : GemBoot).new - end - - def vendor_rails? - File.exist?("#{RAILS_ROOT}/vendor/rails") - end - - def preinitialize - load(preinitializer_path) if File.exist?(preinitializer_path) - end - - def preinitializer_path - "#{RAILS_ROOT}/config/preinitializer.rb" - end - end - - class Boot - def run - load_initializer - Rails::Initializer.run(:set_load_path) - end - end - - class VendorBoot < Boot - def load_initializer - require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" - Rails::Initializer.run(:install_gem_spec_stubs) - Rails::GemDependency.add_frozen_gem_path - end - end - - class GemBoot < Boot - def load_initializer - self.class.load_rubygems - load_rails_gem - require 'initializer' - end - - def load_rails_gem - if version = self.class.gem_version - gem 'rails', version - else - gem 'rails' - end - rescue Gem::LoadError => load_error - $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) - exit 1 - end - - class << self - def rubygems_version - Gem::RubyGemsVersion rescue nil - end - - def gem_version - if defined? RAILS_GEM_VERSION - RAILS_GEM_VERSION - elsif ENV.include?('RAILS_GEM_VERSION') - ENV['RAILS_GEM_VERSION'] - else - parse_gem_version(read_environment_rb) - end - end - - def load_rubygems - min_version = '1.3.2' - require 'rubygems' - unless rubygems_version >= min_version - $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) - exit 1 - end - - rescue LoadError - $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) - exit 1 - end - - def parse_gem_version(text) - $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ - end - - private - def read_environment_rb - File.read("#{RAILS_ROOT}/config/environment.rb") - end - end - end -end - -class Rails::Boot - def run - load_initializer - - Rails::Initializer.class_eval do - def load_gems - @bundler_loaded ||= Bundler.require :default, Rails.env - end - end - - Rails::Initializer.run(:set_load_path) - end -end - -# All that for this: -Rails.boot! diff --git a/tryruby/config/database.yml b/tryruby/config/database.yml deleted file mode 100644 index 854cff5..0000000 --- a/tryruby/config/database.yml +++ /dev/null @@ -1,20 +0,0 @@ -# SQLite version 3.x -# gem install sqlite3-ruby (not necessary on OS X Leopard) -development: - - - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - adapter: sqlite3 - database: db/test.sqlite3 - pool: 5 - timeout: 5000 - -production: - adapter: sqlite3 - database: db/production.sqlite3 - pool: 5 - timeout: 5000 diff --git a/tryruby/config/environment.rb b/tryruby/config/environment.rb deleted file mode 100644 index dec1ee9..0000000 --- a/tryruby/config/environment.rb +++ /dev/null @@ -1,45 +0,0 @@ -# Be sure to restart your server when you modify this file - -# Specifies gem version of Rails to use when vendor/rails is not present -RAILS_GEM_VERSION = '2.3.5' unless defined? RAILS_GEM_VERSION - -# Bootstrap the Rails environment, frameworks, and default configuration -require File.join(File.dirname(__FILE__), 'boot') - -Rails::Initializer.run do |config| - config.frameworks -= [:active_record] - # Settings in config/environments/* take precedence over those specified here. - # Application configuration should go into files in config/initializers - # -- all .rb files in that directory are automatically loaded. - - # Add additional load paths for your own custom dirs - # config.load_paths += %W( #{RAILS_ROOT}/extras ) - - # Specify gems that this application depends on and have them installed with rake gems:install - # config.gem "bj" - # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net" - # config.gem "sqlite3-ruby", :lib => "sqlite3" - # config.gem "aws-s3", :lib => "aws/s3" - - # Only load the plugins named here, in the order given (default is alphabetical). - # :all can be used as a placeholder for all plugins not explicitly named - # config.plugins = [ :exception_notification, :ssl_requirement, :all ] - - # Skip frameworks you're not going to use. To use Rails without a database, - # you must remove the Active Record framework. - # config.frameworks -= [ :active_record, :active_resource, :action_mailer ] - - # Activate observers that should always be running - # config.active_record.observers = :cacher, :garbage_collector, :forum_observer - - # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. - # Run "rake -D time" for a list of tasks for finding time zone names. - config.time_zone = 'UTC' - - # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. - # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')] - # config.i18n.default_locale = :de -end - -#equire 'irb' -#require 'tryruby_runner' diff --git a/tryruby/config/environments/development.rb b/tryruby/config/environments/development.rb deleted file mode 100644 index 85c9a60..0000000 --- a/tryruby/config/environments/development.rb +++ /dev/null @@ -1,17 +0,0 @@ -# Settings specified here will take precedence over those in config/environment.rb - -# In the development environment your application's code is reloaded on -# every request. This slows down response time but is perfect for development -# since you don't have to restart the webserver when you make code changes. -config.cache_classes = false - -# Log error messages when you accidentally call methods on nil. -config.whiny_nils = true - -# Show full error reports and disable caching -config.action_controller.consider_all_requests_local = true -config.action_view.debug_rjs = true -config.action_controller.perform_caching = false - -# Don't care if the mailer can't send -config.action_mailer.raise_delivery_errors = false \ No newline at end of file diff --git a/tryruby/config/environments/production.rb b/tryruby/config/environments/production.rb deleted file mode 100644 index 27119d2..0000000 --- a/tryruby/config/environments/production.rb +++ /dev/null @@ -1,28 +0,0 @@ -# Settings specified here will take precedence over those in config/environment.rb - -# The production environment is meant for finished, "live" apps. -# Code is not reloaded between requests -config.cache_classes = true - -# Full error reports are disabled and caching is turned on -config.action_controller.consider_all_requests_local = false -config.action_controller.perform_caching = true -config.action_view.cache_template_loading = true - -# See everything in the log (default is :info) -# config.log_level = :debug - -# Use a different logger for distributed setups -# config.logger = SyslogLogger.new - -# Use a different cache store in production -# config.cache_store = :mem_cache_store - -# Enable serving of images, stylesheets, and javascripts from an asset server -# config.action_controller.asset_host = "http://assets.example.com" - -# Disable delivery errors, bad email addresses will be ignored -# config.action_mailer.raise_delivery_errors = false - -# Enable threaded mode -# config.threadsafe! \ No newline at end of file diff --git a/tryruby/config/environments/test.rb b/tryruby/config/environments/test.rb deleted file mode 100644 index d6f80a4..0000000 --- a/tryruby/config/environments/test.rb +++ /dev/null @@ -1,28 +0,0 @@ -# Settings specified here will take precedence over those in config/environment.rb - -# The test environment is used exclusively to run your application's -# test suite. You never need to work with it otherwise. Remember that -# your test database is "scratch space" for the test suite and is wiped -# and recreated between test runs. Don't rely on the data there! -config.cache_classes = true - -# Log error messages when you accidentally call methods on nil. -config.whiny_nils = true - -# Show full error reports and disable caching -config.action_controller.consider_all_requests_local = true -config.action_controller.perform_caching = false -config.action_view.cache_template_loading = true - -# Disable request forgery protection in test environment -config.action_controller.allow_forgery_protection = false - -# Tell Action Mailer not to deliver emails to the real world. -# The :test delivery method accumulates sent emails in the -# ActionMailer::Base.deliveries array. -config.action_mailer.delivery_method = :test - -# Use SQL instead of Active Record's schema dumper when creating the test database. -# This is necessary if your schema can't be completely dumped by the schema dumper, -# like if you have constraints or database-specific column types -# config.active_record.schema_format = :sql \ No newline at end of file diff --git a/tryruby/config/initializers/new_rails_defaults.rb b/tryruby/config/initializers/new_rails_defaults.rb deleted file mode 100644 index c94db0a..0000000 --- a/tryruby/config/initializers/new_rails_defaults.rb +++ /dev/null @@ -1,21 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# These settings change the behavior of Rails 2 apps and will be defaults -# for Rails 3. You can remove this initializer when Rails 3 is released. - -if defined?(ActiveRecord) - # Include Active Record class name as root for JSON serialized output. - ActiveRecord::Base.include_root_in_json = true - - # Store the full class name (including module namespace) in STI type column. - ActiveRecord::Base.store_full_sti_class = true -end - -ActionController::Routing.generate_best_match = false - -# Use ISO 8601 format for JSON serialized times and dates. -ActiveSupport.use_standard_json_time_format = true - -# Don't escape HTML entities in JSON, leave that for the #json_escape helper. -# if you're including raw json in an HTML page. -ActiveSupport.escape_html_entities_in_json = false \ No newline at end of file diff --git a/tryruby/config/initializers/session_store.rb b/tryruby/config/initializers/session_store.rb deleted file mode 100644 index b8817bb..0000000 --- a/tryruby/config/initializers/session_store.rb +++ /dev/null @@ -1,15 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Your secret key for verifying cookie session data integrity. -# If you change this key, all old sessions will become invalid! -# Make sure the secret is at least 30 characters and all random, -# no regular words or you'll be exposed to dictionary attacks. -ActionController::Base.session = { - :key => '_tryruby_session', - :secret => '522523ceaba8ca180200d1931128947224663751e03dc442c9cba14d758b80fe166eb4bb2c0b2521a78152e0ae5c3e29237bc826aadb55b8182b1b293af75163' -} - -# Use the database for sessions instead of the cookie-based default, -# which shouldn't be used to store highly confidential information -# (create the session table with "rake db:sessions:create") -# ActionController::Base.session_store = :active_record_store diff --git a/tryruby/config/preinitializer.rb b/tryruby/config/preinitializer.rb deleted file mode 100644 index be50356..0000000 --- a/tryruby/config/preinitializer.rb +++ /dev/null @@ -1,20 +0,0 @@ -begin - require "rubygems" - require "bundler" -rescue LoadError - raise "Could not load the bundler gem. Install it with `gem install bundler`." -end - -if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.24") - raise RuntimeError, "Your bundler version is too old." + - "Run `gem install bundler` to upgrade." -end - -begin - # Set up load paths for all bundled gems - ENV["BUNDLE_GEMFILE"] = File.expand_path("../../Gemfile", __FILE__) - Bundler.setup -rescue Bundler::GemNotFound - raise RuntimeError, "Bundler couldn't find some gems." + - "Did you run `bundle install`?" -end diff --git a/tryruby/config/routes.rb b/tryruby/config/routes.rb deleted file mode 100644 index cc9cbdb..0000000 --- a/tryruby/config/routes.rb +++ /dev/null @@ -1,7 +0,0 @@ -ActionController::Routing::Routes.draw do |map| - # You can have the root of your site routed with map.root -- just remember to delete public/index.html. - map.root :controller => "tryruby" - - map.connect ':controller/:action/:id' - map.connect ':controller/:action/:id.:format' -end diff --git a/tryruby/doc/tryruby.rb b/tryruby/doc/tryruby.rb deleted file mode 100644 index b1390aa..0000000 --- a/tryruby/doc/tryruby.rb +++ /dev/null @@ -1,174 +0,0 @@ -$LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'submodules', 'fakefs', 'lib') -require 'setup.rb' -require 'ruby_parser' -require 'fakefs/safe' -require 'stringio' - -module TryRuby - extend self - - class Session - attr_accessor :past_commands, :current_statement, :start_time - def initialize - @past_commands = '' - @current_statement = '' - @start_time = Time.now - end - end - - class Output - attr_reader :type, :result, :output, :error, :indent_level, :javascript - - def self.standard(params = {}) - Output.new type: :standard, result: params[:result], - output: params[:output] || '' - end - - def self.illegal - Output.new type: :illegal - end - - def self.javascript(js) - Output.new type: :javascript, javascript: js - end - - def self.no_output - Output.standard result: nil - end - - def self.line_continuation(level) - Output.new type: :line_continuation, indent_level: level - end - - def self.error(params = {}) - params[:error] ||= StandardError.new('TryRuby Error') - params[:error].message.gsub! /\(eval\):\d*/, '(TryRuby):1' - Output.new type: :error, error: params[:error], - output: params[:output] || '' - end - - def format - case @type - when :line_continuation - ".." * @indent_level - when :error - @output + "\033[1;33m#{@error.class}: #{@error.message}" - when :illegal - "\033[1;33mYou aren't allowed to run that command!" - when :javascript - "\033[1;JSm#{@javascript}\033[m " - else - @output + "=> \033[1;20m#{@result.inspect}" - end - end - - protected - def initialize(values = {}) - values.each do |variable, value| - instance_variable_set("@#{variable}", value) - end - end - end - - - class << self - attr_accessor :session - TryRuby.session = TryRuby::Session.new - end - - def calculate_nesting_level(statement) - begin - RubyParser.new.parse(statement) - 0 - rescue Racc::ParseError => e - case e.message - when /parse error on value \"\$end\" \(\$end\)/ then - new_statement = statement + "\n end" - begin - RubyParser.new.parse(new_statement) - return 1 - rescue Racc::ParseError => e - if e.message =~ /parse error on value \"end\" \(kEND\)/ then - new_statement = statement + "\n }" - end - end - begin - 1 + calculate_nesting_level(new_statement) - rescue Racc::ParseError => e - return 1 - end - else - raise e - end - end - end - - def run_line(code) - case code.strip - when '!INIT!IRB!' - return Output.no_output - when 'reset' - TryRuby.session.current_statement = '' - return Output.no_output - when 'time' - seconds = (Time.now - session.start_time).ceil - return Output.standard result: - if seconds < 60; "#{seconds} seconds" - elsif seconds < 120; "1 minute" - else; "#{seconds / 60} minutes" - end - end - - # nesting level - level = begin - calculate_nesting_level(session.current_statement + "\n" + code) - rescue Racc::ParseError, SyntaxError - 0 - end - if level > 0 - session.current_statement += "\n" + code - return Output.line_continuation(level) - end - - # run something - FakeFS.activate! - stdout_id = $stdout.to_i - $stdout = StringIO.new - cmd = <<-EOF - #{SetupCode} - $SAFE = 3 - #{session.past_commands} - $stdout = StringIO.new - begin - #{session.current_statement} - #{code} - end - EOF - begin - result = Thread.new { eval cmd, TOPLEVEL_BINDING }.value - rescue SecurityError - return Output.illegal - rescue Exception => e - return Output.error :error => e, :output => get_stdout - ensure - output = get_stdout - $stdout = IO.new(stdout_id) - FakeFS.deactivate! - end - - session.current_statement += "\n" + code - session.past_commands += "\n" + session.current_statement.strip - session.current_statement = '' - - return result if result.is_a? Output and result.type == :javascript - Output.standard result: result, output: output - end - - private - def get_stdout - raise TypeError, "$stdout is a #{$stdout.class}" unless $stdout.is_a? StringIO - $stdout.rewind - $stdout.read - end - -end \ No newline at end of file diff --git a/tryruby/lib/tryruby.rb b/tryruby/lib/tryruby.rb deleted file mode 100644 index e6933a8..0000000 --- a/tryruby/lib/tryruby.rb +++ /dev/null @@ -1,279 +0,0 @@ -require 'stringio' -require 'popup.rb' -require 'setup.rb' -require 'fakefs/safe' - -module FakeFS - - class Dir - def self.entries(dirname) - FileSystem.fs - raise SystemCallError, dirname unless FileSystem.find(dirname) - Dir.new(dirname).map { |file| File.basename(file) } - end - end - - module FileSystem - def fs - @fs ||= FakeDir.new("/") - end - - def normalize_path(path) - if Pathname.new(path).absolute? - File.expand_path(path) - else - parts = [""] + dir_levels + [path] - File.expand_path(File.join(*parts)) - end - end - - def current_dir - parts = FileSystem.dir_levels - return fs if parts.empty? # '/' - entries = find_recurser(fs, parts).flatten - - return case entries.length - when 0 then nil - when 1 then entries.first - else entries - end - end - end - - class FakeDir - def to_s - if parent && parent.to_s == '/' - File.join("", name) - elsif parent - File.join("", *parent.to_s.split(File::PATH_SEPARATOR).reject { |part| part.empty? }, name) - else - "/" - end - end - - end - - class FakeFile - - def to_s - if parent && parent.to_s == '/' - File.join("", name) - elsif parent - File.join("", *parent.to_s.split(File::PATH_SEPARATOR).reject { |part| part.empty? }, name) - else - name - end - end - - end - - class File - def inspect - "#" - end - - def self.foreach(path) - self.read(path).each_line {|line| yield(line) } - end - - def self.expand_path(*args) - file_name, dir_string = args - dir_string ||= FileSystem.current_dir.to_s - if file_name == "/" - return "/" - elsif (file_name.start_with?("/")) - abs_file_name = RealFile.join(file_name) - else - abs_file_name = RealFile.join(dir_string, file_name) - end - path_parts = abs_file_name.split(RealFile::Separator) - result_path_parts = [""] - path_parts.each do |part| - case part - when ".." then result_path_parts.pop - when "." then # ignore - else result_path_parts.push(part) - end - end - RealFile.join(*result_path_parts) - end - - end - - module FileUtils - def copy(src, dest) - cp(src, dest) - nil - end - end -end - -module TryRuby - extend self - - class Session - attr_accessor :past_commands, :current_statement, :start_time - def initialize - @past_commands = '' - @current_statement = '' - @start_time = Time.now - end - end - - class Output - attr_reader :type, :result, :output, :error, :indent_level, :javascript - - def self.standard(params = {}) - Output.new type: :standard, result: params[:result], - output: params[:output] || '' - end - - def self.illegal - Output.new type: :illegal - end - - def self.javascript(js) - Output.new type: :javascript, javascript: js - end - - def self.no_output - Output.standard result: nil - end - - def self.line_continuation(level) - Output.new type: :line_continuation, indent_level: level - end - - def self.error(params = {}) - params[:error] ||= StandardError.new('TryRuby Error') - params[:error].message.gsub! /\(eval\):\d*/, '(TryRuby):1' - Output.new type: :error, error: params[:error], - output: params[:output] || '' - end - - def format - case @type - when :line_continuation - ".." * @indent_level - when :error - @output + "\033[1;33m#{@error.class}: #{@error.message}" - when :illegal - "\033[1;33mYou aren't allowed to run that command!" - when :javascript - "\033[1;JSm#{@javascript}\033[m " - else - @output + "=> \033[1;20m#{@result.inspect}" - end - end - - protected - def initialize(values = {}) - values.each do |variable, value| - instance_variable_set("@#{variable}", value) - end - end - end - - - class << self - attr_accessor :session - TryRuby.session = TryRuby::Session.new - end - - def calculate_nesting_level(statement) - begin - RubyParser.new.parse(statement) - 0 - rescue Racc::ParseError => e - case e.message - when /parse error on value \"\$end\" \(\$end\)/ then - new_statement = statement + "\n end" - begin - RubyParser.new.parse(new_statement) - return 1 - rescue Racc::ParseError => e - if e.message =~ /parse error on value \"end\" \(kEND\)/ then - new_statement = statement + "\n }" - end - end - begin - 1 + calculate_nesting_level(new_statement) - rescue Racc::ParseError => e - return 1 - end - else - raise e - end - end - end - - def run_line(code) - case code.strip - when '!INIT!IRB!' - return Output.no_output - when 'reset' - TryRuby.session.current_statement = '' - return Output.no_output - when 'time' - seconds = (Time.now - session.start_time).ceil - return Output.standard result: - if seconds < 60; "#{seconds} seconds" - elsif seconds < 120; "1 minute" - else; "#{seconds / 60} minutes" - end - end - - # nesting level - level = begin - calculate_nesting_level(session.current_statement + "\n" + code) - rescue Racc::ParseError, SyntaxError - 0 - end - if level > 0 - session.current_statement += "\n" + code - return Output.line_continuation(level) - end - - # run something - FakeFS.activate! - stdout_id = $stdout.to_i - $stdout = StringIO.new - cmd = <<-EOF - #{SetupCode} - $SAFE = 3 - #{session.past_commands} - $stdout = StringIO.new - begin - #{session.current_statement} - #{code} - end - EOF - begin - result = Thread.new { eval cmd, TOPLEVEL_BINDING }.value - rescue SecurityError => e - puts e - return Output.illegal - rescue Exception => e - return Output.error :error => e, :output => get_stdout - ensure - output = get_stdout - $stdout = IO.new(stdout_id) - FakeFS.deactivate! - end - - session.current_statement += "\n" + code - session.past_commands += "\n" + session.current_statement.strip - session.current_statement = '' - - return result if result.is_a? Output and result.type == :javascript - Output.standard result: result, output: output - end - - private - def get_stdout - raise TypeError, "$stdout is a #{$stdout.class}" unless $stdout.is_a? StringIO - $stdout.rewind - $stdout.read - end - -end \ No newline at end of file diff --git a/tryruby/public/404.html b/tryruby/public/404.html deleted file mode 100644 index eff660b..0000000 --- a/tryruby/public/404.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - The page you were looking for doesn't exist (404) - - - - - -
    -

    The page you were looking for doesn't exist.

    -

    You may have mistyped the address or the page may have moved.

    -
    - - \ No newline at end of file diff --git a/tryruby/public/422.html b/tryruby/public/422.html deleted file mode 100644 index b54e4a3..0000000 --- a/tryruby/public/422.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - The change you wanted was rejected (422) - - - - - -
    -

    The change you wanted was rejected.

    -

    Maybe you tried to change something you didn't have access to.

    -
    - - \ No newline at end of file diff --git a/tryruby/public/500.html b/tryruby/public/500.html deleted file mode 100644 index ec3bbf0..0000000 --- a/tryruby/public/500.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - We're sorry, but something went wrong (500) - - - - - -
    -

    We're sorry, but something went wrong.

    -

    We've been notified about this issue and we'll take a look at it shortly.

    -
    - - diff --git a/tryruby/public/blank.html b/tryruby/public/blank.html deleted file mode 100755 index 7879e1c..0000000 --- a/tryruby/public/blank.html +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/tryruby/public/images/header.png b/tryruby/public/images/header.png deleted file mode 100644 index 1a6a3f2..0000000 Binary files a/tryruby/public/images/header.png and /dev/null differ diff --git a/tryruby/public/images/sick.gif b/tryruby/public/images/sick.gif deleted file mode 100755 index 217a67c..0000000 Binary files a/tryruby/public/images/sick.gif and /dev/null differ diff --git a/tryruby/public/javascripts/prototype.js b/tryruby/public/javascripts/prototype.js deleted file mode 100644 index dfe8ab4..0000000 --- a/tryruby/public/javascripts/prototype.js +++ /dev/null @@ -1,4320 +0,0 @@ -/* Prototype JavaScript framework, version 1.6.0.3 - * (c) 2005-2008 Sam Stephenson - * - * Prototype is freely distributable under the terms of an MIT-style license. - * For details, see the Prototype web site: http://www.prototypejs.org/ - * - *--------------------------------------------------------------------------*/ - -var Prototype = { - Version: '1.6.0.3', - - Browser: { - IE: !!(window.attachEvent && - navigator.userAgent.indexOf('Opera') === -1), - Opera: navigator.userAgent.indexOf('Opera') > -1, - WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, - Gecko: navigator.userAgent.indexOf('Gecko') > -1 && - navigator.userAgent.indexOf('KHTML') === -1, - MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) - }, - - BrowserFeatures: { - XPath: !!document.evaluate, - SelectorsAPI: !!document.querySelector, - ElementExtensions: !!window.HTMLElement, - SpecificElementExtensions: - document.createElement('div')['__proto__'] && - document.createElement('div')['__proto__'] !== - document.createElement('form')['__proto__'] - }, - - ScriptFragment: ']*>([\\S\\s]*?)<\/script>', - JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, - - emptyFunction: function() { }, - K: function(x) { return x } -}; - -if (Prototype.Browser.MobileSafari) - Prototype.BrowserFeatures.SpecificElementExtensions = false; - - -/* Based on Alex Arnell's inheritance implementation. */ -var Class = { - create: function() { - var parent = null, properties = $A(arguments); - if (Object.isFunction(properties[0])) - parent = properties.shift(); - - function klass() { - this.initialize.apply(this, arguments); - } - - Object.extend(klass, Class.Methods); - klass.superclass = parent; - klass.subclasses = []; - - if (parent) { - var subclass = function() { }; - subclass.prototype = parent.prototype; - klass.prototype = new subclass; - parent.subclasses.push(klass); - } - - for (var i = 0; i < properties.length; i++) - klass.addMethods(properties[i]); - - if (!klass.prototype.initialize) - klass.prototype.initialize = Prototype.emptyFunction; - - klass.prototype.constructor = klass; - - return klass; - } -}; - -Class.Methods = { - addMethods: function(source) { - var ancestor = this.superclass && this.superclass.prototype; - var properties = Object.keys(source); - - if (!Object.keys({ toString: true }).length) - properties.push("toString", "valueOf"); - - for (var i = 0, length = properties.length; i < length; i++) { - var property = properties[i], value = source[property]; - if (ancestor && Object.isFunction(value) && - value.argumentNames().first() == "$super") { - var method = value; - value = (function(m) { - return function() { return ancestor[m].apply(this, arguments) }; - })(property).wrap(method); - - value.valueOf = method.valueOf.bind(method); - value.toString = method.toString.bind(method); - } - this.prototype[property] = value; - } - - return this; - } -}; - -var Abstract = { }; - -Object.extend = function(destination, source) { - for (var property in source) - destination[property] = source[property]; - return destination; -}; - -Object.extend(Object, { - inspect: function(object) { - try { - if (Object.isUndefined(object)) return 'undefined'; - if (object === null) return 'null'; - return object.inspect ? object.inspect() : String(object); - } catch (e) { - if (e instanceof RangeError) return '...'; - throw e; - } - }, - - toJSON: function(object) { - var type = typeof object; - switch (type) { - case 'undefined': - case 'function': - case 'unknown': return; - case 'boolean': return object.toString(); - } - - if (object === null) return 'null'; - if (object.toJSON) return object.toJSON(); - if (Object.isElement(object)) return; - - var results = []; - for (var property in object) { - var value = Object.toJSON(object[property]); - if (!Object.isUndefined(value)) - results.push(property.toJSON() + ': ' + value); - } - - return '{' + results.join(', ') + '}'; - }, - - toQueryString: function(object) { - return $H(object).toQueryString(); - }, - - toHTML: function(object) { - return object && object.toHTML ? object.toHTML() : String.interpret(object); - }, - - keys: function(object) { - var keys = []; - for (var property in object) - keys.push(property); - return keys; - }, - - values: function(object) { - var values = []; - for (var property in object) - values.push(object[property]); - return values; - }, - - clone: function(object) { - return Object.extend({ }, object); - }, - - isElement: function(object) { - return !!(object && object.nodeType == 1); - }, - - isArray: function(object) { - return object != null && typeof object == "object" && - 'splice' in object && 'join' in object; - }, - - isHash: function(object) { - return object instanceof Hash; - }, - - isFunction: function(object) { - return typeof object == "function"; - }, - - isString: function(object) { - return typeof object == "string"; - }, - - isNumber: function(object) { - return typeof object == "number"; - }, - - isUndefined: function(object) { - return typeof object == "undefined"; - } -}); - -Object.extend(Function.prototype, { - argumentNames: function() { - var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1] - .replace(/\s+/g, '').split(','); - return names.length == 1 && !names[0] ? [] : names; - }, - - bind: function() { - if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; - var __method = this, args = $A(arguments), object = args.shift(); - return function() { - return __method.apply(object, args.concat($A(arguments))); - } - }, - - bindAsEventListener: function() { - var __method = this, args = $A(arguments), object = args.shift(); - return function(event) { - return __method.apply(object, [event || window.event].concat(args)); - } - }, - - curry: function() { - if (!arguments.length) return this; - var __method = this, args = $A(arguments); - return function() { - return __method.apply(this, args.concat($A(arguments))); - } - }, - - delay: function() { - var __method = this, args = $A(arguments), timeout = args.shift() * 1000; - return window.setTimeout(function() { - return __method.apply(__method, args); - }, timeout); - }, - - defer: function() { - var args = [0.01].concat($A(arguments)); - return this.delay.apply(this, args); - }, - - wrap: function(wrapper) { - var __method = this; - return function() { - return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); - } - }, - - methodize: function() { - if (this._methodized) return this._methodized; - var __method = this; - return this._methodized = function() { - return __method.apply(null, [this].concat($A(arguments))); - }; - } -}); - -Date.prototype.toJSON = function() { - return '"' + this.getUTCFullYear() + '-' + - (this.getUTCMonth() + 1).toPaddedString(2) + '-' + - this.getUTCDate().toPaddedString(2) + 'T' + - this.getUTCHours().toPaddedString(2) + ':' + - this.getUTCMinutes().toPaddedString(2) + ':' + - this.getUTCSeconds().toPaddedString(2) + 'Z"'; -}; - -var Try = { - these: function() { - var returnValue; - - for (var i = 0, length = arguments.length; i < length; i++) { - var lambda = arguments[i]; - try { - returnValue = lambda(); - break; - } catch (e) { } - } - - return returnValue; - } -}; - -RegExp.prototype.match = RegExp.prototype.test; - -RegExp.escape = function(str) { - return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); -}; - -/*--------------------------------------------------------------------------*/ - -var PeriodicalExecuter = Class.create({ - initialize: function(callback, frequency) { - this.callback = callback; - this.frequency = frequency; - this.currentlyExecuting = false; - - this.registerCallback(); - }, - - registerCallback: function() { - this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); - }, - - execute: function() { - this.callback(this); - }, - - stop: function() { - if (!this.timer) return; - clearInterval(this.timer); - this.timer = null; - }, - - onTimerEvent: function() { - if (!this.currentlyExecuting) { - try { - this.currentlyExecuting = true; - this.execute(); - } finally { - this.currentlyExecuting = false; - } - } - } -}); -Object.extend(String, { - interpret: function(value) { - return value == null ? '' : String(value); - }, - specialChar: { - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '\\': '\\\\' - } -}); - -Object.extend(String.prototype, { - gsub: function(pattern, replacement) { - var result = '', source = this, match; - replacement = arguments.callee.prepareReplacement(replacement); - - while (source.length > 0) { - if (match = source.match(pattern)) { - result += source.slice(0, match.index); - result += String.interpret(replacement(match)); - source = source.slice(match.index + match[0].length); - } else { - result += source, source = ''; - } - } - return result; - }, - - sub: function(pattern, replacement, count) { - replacement = this.gsub.prepareReplacement(replacement); - count = Object.isUndefined(count) ? 1 : count; - - return this.gsub(pattern, function(match) { - if (--count < 0) return match[0]; - return replacement(match); - }); - }, - - scan: function(pattern, iterator) { - this.gsub(pattern, iterator); - return String(this); - }, - - truncate: function(length, truncation) { - length = length || 30; - truncation = Object.isUndefined(truncation) ? '...' : truncation; - return this.length > length ? - this.slice(0, length - truncation.length) + truncation : String(this); - }, - - strip: function() { - return this.replace(/^\s+/, '').replace(/\s+$/, ''); - }, - - stripTags: function() { - return this.replace(/<\/?[^>]+>/gi, ''); - }, - - stripScripts: function() { - return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); - }, - - extractScripts: function() { - var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); - var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); - return (this.match(matchAll) || []).map(function(scriptTag) { - return (scriptTag.match(matchOne) || ['', ''])[1]; - }); - }, - - evalScripts: function() { - return this.extractScripts().map(function(script) { return eval(script) }); - }, - - escapeHTML: function() { - var self = arguments.callee; - self.text.data = this; - return self.div.innerHTML; - }, - - unescapeHTML: function() { - var div = new Element('div'); - div.innerHTML = this.stripTags(); - return div.childNodes[0] ? (div.childNodes.length > 1 ? - $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : - div.childNodes[0].nodeValue) : ''; - }, - - toQueryParams: function(separator) { - var match = this.strip().match(/([^?#]*)(#.*)?$/); - if (!match) return { }; - - return match[1].split(separator || '&').inject({ }, function(hash, pair) { - if ((pair = pair.split('='))[0]) { - var key = decodeURIComponent(pair.shift()); - var value = pair.length > 1 ? pair.join('=') : pair[0]; - if (value != undefined) value = decodeURIComponent(value); - - if (key in hash) { - if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; - hash[key].push(value); - } - else hash[key] = value; - } - return hash; - }); - }, - - toArray: function() { - return this.split(''); - }, - - succ: function() { - return this.slice(0, this.length - 1) + - String.fromCharCode(this.charCodeAt(this.length - 1) + 1); - }, - - times: function(count) { - return count < 1 ? '' : new Array(count + 1).join(this); - }, - - camelize: function() { - var parts = this.split('-'), len = parts.length; - if (len == 1) return parts[0]; - - var camelized = this.charAt(0) == '-' - ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) - : parts[0]; - - for (var i = 1; i < len; i++) - camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); - - return camelized; - }, - - capitalize: function() { - return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); - }, - - underscore: function() { - return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); - }, - - dasherize: function() { - return this.gsub(/_/,'-'); - }, - - inspect: function(useDoubleQuotes) { - var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { - var character = String.specialChar[match[0]]; - return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); - }); - if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; - return "'" + escapedString.replace(/'/g, '\\\'') + "'"; - }, - - toJSON: function() { - return this.inspect(true); - }, - - unfilterJSON: function(filter) { - return this.sub(filter || Prototype.JSONFilter, '#{1}'); - }, - - isJSON: function() { - var str = this; - if (str.blank()) return false; - str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); - return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); - }, - - evalJSON: function(sanitize) { - var json = this.unfilterJSON(); - try { - if (!sanitize || json.isJSON()) return eval('(' + json + ')'); - } catch (e) { } - throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); - }, - - include: function(pattern) { - return this.indexOf(pattern) > -1; - }, - - startsWith: function(pattern) { - return this.indexOf(pattern) === 0; - }, - - endsWith: function(pattern) { - var d = this.length - pattern.length; - return d >= 0 && this.lastIndexOf(pattern) === d; - }, - - empty: function() { - return this == ''; - }, - - blank: function() { - return /^\s*$/.test(this); - }, - - interpolate: function(object, pattern) { - return new Template(this, pattern).evaluate(object); - } -}); - -if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { - escapeHTML: function() { - return this.replace(/&/g,'&').replace(//g,'>'); - }, - unescapeHTML: function() { - return this.stripTags().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); - } -}); - -String.prototype.gsub.prepareReplacement = function(replacement) { - if (Object.isFunction(replacement)) return replacement; - var template = new Template(replacement); - return function(match) { return template.evaluate(match) }; -}; - -String.prototype.parseQuery = String.prototype.toQueryParams; - -Object.extend(String.prototype.escapeHTML, { - div: document.createElement('div'), - text: document.createTextNode('') -}); - -String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text); - -var Template = Class.create({ - initialize: function(template, pattern) { - this.template = template.toString(); - this.pattern = pattern || Template.Pattern; - }, - - evaluate: function(object) { - if (Object.isFunction(object.toTemplateReplacements)) - object = object.toTemplateReplacements(); - - return this.template.gsub(this.pattern, function(match) { - if (object == null) return ''; - - var before = match[1] || ''; - if (before == '\\') return match[2]; - - var ctx = object, expr = match[3]; - var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; - match = pattern.exec(expr); - if (match == null) return before; - - while (match != null) { - var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; - ctx = ctx[comp]; - if (null == ctx || '' == match[3]) break; - expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); - match = pattern.exec(expr); - } - - return before + String.interpret(ctx); - }); - } -}); -Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; - -var $break = { }; - -var Enumerable = { - each: function(iterator, context) { - var index = 0; - try { - this._each(function(value) { - iterator.call(context, value, index++); - }); - } catch (e) { - if (e != $break) throw e; - } - return this; - }, - - eachSlice: function(number, iterator, context) { - var index = -number, slices = [], array = this.toArray(); - if (number < 1) return array; - while ((index += number) < array.length) - slices.push(array.slice(index, index+number)); - return slices.collect(iterator, context); - }, - - all: function(iterator, context) { - iterator = iterator || Prototype.K; - var result = true; - this.each(function(value, index) { - result = result && !!iterator.call(context, value, index); - if (!result) throw $break; - }); - return result; - }, - - any: function(iterator, context) { - iterator = iterator || Prototype.K; - var result = false; - this.each(function(value, index) { - if (result = !!iterator.call(context, value, index)) - throw $break; - }); - return result; - }, - - collect: function(iterator, context) { - iterator = iterator || Prototype.K; - var results = []; - this.each(function(value, index) { - results.push(iterator.call(context, value, index)); - }); - return results; - }, - - detect: function(iterator, context) { - var result; - this.each(function(value, index) { - if (iterator.call(context, value, index)) { - result = value; - throw $break; - } - }); - return result; - }, - - findAll: function(iterator, context) { - var results = []; - this.each(function(value, index) { - if (iterator.call(context, value, index)) - results.push(value); - }); - return results; - }, - - grep: function(filter, iterator, context) { - iterator = iterator || Prototype.K; - var results = []; - - if (Object.isString(filter)) - filter = new RegExp(filter); - - this.each(function(value, index) { - if (filter.match(value)) - results.push(iterator.call(context, value, index)); - }); - return results; - }, - - include: function(object) { - if (Object.isFunction(this.indexOf)) - if (this.indexOf(object) != -1) return true; - - var found = false; - this.each(function(value) { - if (value == object) { - found = true; - throw $break; - } - }); - return found; - }, - - inGroupsOf: function(number, fillWith) { - fillWith = Object.isUndefined(fillWith) ? null : fillWith; - return this.eachSlice(number, function(slice) { - while(slice.length < number) slice.push(fillWith); - return slice; - }); - }, - - inject: function(memo, iterator, context) { - this.each(function(value, index) { - memo = iterator.call(context, memo, value, index); - }); - return memo; - }, - - invoke: function(method) { - var args = $A(arguments).slice(1); - return this.map(function(value) { - return value[method].apply(value, args); - }); - }, - - max: function(iterator, context) { - iterator = iterator || Prototype.K; - var result; - this.each(function(value, index) { - value = iterator.call(context, value, index); - if (result == null || value >= result) - result = value; - }); - return result; - }, - - min: function(iterator, context) { - iterator = iterator || Prototype.K; - var result; - this.each(function(value, index) { - value = iterator.call(context, value, index); - if (result == null || value < result) - result = value; - }); - return result; - }, - - partition: function(iterator, context) { - iterator = iterator || Prototype.K; - var trues = [], falses = []; - this.each(function(value, index) { - (iterator.call(context, value, index) ? - trues : falses).push(value); - }); - return [trues, falses]; - }, - - pluck: function(property) { - var results = []; - this.each(function(value) { - results.push(value[property]); - }); - return results; - }, - - reject: function(iterator, context) { - var results = []; - this.each(function(value, index) { - if (!iterator.call(context, value, index)) - results.push(value); - }); - return results; - }, - - sortBy: function(iterator, context) { - return this.map(function(value, index) { - return { - value: value, - criteria: iterator.call(context, value, index) - }; - }).sort(function(left, right) { - var a = left.criteria, b = right.criteria; - return a < b ? -1 : a > b ? 1 : 0; - }).pluck('value'); - }, - - toArray: function() { - return this.map(); - }, - - zip: function() { - var iterator = Prototype.K, args = $A(arguments); - if (Object.isFunction(args.last())) - iterator = args.pop(); - - var collections = [this].concat(args).map($A); - return this.map(function(value, index) { - return iterator(collections.pluck(index)); - }); - }, - - size: function() { - return this.toArray().length; - }, - - inspect: function() { - return '#'; - } -}; - -Object.extend(Enumerable, { - map: Enumerable.collect, - find: Enumerable.detect, - select: Enumerable.findAll, - filter: Enumerable.findAll, - member: Enumerable.include, - entries: Enumerable.toArray, - every: Enumerable.all, - some: Enumerable.any -}); -function $A(iterable) { - if (!iterable) return []; - if (iterable.toArray) return iterable.toArray(); - var length = iterable.length || 0, results = new Array(length); - while (length--) results[length] = iterable[length]; - return results; -} - -if (Prototype.Browser.WebKit) { - $A = function(iterable) { - if (!iterable) return []; - // In Safari, only use the `toArray` method if it's not a NodeList. - // A NodeList is a function, has an function `item` property, and a numeric - // `length` property. Adapted from Google Doctype. - if (!(typeof iterable === 'function' && typeof iterable.length === - 'number' && typeof iterable.item === 'function') && iterable.toArray) - return iterable.toArray(); - var length = iterable.length || 0, results = new Array(length); - while (length--) results[length] = iterable[length]; - return results; - }; -} - -Array.from = $A; - -Object.extend(Array.prototype, Enumerable); - -if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; - -Object.extend(Array.prototype, { - _each: function(iterator) { - for (var i = 0, length = this.length; i < length; i++) - iterator(this[i]); - }, - - clear: function() { - this.length = 0; - return this; - }, - - first: function() { - return this[0]; - }, - - last: function() { - return this[this.length - 1]; - }, - - compact: function() { - return this.select(function(value) { - return value != null; - }); - }, - - flatten: function() { - return this.inject([], function(array, value) { - return array.concat(Object.isArray(value) ? - value.flatten() : [value]); - }); - }, - - without: function() { - var values = $A(arguments); - return this.select(function(value) { - return !values.include(value); - }); - }, - - reverse: function(inline) { - return (inline !== false ? this : this.toArray())._reverse(); - }, - - reduce: function() { - return this.length > 1 ? this : this[0]; - }, - - uniq: function(sorted) { - return this.inject([], function(array, value, index) { - if (0 == index || (sorted ? array.last() != value : !array.include(value))) - array.push(value); - return array; - }); - }, - - intersect: function(array) { - return this.uniq().findAll(function(item) { - return array.detect(function(value) { return item === value }); - }); - }, - - clone: function() { - return [].concat(this); - }, - - size: function() { - return this.length; - }, - - inspect: function() { - return '[' + this.map(Object.inspect).join(', ') + ']'; - }, - - toJSON: function() { - var results = []; - this.each(function(object) { - var value = Object.toJSON(object); - if (!Object.isUndefined(value)) results.push(value); - }); - return '[' + results.join(', ') + ']'; - } -}); - -// use native browser JS 1.6 implementation if available -if (Object.isFunction(Array.prototype.forEach)) - Array.prototype._each = Array.prototype.forEach; - -if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { - i || (i = 0); - var length = this.length; - if (i < 0) i = length + i; - for (; i < length; i++) - if (this[i] === item) return i; - return -1; -}; - -if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { - i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; - var n = this.slice(0, i).reverse().indexOf(item); - return (n < 0) ? n : i - n - 1; -}; - -Array.prototype.toArray = Array.prototype.clone; - -function $w(string) { - if (!Object.isString(string)) return []; - string = string.strip(); - return string ? string.split(/\s+/) : []; -} - -if (Prototype.Browser.Opera){ - Array.prototype.concat = function() { - var array = []; - for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); - for (var i = 0, length = arguments.length; i < length; i++) { - if (Object.isArray(arguments[i])) { - for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) - array.push(arguments[i][j]); - } else { - array.push(arguments[i]); - } - } - return array; - }; -} -Object.extend(Number.prototype, { - toColorPart: function() { - return this.toPaddedString(2, 16); - }, - - succ: function() { - return this + 1; - }, - - times: function(iterator, context) { - $R(0, this, true).each(iterator, context); - return this; - }, - - toPaddedString: function(length, radix) { - var string = this.toString(radix || 10); - return '0'.times(length - string.length) + string; - }, - - toJSON: function() { - return isFinite(this) ? this.toString() : 'null'; - } -}); - -$w('abs round ceil floor').each(function(method){ - Number.prototype[method] = Math[method].methodize(); -}); -function $H(object) { - return new Hash(object); -}; - -var Hash = Class.create(Enumerable, (function() { - - function toQueryPair(key, value) { - if (Object.isUndefined(value)) return key; - return key + '=' + encodeURIComponent(String.interpret(value)); - } - - return { - initialize: function(object) { - this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); - }, - - _each: function(iterator) { - for (var key in this._object) { - var value = this._object[key], pair = [key, value]; - pair.key = key; - pair.value = value; - iterator(pair); - } - }, - - set: function(key, value) { - return this._object[key] = value; - }, - - get: function(key) { - // simulating poorly supported hasOwnProperty - if (this._object[key] !== Object.prototype[key]) - return this._object[key]; - }, - - unset: function(key) { - var value = this._object[key]; - delete this._object[key]; - return value; - }, - - toObject: function() { - return Object.clone(this._object); - }, - - keys: function() { - return this.pluck('key'); - }, - - values: function() { - return this.pluck('value'); - }, - - index: function(value) { - var match = this.detect(function(pair) { - return pair.value === value; - }); - return match && match.key; - }, - - merge: function(object) { - return this.clone().update(object); - }, - - update: function(object) { - return new Hash(object).inject(this, function(result, pair) { - result.set(pair.key, pair.value); - return result; - }); - }, - - toQueryString: function() { - return this.inject([], function(results, pair) { - var key = encodeURIComponent(pair.key), values = pair.value; - - if (values && typeof values == 'object') { - if (Object.isArray(values)) - return results.concat(values.map(toQueryPair.curry(key))); - } else results.push(toQueryPair(key, values)); - return results; - }).join('&'); - }, - - inspect: function() { - return '#'; - }, - - toJSON: function() { - return Object.toJSON(this.toObject()); - }, - - clone: function() { - return new Hash(this); - } - } -})()); - -Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; -Hash.from = $H; -var ObjectRange = Class.create(Enumerable, { - initialize: function(start, end, exclusive) { - this.start = start; - this.end = end; - this.exclusive = exclusive; - }, - - _each: function(iterator) { - var value = this.start; - while (this.include(value)) { - iterator(value); - value = value.succ(); - } - }, - - include: function(value) { - if (value < this.start) - return false; - if (this.exclusive) - return value < this.end; - return value <= this.end; - } -}); - -var $R = function(start, end, exclusive) { - return new ObjectRange(start, end, exclusive); -}; - -var Ajax = { - getTransport: function() { - return Try.these( - function() {return new XMLHttpRequest()}, - function() {return new ActiveXObject('Msxml2.XMLHTTP')}, - function() {return new ActiveXObject('Microsoft.XMLHTTP')} - ) || false; - }, - - activeRequestCount: 0 -}; - -Ajax.Responders = { - responders: [], - - _each: function(iterator) { - this.responders._each(iterator); - }, - - register: function(responder) { - if (!this.include(responder)) - this.responders.push(responder); - }, - - unregister: function(responder) { - this.responders = this.responders.without(responder); - }, - - dispatch: function(callback, request, transport, json) { - this.each(function(responder) { - if (Object.isFunction(responder[callback])) { - try { - responder[callback].apply(responder, [request, transport, json]); - } catch (e) { } - } - }); - } -}; - -Object.extend(Ajax.Responders, Enumerable); - -Ajax.Responders.register({ - onCreate: function() { Ajax.activeRequestCount++ }, - onComplete: function() { Ajax.activeRequestCount-- } -}); - -Ajax.Base = Class.create({ - initialize: function(options) { - this.options = { - method: 'post', - asynchronous: true, - contentType: 'application/x-www-form-urlencoded', - encoding: 'UTF-8', - parameters: '', - evalJSON: true, - evalJS: true - }; - Object.extend(this.options, options || { }); - - this.options.method = this.options.method.toLowerCase(); - - if (Object.isString(this.options.parameters)) - this.options.parameters = this.options.parameters.toQueryParams(); - else if (Object.isHash(this.options.parameters)) - this.options.parameters = this.options.parameters.toObject(); - } -}); - -Ajax.Request = Class.create(Ajax.Base, { - _complete: false, - - initialize: function($super, url, options) { - $super(options); - this.transport = Ajax.getTransport(); - this.request(url); - }, - - request: function(url) { - this.url = url; - this.method = this.options.method; - var params = Object.clone(this.options.parameters); - - if (!['get', 'post'].include(this.method)) { - // simulate other verbs over post - params['_method'] = this.method; - this.method = 'post'; - } - - this.parameters = params; - - if (params = Object.toQueryString(params)) { - // when GET, append parameters to URL - if (this.method == 'get') - this.url += (this.url.include('?') ? '&' : '?') + params; - else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) - params += '&_='; - } - - try { - var response = new Ajax.Response(this); - if (this.options.onCreate) this.options.onCreate(response); - Ajax.Responders.dispatch('onCreate', this, response); - - this.transport.open(this.method.toUpperCase(), this.url, - this.options.asynchronous); - - if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); - - this.transport.onreadystatechange = this.onStateChange.bind(this); - this.setRequestHeaders(); - - this.body = this.method == 'post' ? (this.options.postBody || params) : null; - this.transport.send(this.body); - - /* Force Firefox to handle ready state 4 for synchronous requests */ - if (!this.options.asynchronous && this.transport.overrideMimeType) - this.onStateChange(); - - } - catch (e) { - this.dispatchException(e); - } - }, - - onStateChange: function() { - var readyState = this.transport.readyState; - if (readyState > 1 && !((readyState == 4) && this._complete)) - this.respondToReadyState(this.transport.readyState); - }, - - setRequestHeaders: function() { - var headers = { - 'X-Requested-With': 'XMLHttpRequest', - 'X-Prototype-Version': Prototype.Version, - 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' - }; - - if (this.method == 'post') { - headers['Content-type'] = this.options.contentType + - (this.options.encoding ? '; charset=' + this.options.encoding : ''); - - /* Force "Connection: close" for older Mozilla browsers to work - * around a bug where XMLHttpRequest sends an incorrect - * Content-length header. See Mozilla Bugzilla #246651. - */ - if (this.transport.overrideMimeType && - (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) - headers['Connection'] = 'close'; - } - - // user-defined headers - if (typeof this.options.requestHeaders == 'object') { - var extras = this.options.requestHeaders; - - if (Object.isFunction(extras.push)) - for (var i = 0, length = extras.length; i < length; i += 2) - headers[extras[i]] = extras[i+1]; - else - $H(extras).each(function(pair) { headers[pair.key] = pair.value }); - } - - for (var name in headers) - this.transport.setRequestHeader(name, headers[name]); - }, - - success: function() { - var status = this.getStatus(); - return !status || (status >= 200 && status < 300); - }, - - getStatus: function() { - try { - return this.transport.status || 0; - } catch (e) { return 0 } - }, - - respondToReadyState: function(readyState) { - var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); - - if (state == 'Complete') { - try { - this._complete = true; - (this.options['on' + response.status] - || this.options['on' + (this.success() ? 'Success' : 'Failure')] - || Prototype.emptyFunction)(response, response.headerJSON); - } catch (e) { - this.dispatchException(e); - } - - var contentType = response.getHeader('Content-type'); - if (this.options.evalJS == 'force' - || (this.options.evalJS && this.isSameOrigin() && contentType - && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) - this.evalResponse(); - } - - try { - (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); - Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); - } catch (e) { - this.dispatchException(e); - } - - if (state == 'Complete') { - // avoid memory leak in MSIE: clean up - this.transport.onreadystatechange = Prototype.emptyFunction; - } - }, - - isSameOrigin: function() { - var m = this.url.match(/^\s*https?:\/\/[^\/]*/); - return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ - protocol: location.protocol, - domain: document.domain, - port: location.port ? ':' + location.port : '' - })); - }, - - getHeader: function(name) { - try { - return this.transport.getResponseHeader(name) || null; - } catch (e) { return null } - }, - - evalResponse: function() { - try { - return eval((this.transport.responseText || '').unfilterJSON()); - } catch (e) { - this.dispatchException(e); - } - }, - - dispatchException: function(exception) { - (this.options.onException || Prototype.emptyFunction)(this, exception); - Ajax.Responders.dispatch('onException', this, exception); - } -}); - -Ajax.Request.Events = - ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; - -Ajax.Response = Class.create({ - initialize: function(request){ - this.request = request; - var transport = this.transport = request.transport, - readyState = this.readyState = transport.readyState; - - if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { - this.status = this.getStatus(); - this.statusText = this.getStatusText(); - this.responseText = String.interpret(transport.responseText); - this.headerJSON = this._getHeaderJSON(); - } - - if(readyState == 4) { - var xml = transport.responseXML; - this.responseXML = Object.isUndefined(xml) ? null : xml; - this.responseJSON = this._getResponseJSON(); - } - }, - - status: 0, - statusText: '', - - getStatus: Ajax.Request.prototype.getStatus, - - getStatusText: function() { - try { - return this.transport.statusText || ''; - } catch (e) { return '' } - }, - - getHeader: Ajax.Request.prototype.getHeader, - - getAllHeaders: function() { - try { - return this.getAllResponseHeaders(); - } catch (e) { return null } - }, - - getResponseHeader: function(name) { - return this.transport.getResponseHeader(name); - }, - - getAllResponseHeaders: function() { - return this.transport.getAllResponseHeaders(); - }, - - _getHeaderJSON: function() { - var json = this.getHeader('X-JSON'); - if (!json) return null; - json = decodeURIComponent(escape(json)); - try { - return json.evalJSON(this.request.options.sanitizeJSON || - !this.request.isSameOrigin()); - } catch (e) { - this.request.dispatchException(e); - } - }, - - _getResponseJSON: function() { - var options = this.request.options; - if (!options.evalJSON || (options.evalJSON != 'force' && - !(this.getHeader('Content-type') || '').include('application/json')) || - this.responseText.blank()) - return null; - try { - return this.responseText.evalJSON(options.sanitizeJSON || - !this.request.isSameOrigin()); - } catch (e) { - this.request.dispatchException(e); - } - } -}); - -Ajax.Updater = Class.create(Ajax.Request, { - initialize: function($super, container, url, options) { - this.container = { - success: (container.success || container), - failure: (container.failure || (container.success ? null : container)) - }; - - options = Object.clone(options); - var onComplete = options.onComplete; - options.onComplete = (function(response, json) { - this.updateContent(response.responseText); - if (Object.isFunction(onComplete)) onComplete(response, json); - }).bind(this); - - $super(url, options); - }, - - updateContent: function(responseText) { - var receiver = this.container[this.success() ? 'success' : 'failure'], - options = this.options; - - if (!options.evalScripts) responseText = responseText.stripScripts(); - - if (receiver = $(receiver)) { - if (options.insertion) { - if (Object.isString(options.insertion)) { - var insertion = { }; insertion[options.insertion] = responseText; - receiver.insert(insertion); - } - else options.insertion(receiver, responseText); - } - else receiver.update(responseText); - } - } -}); - -Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { - initialize: function($super, container, url, options) { - $super(options); - this.onComplete = this.options.onComplete; - - this.frequency = (this.options.frequency || 2); - this.decay = (this.options.decay || 1); - - this.updater = { }; - this.container = container; - this.url = url; - - this.start(); - }, - - start: function() { - this.options.onComplete = this.updateComplete.bind(this); - this.onTimerEvent(); - }, - - stop: function() { - this.updater.options.onComplete = undefined; - clearTimeout(this.timer); - (this.onComplete || Prototype.emptyFunction).apply(this, arguments); - }, - - updateComplete: function(response) { - if (this.options.decay) { - this.decay = (response.responseText == this.lastText ? - this.decay * this.options.decay : 1); - - this.lastText = response.responseText; - } - this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); - }, - - onTimerEvent: function() { - this.updater = new Ajax.Updater(this.container, this.url, this.options); - } -}); -function $(element) { - if (arguments.length > 1) { - for (var i = 0, elements = [], length = arguments.length; i < length; i++) - elements.push($(arguments[i])); - return elements; - } - if (Object.isString(element)) - element = document.getElementById(element); - return Element.extend(element); -} - -if (Prototype.BrowserFeatures.XPath) { - document._getElementsByXPath = function(expression, parentElement) { - var results = []; - var query = document.evaluate(expression, $(parentElement) || document, - null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); - for (var i = 0, length = query.snapshotLength; i < length; i++) - results.push(Element.extend(query.snapshotItem(i))); - return results; - }; -} - -/*--------------------------------------------------------------------------*/ - -if (!window.Node) var Node = { }; - -if (!Node.ELEMENT_NODE) { - // DOM level 2 ECMAScript Language Binding - Object.extend(Node, { - ELEMENT_NODE: 1, - ATTRIBUTE_NODE: 2, - TEXT_NODE: 3, - CDATA_SECTION_NODE: 4, - ENTITY_REFERENCE_NODE: 5, - ENTITY_NODE: 6, - PROCESSING_INSTRUCTION_NODE: 7, - COMMENT_NODE: 8, - DOCUMENT_NODE: 9, - DOCUMENT_TYPE_NODE: 10, - DOCUMENT_FRAGMENT_NODE: 11, - NOTATION_NODE: 12 - }); -} - -(function() { - var element = this.Element; - this.Element = function(tagName, attributes) { - attributes = attributes || { }; - tagName = tagName.toLowerCase(); - var cache = Element.cache; - if (Prototype.Browser.IE && attributes.name) { - tagName = '<' + tagName + ' name="' + attributes.name + '">'; - delete attributes.name; - return Element.writeAttribute(document.createElement(tagName), attributes); - } - if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); - return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); - }; - Object.extend(this.Element, element || { }); - if (element) this.Element.prototype = element.prototype; -}).call(window); - -Element.cache = { }; - -Element.Methods = { - visible: function(element) { - return $(element).style.display != 'none'; - }, - - toggle: function(element) { - element = $(element); - Element[Element.visible(element) ? 'hide' : 'show'](element); - return element; - }, - - hide: function(element) { - element = $(element); - element.style.display = 'none'; - return element; - }, - - show: function(element) { - element = $(element); - element.style.display = ''; - return element; - }, - - remove: function(element) { - element = $(element); - element.parentNode.removeChild(element); - return element; - }, - - update: function(element, content) { - element = $(element); - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) return element.update().insert(content); - content = Object.toHTML(content); - element.innerHTML = content.stripScripts(); - content.evalScripts.bind(content).defer(); - return element; - }, - - replace: function(element, content) { - element = $(element); - if (content && content.toElement) content = content.toElement(); - else if (!Object.isElement(content)) { - content = Object.toHTML(content); - var range = element.ownerDocument.createRange(); - range.selectNode(element); - content.evalScripts.bind(content).defer(); - content = range.createContextualFragment(content.stripScripts()); - } - element.parentNode.replaceChild(content, element); - return element; - }, - - insert: function(element, insertions) { - element = $(element); - - if (Object.isString(insertions) || Object.isNumber(insertions) || - Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) - insertions = {bottom:insertions}; - - var content, insert, tagName, childNodes; - - for (var position in insertions) { - content = insertions[position]; - position = position.toLowerCase(); - insert = Element._insertionTranslations[position]; - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - insert(element, content); - continue; - } - - content = Object.toHTML(content); - - tagName = ((position == 'before' || position == 'after') - ? element.parentNode : element).tagName.toUpperCase(); - - childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - - if (position == 'top' || position == 'after') childNodes.reverse(); - childNodes.each(insert.curry(element)); - - content.evalScripts.bind(content).defer(); - } - - return element; - }, - - wrap: function(element, wrapper, attributes) { - element = $(element); - if (Object.isElement(wrapper)) - $(wrapper).writeAttribute(attributes || { }); - else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); - else wrapper = new Element('div', wrapper); - if (element.parentNode) - element.parentNode.replaceChild(wrapper, element); - wrapper.appendChild(element); - return wrapper; - }, - - inspect: function(element) { - element = $(element); - var result = '<' + element.tagName.toLowerCase(); - $H({'id': 'id', 'className': 'class'}).each(function(pair) { - var property = pair.first(), attribute = pair.last(); - var value = (element[property] || '').toString(); - if (value) result += ' ' + attribute + '=' + value.inspect(true); - }); - return result + '>'; - }, - - recursivelyCollect: function(element, property) { - element = $(element); - var elements = []; - while (element = element[property]) - if (element.nodeType == 1) - elements.push(Element.extend(element)); - return elements; - }, - - ancestors: function(element) { - return $(element).recursivelyCollect('parentNode'); - }, - - descendants: function(element) { - return $(element).select("*"); - }, - - firstDescendant: function(element) { - element = $(element).firstChild; - while (element && element.nodeType != 1) element = element.nextSibling; - return $(element); - }, - - immediateDescendants: function(element) { - if (!(element = $(element).firstChild)) return []; - while (element && element.nodeType != 1) element = element.nextSibling; - if (element) return [element].concat($(element).nextSiblings()); - return []; - }, - - previousSiblings: function(element) { - return $(element).recursivelyCollect('previousSibling'); - }, - - nextSiblings: function(element) { - return $(element).recursivelyCollect('nextSibling'); - }, - - siblings: function(element) { - element = $(element); - return element.previousSiblings().reverse().concat(element.nextSiblings()); - }, - - match: function(element, selector) { - if (Object.isString(selector)) - selector = new Selector(selector); - return selector.match($(element)); - }, - - up: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return $(element.parentNode); - var ancestors = element.ancestors(); - return Object.isNumber(expression) ? ancestors[expression] : - Selector.findElement(ancestors, expression, index); - }, - - down: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return element.firstDescendant(); - return Object.isNumber(expression) ? element.descendants()[expression] : - Element.select(element, expression)[index || 0]; - }, - - previous: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); - var previousSiblings = element.previousSiblings(); - return Object.isNumber(expression) ? previousSiblings[expression] : - Selector.findElement(previousSiblings, expression, index); - }, - - next: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); - var nextSiblings = element.nextSiblings(); - return Object.isNumber(expression) ? nextSiblings[expression] : - Selector.findElement(nextSiblings, expression, index); - }, - - select: function() { - var args = $A(arguments), element = $(args.shift()); - return Selector.findChildElements(element, args); - }, - - adjacent: function() { - var args = $A(arguments), element = $(args.shift()); - return Selector.findChildElements(element.parentNode, args).without(element); - }, - - identify: function(element) { - element = $(element); - var id = element.readAttribute('id'), self = arguments.callee; - if (id) return id; - do { id = 'anonymous_element_' + self.counter++ } while ($(id)); - element.writeAttribute('id', id); - return id; - }, - - readAttribute: function(element, name) { - element = $(element); - if (Prototype.Browser.IE) { - var t = Element._attributeTranslations.read; - if (t.values[name]) return t.values[name](element, name); - if (t.names[name]) name = t.names[name]; - if (name.include(':')) { - return (!element.attributes || !element.attributes[name]) ? null : - element.attributes[name].value; - } - } - return element.getAttribute(name); - }, - - writeAttribute: function(element, name, value) { - element = $(element); - var attributes = { }, t = Element._attributeTranslations.write; - - if (typeof name == 'object') attributes = name; - else attributes[name] = Object.isUndefined(value) ? true : value; - - for (var attr in attributes) { - name = t.names[attr] || attr; - value = attributes[attr]; - if (t.values[attr]) name = t.values[attr](element, value); - if (value === false || value === null) - element.removeAttribute(name); - else if (value === true) - element.setAttribute(name, name); - else element.setAttribute(name, value); - } - return element; - }, - - getHeight: function(element) { - return $(element).getDimensions().height; - }, - - getWidth: function(element) { - return $(element).getDimensions().width; - }, - - classNames: function(element) { - return new Element.ClassNames(element); - }, - - hasClassName: function(element, className) { - if (!(element = $(element))) return; - var elementClassName = element.className; - return (elementClassName.length > 0 && (elementClassName == className || - new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); - }, - - addClassName: function(element, className) { - if (!(element = $(element))) return; - if (!element.hasClassName(className)) - element.className += (element.className ? ' ' : '') + className; - return element; - }, - - removeClassName: function(element, className) { - if (!(element = $(element))) return; - element.className = element.className.replace( - new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); - return element; - }, - - toggleClassName: function(element, className) { - if (!(element = $(element))) return; - return element[element.hasClassName(className) ? - 'removeClassName' : 'addClassName'](className); - }, - - // removes whitespace-only text node children - cleanWhitespace: function(element) { - element = $(element); - var node = element.firstChild; - while (node) { - var nextNode = node.nextSibling; - if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) - element.removeChild(node); - node = nextNode; - } - return element; - }, - - empty: function(element) { - return $(element).innerHTML.blank(); - }, - - descendantOf: function(element, ancestor) { - element = $(element), ancestor = $(ancestor); - - if (element.compareDocumentPosition) - return (element.compareDocumentPosition(ancestor) & 8) === 8; - - if (ancestor.contains) - return ancestor.contains(element) && ancestor !== element; - - while (element = element.parentNode) - if (element == ancestor) return true; - - return false; - }, - - scrollTo: function(element) { - element = $(element); - var pos = element.cumulativeOffset(); - window.scrollTo(pos[0], pos[1]); - return element; - }, - - getStyle: function(element, style) { - element = $(element); - style = style == 'float' ? 'cssFloat' : style.camelize(); - var value = element.style[style]; - if (!value || value == 'auto') { - var css = document.defaultView.getComputedStyle(element, null); - value = css ? css[style] : null; - } - if (style == 'opacity') return value ? parseFloat(value) : 1.0; - return value == 'auto' ? null : value; - }, - - getOpacity: function(element) { - return $(element).getStyle('opacity'); - }, - - setStyle: function(element, styles) { - element = $(element); - var elementStyle = element.style, match; - if (Object.isString(styles)) { - element.style.cssText += ';' + styles; - return styles.include('opacity') ? - element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; - } - for (var property in styles) - if (property == 'opacity') element.setOpacity(styles[property]); - else - elementStyle[(property == 'float' || property == 'cssFloat') ? - (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : - property] = styles[property]; - - return element; - }, - - setOpacity: function(element, value) { - element = $(element); - element.style.opacity = (value == 1 || value === '') ? '' : - (value < 0.00001) ? 0 : value; - return element; - }, - - getDimensions: function(element) { - element = $(element); - var display = element.getStyle('display'); - if (display != 'none' && display != null) // Safari bug - return {width: element.offsetWidth, height: element.offsetHeight}; - - // All *Width and *Height properties give 0 on elements with display none, - // so enable the element temporarily - var els = element.style; - var originalVisibility = els.visibility; - var originalPosition = els.position; - var originalDisplay = els.display; - els.visibility = 'hidden'; - els.position = 'absolute'; - els.display = 'block'; - var originalWidth = element.clientWidth; - var originalHeight = element.clientHeight; - els.display = originalDisplay; - els.position = originalPosition; - els.visibility = originalVisibility; - return {width: originalWidth, height: originalHeight}; - }, - - makePositioned: function(element) { - element = $(element); - var pos = Element.getStyle(element, 'position'); - if (pos == 'static' || !pos) { - element._madePositioned = true; - element.style.position = 'relative'; - // Opera returns the offset relative to the positioning context, when an - // element is position relative but top and left have not been defined - if (Prototype.Browser.Opera) { - element.style.top = 0; - element.style.left = 0; - } - } - return element; - }, - - undoPositioned: function(element) { - element = $(element); - if (element._madePositioned) { - element._madePositioned = undefined; - element.style.position = - element.style.top = - element.style.left = - element.style.bottom = - element.style.right = ''; - } - return element; - }, - - makeClipping: function(element) { - element = $(element); - if (element._overflow) return element; - element._overflow = Element.getStyle(element, 'overflow') || 'auto'; - if (element._overflow !== 'hidden') - element.style.overflow = 'hidden'; - return element; - }, - - undoClipping: function(element) { - element = $(element); - if (!element._overflow) return element; - element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; - element._overflow = null; - return element; - }, - - cumulativeOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - positionedOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - if (element) { - if (element.tagName.toUpperCase() == 'BODY') break; - var p = Element.getStyle(element, 'position'); - if (p !== 'static') break; - } - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - absolutize: function(element) { - element = $(element); - if (element.getStyle('position') == 'absolute') return element; - // Position.prepare(); // To be done manually by Scripty when it needs it. - - var offsets = element.positionedOffset(); - var top = offsets[1]; - var left = offsets[0]; - var width = element.clientWidth; - var height = element.clientHeight; - - element._originalLeft = left - parseFloat(element.style.left || 0); - element._originalTop = top - parseFloat(element.style.top || 0); - element._originalWidth = element.style.width; - element._originalHeight = element.style.height; - - element.style.position = 'absolute'; - element.style.top = top + 'px'; - element.style.left = left + 'px'; - element.style.width = width + 'px'; - element.style.height = height + 'px'; - return element; - }, - - relativize: function(element) { - element = $(element); - if (element.getStyle('position') == 'relative') return element; - // Position.prepare(); // To be done manually by Scripty when it needs it. - - element.style.position = 'relative'; - var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); - var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); - - element.style.top = top + 'px'; - element.style.left = left + 'px'; - element.style.height = element._originalHeight; - element.style.width = element._originalWidth; - return element; - }, - - cumulativeScrollOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.scrollTop || 0; - valueL += element.scrollLeft || 0; - element = element.parentNode; - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - getOffsetParent: function(element) { - if (element.offsetParent) return $(element.offsetParent); - if (element == document.body) return $(element); - - while ((element = element.parentNode) && element != document.body) - if (Element.getStyle(element, 'position') != 'static') - return $(element); - - return $(document.body); - }, - - viewportOffset: function(forElement) { - var valueT = 0, valueL = 0; - - var element = forElement; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - - // Safari fix - if (element.offsetParent == document.body && - Element.getStyle(element, 'position') == 'absolute') break; - - } while (element = element.offsetParent); - - element = forElement; - do { - if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { - valueT -= element.scrollTop || 0; - valueL -= element.scrollLeft || 0; - } - } while (element = element.parentNode); - - return Element._returnOffset(valueL, valueT); - }, - - clonePosition: function(element, source) { - var options = Object.extend({ - setLeft: true, - setTop: true, - setWidth: true, - setHeight: true, - offsetTop: 0, - offsetLeft: 0 - }, arguments[2] || { }); - - // find page position of source - source = $(source); - var p = source.viewportOffset(); - - // find coordinate system to use - element = $(element); - var delta = [0, 0]; - var parent = null; - // delta [0,0] will do fine with position: fixed elements, - // position:absolute needs offsetParent deltas - if (Element.getStyle(element, 'position') == 'absolute') { - parent = element.getOffsetParent(); - delta = parent.viewportOffset(); - } - - // correct by body offsets (fixes Safari) - if (parent == document.body) { - delta[0] -= document.body.offsetLeft; - delta[1] -= document.body.offsetTop; - } - - // set position - if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; - if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; - if (options.setWidth) element.style.width = source.offsetWidth + 'px'; - if (options.setHeight) element.style.height = source.offsetHeight + 'px'; - return element; - } -}; - -Element.Methods.identify.counter = 1; - -Object.extend(Element.Methods, { - getElementsBySelector: Element.Methods.select, - childElements: Element.Methods.immediateDescendants -}); - -Element._attributeTranslations = { - write: { - names: { - className: 'class', - htmlFor: 'for' - }, - values: { } - } -}; - -if (Prototype.Browser.Opera) { - Element.Methods.getStyle = Element.Methods.getStyle.wrap( - function(proceed, element, style) { - switch (style) { - case 'left': case 'top': case 'right': case 'bottom': - if (proceed(element, 'position') === 'static') return null; - case 'height': case 'width': - // returns '0px' for hidden elements; we want it to return null - if (!Element.visible(element)) return null; - - // returns the border-box dimensions rather than the content-box - // dimensions, so we subtract padding and borders from the value - var dim = parseInt(proceed(element, style), 10); - - if (dim !== element['offset' + style.capitalize()]) - return dim + 'px'; - - var properties; - if (style === 'height') { - properties = ['border-top-width', 'padding-top', - 'padding-bottom', 'border-bottom-width']; - } - else { - properties = ['border-left-width', 'padding-left', - 'padding-right', 'border-right-width']; - } - return properties.inject(dim, function(memo, property) { - var val = proceed(element, property); - return val === null ? memo : memo - parseInt(val, 10); - }) + 'px'; - default: return proceed(element, style); - } - } - ); - - Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( - function(proceed, element, attribute) { - if (attribute === 'title') return element.title; - return proceed(element, attribute); - } - ); -} - -else if (Prototype.Browser.IE) { - // IE doesn't report offsets correctly for static elements, so we change them - // to "relative" to get the values, then change them back. - Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( - function(proceed, element) { - element = $(element); - // IE throws an error if element is not in document - try { element.offsetParent } - catch(e) { return $(document.body) } - var position = element.getStyle('position'); - if (position !== 'static') return proceed(element); - element.setStyle({ position: 'relative' }); - var value = proceed(element); - element.setStyle({ position: position }); - return value; - } - ); - - $w('positionedOffset viewportOffset').each(function(method) { - Element.Methods[method] = Element.Methods[method].wrap( - function(proceed, element) { - element = $(element); - try { element.offsetParent } - catch(e) { return Element._returnOffset(0,0) } - var position = element.getStyle('position'); - if (position !== 'static') return proceed(element); - // Trigger hasLayout on the offset parent so that IE6 reports - // accurate offsetTop and offsetLeft values for position: fixed. - var offsetParent = element.getOffsetParent(); - if (offsetParent && offsetParent.getStyle('position') === 'fixed') - offsetParent.setStyle({ zoom: 1 }); - element.setStyle({ position: 'relative' }); - var value = proceed(element); - element.setStyle({ position: position }); - return value; - } - ); - }); - - Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( - function(proceed, element) { - try { element.offsetParent } - catch(e) { return Element._returnOffset(0,0) } - return proceed(element); - } - ); - - Element.Methods.getStyle = function(element, style) { - element = $(element); - style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); - var value = element.style[style]; - if (!value && element.currentStyle) value = element.currentStyle[style]; - - if (style == 'opacity') { - if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) - if (value[1]) return parseFloat(value[1]) / 100; - return 1.0; - } - - if (value == 'auto') { - if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) - return element['offset' + style.capitalize()] + 'px'; - return null; - } - return value; - }; - - Element.Methods.setOpacity = function(element, value) { - function stripAlpha(filter){ - return filter.replace(/alpha\([^\)]*\)/gi,''); - } - element = $(element); - var currentStyle = element.currentStyle; - if ((currentStyle && !currentStyle.hasLayout) || - (!currentStyle && element.style.zoom == 'normal')) - element.style.zoom = 1; - - var filter = element.getStyle('filter'), style = element.style; - if (value == 1 || value === '') { - (filter = stripAlpha(filter)) ? - style.filter = filter : style.removeAttribute('filter'); - return element; - } else if (value < 0.00001) value = 0; - style.filter = stripAlpha(filter) + - 'alpha(opacity=' + (value * 100) + ')'; - return element; - }; - - Element._attributeTranslations = { - read: { - names: { - 'class': 'className', - 'for': 'htmlFor' - }, - values: { - _getAttr: function(element, attribute) { - return element.getAttribute(attribute, 2); - }, - _getAttrNode: function(element, attribute) { - var node = element.getAttributeNode(attribute); - return node ? node.value : ""; - }, - _getEv: function(element, attribute) { - attribute = element.getAttribute(attribute); - return attribute ? attribute.toString().slice(23, -2) : null; - }, - _flag: function(element, attribute) { - return $(element).hasAttribute(attribute) ? attribute : null; - }, - style: function(element) { - return element.style.cssText.toLowerCase(); - }, - title: function(element) { - return element.title; - } - } - } - }; - - Element._attributeTranslations.write = { - names: Object.extend({ - cellpadding: 'cellPadding', - cellspacing: 'cellSpacing' - }, Element._attributeTranslations.read.names), - values: { - checked: function(element, value) { - element.checked = !!value; - }, - - style: function(element, value) { - element.style.cssText = value ? value : ''; - } - } - }; - - Element._attributeTranslations.has = {}; - - $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + - 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { - Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; - Element._attributeTranslations.has[attr.toLowerCase()] = attr; - }); - - (function(v) { - Object.extend(v, { - href: v._getAttr, - src: v._getAttr, - type: v._getAttr, - action: v._getAttrNode, - disabled: v._flag, - checked: v._flag, - readonly: v._flag, - multiple: v._flag, - onload: v._getEv, - onunload: v._getEv, - onclick: v._getEv, - ondblclick: v._getEv, - onmousedown: v._getEv, - onmouseup: v._getEv, - onmouseover: v._getEv, - onmousemove: v._getEv, - onmouseout: v._getEv, - onfocus: v._getEv, - onblur: v._getEv, - onkeypress: v._getEv, - onkeydown: v._getEv, - onkeyup: v._getEv, - onsubmit: v._getEv, - onreset: v._getEv, - onselect: v._getEv, - onchange: v._getEv - }); - })(Element._attributeTranslations.read.values); -} - -else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { - Element.Methods.setOpacity = function(element, value) { - element = $(element); - element.style.opacity = (value == 1) ? 0.999999 : - (value === '') ? '' : (value < 0.00001) ? 0 : value; - return element; - }; -} - -else if (Prototype.Browser.WebKit) { - Element.Methods.setOpacity = function(element, value) { - element = $(element); - element.style.opacity = (value == 1 || value === '') ? '' : - (value < 0.00001) ? 0 : value; - - if (value == 1) - if(element.tagName.toUpperCase() == 'IMG' && element.width) { - element.width++; element.width--; - } else try { - var n = document.createTextNode(' '); - element.appendChild(n); - element.removeChild(n); - } catch (e) { } - - return element; - }; - - // Safari returns margins on body which is incorrect if the child is absolutely - // positioned. For performance reasons, redefine Element#cumulativeOffset for - // KHTML/WebKit only. - Element.Methods.cumulativeOffset = function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - if (element.offsetParent == document.body) - if (Element.getStyle(element, 'position') == 'absolute') break; - - element = element.offsetParent; - } while (element); - - return Element._returnOffset(valueL, valueT); - }; -} - -if (Prototype.Browser.IE || Prototype.Browser.Opera) { - // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements - Element.Methods.update = function(element, content) { - element = $(element); - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) return element.update().insert(content); - - content = Object.toHTML(content); - var tagName = element.tagName.toUpperCase(); - - if (tagName in Element._insertionTranslations.tags) { - $A(element.childNodes).each(function(node) { element.removeChild(node) }); - Element._getContentFromAnonymousElement(tagName, content.stripScripts()) - .each(function(node) { element.appendChild(node) }); - } - else element.innerHTML = content.stripScripts(); - - content.evalScripts.bind(content).defer(); - return element; - }; -} - -if ('outerHTML' in document.createElement('div')) { - Element.Methods.replace = function(element, content) { - element = $(element); - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - element.parentNode.replaceChild(content, element); - return element; - } - - content = Object.toHTML(content); - var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); - - if (Element._insertionTranslations.tags[tagName]) { - var nextSibling = element.next(); - var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - parent.removeChild(element); - if (nextSibling) - fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); - else - fragments.each(function(node) { parent.appendChild(node) }); - } - else element.outerHTML = content.stripScripts(); - - content.evalScripts.bind(content).defer(); - return element; - }; -} - -Element._returnOffset = function(l, t) { - var result = [l, t]; - result.left = l; - result.top = t; - return result; -}; - -Element._getContentFromAnonymousElement = function(tagName, html) { - var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; - if (t) { - div.innerHTML = t[0] + html + t[1]; - t[2].times(function() { div = div.firstChild }); - } else div.innerHTML = html; - return $A(div.childNodes); -}; - -Element._insertionTranslations = { - before: function(element, node) { - element.parentNode.insertBefore(node, element); - }, - top: function(element, node) { - element.insertBefore(node, element.firstChild); - }, - bottom: function(element, node) { - element.appendChild(node); - }, - after: function(element, node) { - element.parentNode.insertBefore(node, element.nextSibling); - }, - tags: { - TABLE: ['', '
    ', 1], - TBODY: ['', '
    ', 2], - TR: ['', '
    ', 3], - TD: ['
    ', '
    ', 4], - SELECT: ['', 1] - } -}; - -(function() { - Object.extend(this.tags, { - THEAD: this.tags.TBODY, - TFOOT: this.tags.TBODY, - TH: this.tags.TD - }); -}).call(Element._insertionTranslations); - -Element.Methods.Simulated = { - hasAttribute: function(element, attribute) { - attribute = Element._attributeTranslations.has[attribute] || attribute; - var node = $(element).getAttributeNode(attribute); - return !!(node && node.specified); - } -}; - -Element.Methods.ByTag = { }; - -Object.extend(Element, Element.Methods); - -if (!Prototype.BrowserFeatures.ElementExtensions && - document.createElement('div')['__proto__']) { - window.HTMLElement = { }; - window.HTMLElement.prototype = document.createElement('div')['__proto__']; - Prototype.BrowserFeatures.ElementExtensions = true; -} - -Element.extend = (function() { - if (Prototype.BrowserFeatures.SpecificElementExtensions) - return Prototype.K; - - var Methods = { }, ByTag = Element.Methods.ByTag; - - var extend = Object.extend(function(element) { - if (!element || element._extendedByPrototype || - element.nodeType != 1 || element == window) return element; - - var methods = Object.clone(Methods), - tagName = element.tagName.toUpperCase(), property, value; - - // extend methods for specific tags - if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); - - for (property in methods) { - value = methods[property]; - if (Object.isFunction(value) && !(property in element)) - element[property] = value.methodize(); - } - - element._extendedByPrototype = Prototype.emptyFunction; - return element; - - }, { - refresh: function() { - // extend methods for all tags (Safari doesn't need this) - if (!Prototype.BrowserFeatures.ElementExtensions) { - Object.extend(Methods, Element.Methods); - Object.extend(Methods, Element.Methods.Simulated); - } - } - }); - - extend.refresh(); - return extend; -})(); - -Element.hasAttribute = function(element, attribute) { - if (element.hasAttribute) return element.hasAttribute(attribute); - return Element.Methods.Simulated.hasAttribute(element, attribute); -}; - -Element.addMethods = function(methods) { - var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; - - if (!methods) { - Object.extend(Form, Form.Methods); - Object.extend(Form.Element, Form.Element.Methods); - Object.extend(Element.Methods.ByTag, { - "FORM": Object.clone(Form.Methods), - "INPUT": Object.clone(Form.Element.Methods), - "SELECT": Object.clone(Form.Element.Methods), - "TEXTAREA": Object.clone(Form.Element.Methods) - }); - } - - if (arguments.length == 2) { - var tagName = methods; - methods = arguments[1]; - } - - if (!tagName) Object.extend(Element.Methods, methods || { }); - else { - if (Object.isArray(tagName)) tagName.each(extend); - else extend(tagName); - } - - function extend(tagName) { - tagName = tagName.toUpperCase(); - if (!Element.Methods.ByTag[tagName]) - Element.Methods.ByTag[tagName] = { }; - Object.extend(Element.Methods.ByTag[tagName], methods); - } - - function copy(methods, destination, onlyIfAbsent) { - onlyIfAbsent = onlyIfAbsent || false; - for (var property in methods) { - var value = methods[property]; - if (!Object.isFunction(value)) continue; - if (!onlyIfAbsent || !(property in destination)) - destination[property] = value.methodize(); - } - } - - function findDOMClass(tagName) { - var klass; - var trans = { - "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", - "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", - "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", - "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", - "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": - "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": - "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": - "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": - "FrameSet", "IFRAME": "IFrame" - }; - if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; - if (window[klass]) return window[klass]; - klass = 'HTML' + tagName + 'Element'; - if (window[klass]) return window[klass]; - klass = 'HTML' + tagName.capitalize() + 'Element'; - if (window[klass]) return window[klass]; - - window[klass] = { }; - window[klass].prototype = document.createElement(tagName)['__proto__']; - return window[klass]; - } - - if (F.ElementExtensions) { - copy(Element.Methods, HTMLElement.prototype); - copy(Element.Methods.Simulated, HTMLElement.prototype, true); - } - - if (F.SpecificElementExtensions) { - for (var tag in Element.Methods.ByTag) { - var klass = findDOMClass(tag); - if (Object.isUndefined(klass)) continue; - copy(T[tag], klass.prototype); - } - } - - Object.extend(Element, Element.Methods); - delete Element.ByTag; - - if (Element.extend.refresh) Element.extend.refresh(); - Element.cache = { }; -}; - -document.viewport = { - getDimensions: function() { - var dimensions = { }, B = Prototype.Browser; - $w('width height').each(function(d) { - var D = d.capitalize(); - if (B.WebKit && !document.evaluate) { - // Safari <3.0 needs self.innerWidth/Height - dimensions[d] = self['inner' + D]; - } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) { - // Opera <9.5 needs document.body.clientWidth/Height - dimensions[d] = document.body['client' + D] - } else { - dimensions[d] = document.documentElement['client' + D]; - } - }); - return dimensions; - }, - - getWidth: function() { - return this.getDimensions().width; - }, - - getHeight: function() { - return this.getDimensions().height; - }, - - getScrollOffsets: function() { - return Element._returnOffset( - window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, - window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); - } -}; -/* Portions of the Selector class are derived from Jack Slocum's DomQuery, - * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style - * license. Please see http://www.yui-ext.com/ for more information. */ - -var Selector = Class.create({ - initialize: function(expression) { - this.expression = expression.strip(); - - if (this.shouldUseSelectorsAPI()) { - this.mode = 'selectorsAPI'; - } else if (this.shouldUseXPath()) { - this.mode = 'xpath'; - this.compileXPathMatcher(); - } else { - this.mode = "normal"; - this.compileMatcher(); - } - - }, - - shouldUseXPath: function() { - if (!Prototype.BrowserFeatures.XPath) return false; - - var e = this.expression; - - // Safari 3 chokes on :*-of-type and :empty - if (Prototype.Browser.WebKit && - (e.include("-of-type") || e.include(":empty"))) - return false; - - // XPath can't do namespaced attributes, nor can it read - // the "checked" property from DOM nodes - if ((/(\[[\w-]*?:|:checked)/).test(e)) - return false; - - return true; - }, - - shouldUseSelectorsAPI: function() { - if (!Prototype.BrowserFeatures.SelectorsAPI) return false; - - if (!Selector._div) Selector._div = new Element('div'); - - // Make sure the browser treats the selector as valid. Test on an - // isolated element to minimize cost of this check. - try { - Selector._div.querySelector(this.expression); - } catch(e) { - return false; - } - - return true; - }, - - compileMatcher: function() { - var e = this.expression, ps = Selector.patterns, h = Selector.handlers, - c = Selector.criteria, le, p, m; - - if (Selector._cache[e]) { - this.matcher = Selector._cache[e]; - return; - } - - this.matcher = ["this.matcher = function(root) {", - "var r = root, h = Selector.handlers, c = false, n;"]; - - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i in ps) { - p = ps[i]; - if (m = e.match(p)) { - this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : - new Template(c[i]).evaluate(m)); - e = e.replace(m[0], ''); - break; - } - } - } - - this.matcher.push("return h.unique(n);\n}"); - eval(this.matcher.join('\n')); - Selector._cache[this.expression] = this.matcher; - }, - - compileXPathMatcher: function() { - var e = this.expression, ps = Selector.patterns, - x = Selector.xpath, le, m; - - if (Selector._cache[e]) { - this.xpath = Selector._cache[e]; return; - } - - this.matcher = ['.//*']; - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i in ps) { - if (m = e.match(ps[i])) { - this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : - new Template(x[i]).evaluate(m)); - e = e.replace(m[0], ''); - break; - } - } - } - - this.xpath = this.matcher.join(''); - Selector._cache[this.expression] = this.xpath; - }, - - findElements: function(root) { - root = root || document; - var e = this.expression, results; - - switch (this.mode) { - case 'selectorsAPI': - // querySelectorAll queries document-wide, then filters to descendants - // of the context element. That's not what we want. - // Add an explicit context to the selector if necessary. - if (root !== document) { - var oldId = root.id, id = $(root).identify(); - e = "#" + id + " " + e; - } - - results = $A(root.querySelectorAll(e)).map(Element.extend); - root.id = oldId; - - return results; - case 'xpath': - return document._getElementsByXPath(this.xpath, root); - default: - return this.matcher(root); - } - }, - - match: function(element) { - this.tokens = []; - - var e = this.expression, ps = Selector.patterns, as = Selector.assertions; - var le, p, m; - - while (e && le !== e && (/\S/).test(e)) { - le = e; - for (var i in ps) { - p = ps[i]; - if (m = e.match(p)) { - // use the Selector.assertions methods unless the selector - // is too complex. - if (as[i]) { - this.tokens.push([i, Object.clone(m)]); - e = e.replace(m[0], ''); - } else { - // reluctantly do a document-wide search - // and look for a match in the array - return this.findElements(document).include(element); - } - } - } - } - - var match = true, name, matches; - for (var i = 0, token; token = this.tokens[i]; i++) { - name = token[0], matches = token[1]; - if (!Selector.assertions[name](element, matches)) { - match = false; break; - } - } - - return match; - }, - - toString: function() { - return this.expression; - }, - - inspect: function() { - return "#"; - } -}); - -Object.extend(Selector, { - _cache: { }, - - xpath: { - descendant: "//*", - child: "/*", - adjacent: "/following-sibling::*[1]", - laterSibling: '/following-sibling::*', - tagName: function(m) { - if (m[1] == '*') return ''; - return "[local-name()='" + m[1].toLowerCase() + - "' or local-name()='" + m[1].toUpperCase() + "']"; - }, - className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", - id: "[@id='#{1}']", - attrPresence: function(m) { - m[1] = m[1].toLowerCase(); - return new Template("[@#{1}]").evaluate(m); - }, - attr: function(m) { - m[1] = m[1].toLowerCase(); - m[3] = m[5] || m[6]; - return new Template(Selector.xpath.operators[m[2]]).evaluate(m); - }, - pseudo: function(m) { - var h = Selector.xpath.pseudos[m[1]]; - if (!h) return ''; - if (Object.isFunction(h)) return h(m); - return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); - }, - operators: { - '=': "[@#{1}='#{3}']", - '!=': "[@#{1}!='#{3}']", - '^=': "[starts-with(@#{1}, '#{3}')]", - '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", - '*=': "[contains(@#{1}, '#{3}')]", - '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", - '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" - }, - pseudos: { - 'first-child': '[not(preceding-sibling::*)]', - 'last-child': '[not(following-sibling::*)]', - 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', - 'empty': "[count(*) = 0 and (count(text()) = 0)]", - 'checked': "[@checked]", - 'disabled': "[(@disabled) and (@type!='hidden')]", - 'enabled': "[not(@disabled) and (@type!='hidden')]", - 'not': function(m) { - var e = m[6], p = Selector.patterns, - x = Selector.xpath, le, v; - - var exclusion = []; - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i in p) { - if (m = e.match(p[i])) { - v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); - exclusion.push("(" + v.substring(1, v.length - 1) + ")"); - e = e.replace(m[0], ''); - break; - } - } - } - return "[not(" + exclusion.join(" and ") + ")]"; - }, - 'nth-child': function(m) { - return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); - }, - 'nth-last-child': function(m) { - return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); - }, - 'nth-of-type': function(m) { - return Selector.xpath.pseudos.nth("position() ", m); - }, - 'nth-last-of-type': function(m) { - return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); - }, - 'first-of-type': function(m) { - m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); - }, - 'last-of-type': function(m) { - m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); - }, - 'only-of-type': function(m) { - var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); - }, - nth: function(fragment, m) { - var mm, formula = m[6], predicate; - if (formula == 'even') formula = '2n+0'; - if (formula == 'odd') formula = '2n+1'; - if (mm = formula.match(/^(\d+)$/)) // digit only - return '[' + fragment + "= " + mm[1] + ']'; - if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b - if (mm[1] == "-") mm[1] = -1; - var a = mm[1] ? Number(mm[1]) : 1; - var b = mm[2] ? Number(mm[2]) : 0; - predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + - "((#{fragment} - #{b}) div #{a} >= 0)]"; - return new Template(predicate).evaluate({ - fragment: fragment, a: a, b: b }); - } - } - } - }, - - criteria: { - tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', - className: 'n = h.className(n, r, "#{1}", c); c = false;', - id: 'n = h.id(n, r, "#{1}", c); c = false;', - attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', - attr: function(m) { - m[3] = (m[5] || m[6]); - return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); - }, - pseudo: function(m) { - if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); - return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); - }, - descendant: 'c = "descendant";', - child: 'c = "child";', - adjacent: 'c = "adjacent";', - laterSibling: 'c = "laterSibling";' - }, - - patterns: { - // combinators must be listed first - // (and descendant needs to be last combinator) - laterSibling: /^\s*~\s*/, - child: /^\s*>\s*/, - adjacent: /^\s*\+\s*/, - descendant: /^\s/, - - // selectors follow - tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, - id: /^#([\w\-\*]+)(\b|$)/, - className: /^\.([\w\-\*]+)(\b|$)/, - pseudo: -/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, - attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/, - attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ - }, - - // for Selector.match and Element#match - assertions: { - tagName: function(element, matches) { - return matches[1].toUpperCase() == element.tagName.toUpperCase(); - }, - - className: function(element, matches) { - return Element.hasClassName(element, matches[1]); - }, - - id: function(element, matches) { - return element.id === matches[1]; - }, - - attrPresence: function(element, matches) { - return Element.hasAttribute(element, matches[1]); - }, - - attr: function(element, matches) { - var nodeValue = Element.readAttribute(element, matches[1]); - return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); - } - }, - - handlers: { - // UTILITY FUNCTIONS - // joins two collections - concat: function(a, b) { - for (var i = 0, node; node = b[i]; i++) - a.push(node); - return a; - }, - - // marks an array of nodes for counting - mark: function(nodes) { - var _true = Prototype.emptyFunction; - for (var i = 0, node; node = nodes[i]; i++) - node._countedByPrototype = _true; - return nodes; - }, - - unmark: function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node._countedByPrototype = undefined; - return nodes; - }, - - // mark each child node with its position (for nth calls) - // "ofType" flag indicates whether we're indexing for nth-of-type - // rather than nth-child - index: function(parentNode, reverse, ofType) { - parentNode._countedByPrototype = Prototype.emptyFunction; - if (reverse) { - for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { - var node = nodes[i]; - if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; - } - } else { - for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) - if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; - } - }, - - // filters out duplicates and extends all nodes - unique: function(nodes) { - if (nodes.length == 0) return nodes; - var results = [], n; - for (var i = 0, l = nodes.length; i < l; i++) - if (!(n = nodes[i])._countedByPrototype) { - n._countedByPrototype = Prototype.emptyFunction; - results.push(Element.extend(n)); - } - return Selector.handlers.unmark(results); - }, - - // COMBINATOR FUNCTIONS - descendant: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - h.concat(results, node.getElementsByTagName('*')); - return results; - }, - - child: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) { - for (var j = 0, child; child = node.childNodes[j]; j++) - if (child.nodeType == 1 && child.tagName != '!') results.push(child); - } - return results; - }, - - adjacent: function(nodes) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - var next = this.nextElementSibling(node); - if (next) results.push(next); - } - return results; - }, - - laterSibling: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - h.concat(results, Element.nextSiblings(node)); - return results; - }, - - nextElementSibling: function(node) { - while (node = node.nextSibling) - if (node.nodeType == 1) return node; - return null; - }, - - previousElementSibling: function(node) { - while (node = node.previousSibling) - if (node.nodeType == 1) return node; - return null; - }, - - // TOKEN FUNCTIONS - tagName: function(nodes, root, tagName, combinator) { - var uTagName = tagName.toUpperCase(); - var results = [], h = Selector.handlers; - if (nodes) { - if (combinator) { - // fastlane for ordinary descendant combinators - if (combinator == "descendant") { - for (var i = 0, node; node = nodes[i]; i++) - h.concat(results, node.getElementsByTagName(tagName)); - return results; - } else nodes = this[combinator](nodes); - if (tagName == "*") return nodes; - } - for (var i = 0, node; node = nodes[i]; i++) - if (node.tagName.toUpperCase() === uTagName) results.push(node); - return results; - } else return root.getElementsByTagName(tagName); - }, - - id: function(nodes, root, id, combinator) { - var targetNode = $(id), h = Selector.handlers; - if (!targetNode) return []; - if (!nodes && root == document) return [targetNode]; - if (nodes) { - if (combinator) { - if (combinator == 'child') { - for (var i = 0, node; node = nodes[i]; i++) - if (targetNode.parentNode == node) return [targetNode]; - } else if (combinator == 'descendant') { - for (var i = 0, node; node = nodes[i]; i++) - if (Element.descendantOf(targetNode, node)) return [targetNode]; - } else if (combinator == 'adjacent') { - for (var i = 0, node; node = nodes[i]; i++) - if (Selector.handlers.previousElementSibling(targetNode) == node) - return [targetNode]; - } else nodes = h[combinator](nodes); - } - for (var i = 0, node; node = nodes[i]; i++) - if (node == targetNode) return [targetNode]; - return []; - } - return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; - }, - - className: function(nodes, root, className, combinator) { - if (nodes && combinator) nodes = this[combinator](nodes); - return Selector.handlers.byClassName(nodes, root, className); - }, - - byClassName: function(nodes, root, className) { - if (!nodes) nodes = Selector.handlers.descendant([root]); - var needle = ' ' + className + ' '; - for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { - nodeClassName = node.className; - if (nodeClassName.length == 0) continue; - if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) - results.push(node); - } - return results; - }, - - attrPresence: function(nodes, root, attr, combinator) { - if (!nodes) nodes = root.getElementsByTagName("*"); - if (nodes && combinator) nodes = this[combinator](nodes); - var results = []; - for (var i = 0, node; node = nodes[i]; i++) - if (Element.hasAttribute(node, attr)) results.push(node); - return results; - }, - - attr: function(nodes, root, attr, value, operator, combinator) { - if (!nodes) nodes = root.getElementsByTagName("*"); - if (nodes && combinator) nodes = this[combinator](nodes); - var handler = Selector.operators[operator], results = []; - for (var i = 0, node; node = nodes[i]; i++) { - var nodeValue = Element.readAttribute(node, attr); - if (nodeValue === null) continue; - if (handler(nodeValue, value)) results.push(node); - } - return results; - }, - - pseudo: function(nodes, name, value, root, combinator) { - if (nodes && combinator) nodes = this[combinator](nodes); - if (!nodes) nodes = root.getElementsByTagName("*"); - return Selector.pseudos[name](nodes, value, root); - } - }, - - pseudos: { - 'first-child': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Selector.handlers.previousElementSibling(node)) continue; - results.push(node); - } - return results; - }, - 'last-child': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Selector.handlers.nextElementSibling(node)) continue; - results.push(node); - } - return results; - }, - 'only-child': function(nodes, value, root) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) - results.push(node); - return results; - }, - 'nth-child': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root); - }, - 'nth-last-child': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, true); - }, - 'nth-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, false, true); - }, - 'nth-last-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, true, true); - }, - 'first-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, "1", root, false, true); - }, - 'last-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, "1", root, true, true); - }, - 'only-of-type': function(nodes, formula, root) { - var p = Selector.pseudos; - return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); - }, - - // handles the an+b logic - getIndices: function(a, b, total) { - if (a == 0) return b > 0 ? [b] : []; - return $R(1, total).inject([], function(memo, i) { - if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); - return memo; - }); - }, - - // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type - nth: function(nodes, formula, root, reverse, ofType) { - if (nodes.length == 0) return []; - if (formula == 'even') formula = '2n+0'; - if (formula == 'odd') formula = '2n+1'; - var h = Selector.handlers, results = [], indexed = [], m; - h.mark(nodes); - for (var i = 0, node; node = nodes[i]; i++) { - if (!node.parentNode._countedByPrototype) { - h.index(node.parentNode, reverse, ofType); - indexed.push(node.parentNode); - } - } - if (formula.match(/^\d+$/)) { // just a number - formula = Number(formula); - for (var i = 0, node; node = nodes[i]; i++) - if (node.nodeIndex == formula) results.push(node); - } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b - if (m[1] == "-") m[1] = -1; - var a = m[1] ? Number(m[1]) : 1; - var b = m[2] ? Number(m[2]) : 0; - var indices = Selector.pseudos.getIndices(a, b, nodes.length); - for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { - for (var j = 0; j < l; j++) - if (node.nodeIndex == indices[j]) results.push(node); - } - } - h.unmark(nodes); - h.unmark(indexed); - return results; - }, - - 'empty': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - // IE treats comments as element nodes - if (node.tagName == '!' || node.firstChild) continue; - results.push(node); - } - return results; - }, - - 'not': function(nodes, selector, root) { - var h = Selector.handlers, selectorType, m; - var exclusions = new Selector(selector).findElements(root); - h.mark(exclusions); - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node._countedByPrototype) results.push(node); - h.unmark(exclusions); - return results; - }, - - 'enabled': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node.disabled && (!node.type || node.type !== 'hidden')) - results.push(node); - return results; - }, - - 'disabled': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (node.disabled) results.push(node); - return results; - }, - - 'checked': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (node.checked) results.push(node); - return results; - } - }, - - operators: { - '=': function(nv, v) { return nv == v; }, - '!=': function(nv, v) { return nv != v; }, - '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, - '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, - '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, - '$=': function(nv, v) { return nv.endsWith(v); }, - '*=': function(nv, v) { return nv.include(v); }, - '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, - '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + - '-').include('-' + (v || "").toUpperCase() + '-'); } - }, - - split: function(expression) { - var expressions = []; - expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { - expressions.push(m[1].strip()); - }); - return expressions; - }, - - matchElements: function(elements, expression) { - var matches = $$(expression), h = Selector.handlers; - h.mark(matches); - for (var i = 0, results = [], element; element = elements[i]; i++) - if (element._countedByPrototype) results.push(element); - h.unmark(matches); - return results; - }, - - findElement: function(elements, expression, index) { - if (Object.isNumber(expression)) { - index = expression; expression = false; - } - return Selector.matchElements(elements, expression || '*')[index || 0]; - }, - - findChildElements: function(element, expressions) { - expressions = Selector.split(expressions.join(',')); - var results = [], h = Selector.handlers; - for (var i = 0, l = expressions.length, selector; i < l; i++) { - selector = new Selector(expressions[i].strip()); - h.concat(results, selector.findElements(element)); - } - return (l > 1) ? h.unique(results) : results; - } -}); - -if (Prototype.Browser.IE) { - Object.extend(Selector.handlers, { - // IE returns comment nodes on getElementsByTagName("*"). - // Filter them out. - concat: function(a, b) { - for (var i = 0, node; node = b[i]; i++) - if (node.tagName !== "!") a.push(node); - return a; - }, - - // IE improperly serializes _countedByPrototype in (inner|outer)HTML. - unmark: function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node.removeAttribute('_countedByPrototype'); - return nodes; - } - }); -} - -function $$() { - return Selector.findChildElements(document, $A(arguments)); -} -var Form = { - reset: function(form) { - $(form).reset(); - return form; - }, - - serializeElements: function(elements, options) { - if (typeof options != 'object') options = { hash: !!options }; - else if (Object.isUndefined(options.hash)) options.hash = true; - var key, value, submitted = false, submit = options.submit; - - var data = elements.inject({ }, function(result, element) { - if (!element.disabled && element.name) { - key = element.name; value = $(element).getValue(); - if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && - submit !== false && (!submit || key == submit) && (submitted = true)))) { - if (key in result) { - // a key is already present; construct an array of values - if (!Object.isArray(result[key])) result[key] = [result[key]]; - result[key].push(value); - } - else result[key] = value; - } - } - return result; - }); - - return options.hash ? data : Object.toQueryString(data); - } -}; - -Form.Methods = { - serialize: function(form, options) { - return Form.serializeElements(Form.getElements(form), options); - }, - - getElements: function(form) { - return $A($(form).getElementsByTagName('*')).inject([], - function(elements, child) { - if (Form.Element.Serializers[child.tagName.toLowerCase()]) - elements.push(Element.extend(child)); - return elements; - } - ); - }, - - getInputs: function(form, typeName, name) { - form = $(form); - var inputs = form.getElementsByTagName('input'); - - if (!typeName && !name) return $A(inputs).map(Element.extend); - - for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { - var input = inputs[i]; - if ((typeName && input.type != typeName) || (name && input.name != name)) - continue; - matchingInputs.push(Element.extend(input)); - } - - return matchingInputs; - }, - - disable: function(form) { - form = $(form); - Form.getElements(form).invoke('disable'); - return form; - }, - - enable: function(form) { - form = $(form); - Form.getElements(form).invoke('enable'); - return form; - }, - - findFirstElement: function(form) { - var elements = $(form).getElements().findAll(function(element) { - return 'hidden' != element.type && !element.disabled; - }); - var firstByIndex = elements.findAll(function(element) { - return element.hasAttribute('tabIndex') && element.tabIndex >= 0; - }).sortBy(function(element) { return element.tabIndex }).first(); - - return firstByIndex ? firstByIndex : elements.find(function(element) { - return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); - }); - }, - - focusFirstElement: function(form) { - form = $(form); - form.findFirstElement().activate(); - return form; - }, - - request: function(form, options) { - form = $(form), options = Object.clone(options || { }); - - var params = options.parameters, action = form.readAttribute('action') || ''; - if (action.blank()) action = window.location.href; - options.parameters = form.serialize(true); - - if (params) { - if (Object.isString(params)) params = params.toQueryParams(); - Object.extend(options.parameters, params); - } - - if (form.hasAttribute('method') && !options.method) - options.method = form.method; - - return new Ajax.Request(action, options); - } -}; - -/*--------------------------------------------------------------------------*/ - -Form.Element = { - focus: function(element) { - $(element).focus(); - return element; - }, - - select: function(element) { - $(element).select(); - return element; - } -}; - -Form.Element.Methods = { - serialize: function(element) { - element = $(element); - if (!element.disabled && element.name) { - var value = element.getValue(); - if (value != undefined) { - var pair = { }; - pair[element.name] = value; - return Object.toQueryString(pair); - } - } - return ''; - }, - - getValue: function(element) { - element = $(element); - var method = element.tagName.toLowerCase(); - return Form.Element.Serializers[method](element); - }, - - setValue: function(element, value) { - element = $(element); - var method = element.tagName.toLowerCase(); - Form.Element.Serializers[method](element, value); - return element; - }, - - clear: function(element) { - $(element).value = ''; - return element; - }, - - present: function(element) { - return $(element).value != ''; - }, - - activate: function(element) { - element = $(element); - try { - element.focus(); - if (element.select && (element.tagName.toLowerCase() != 'input' || - !['button', 'reset', 'submit'].include(element.type))) - element.select(); - } catch (e) { } - return element; - }, - - disable: function(element) { - element = $(element); - element.disabled = true; - return element; - }, - - enable: function(element) { - element = $(element); - element.disabled = false; - return element; - } -}; - -/*--------------------------------------------------------------------------*/ - -var Field = Form.Element; -var $F = Form.Element.Methods.getValue; - -/*--------------------------------------------------------------------------*/ - -Form.Element.Serializers = { - input: function(element, value) { - switch (element.type.toLowerCase()) { - case 'checkbox': - case 'radio': - return Form.Element.Serializers.inputSelector(element, value); - default: - return Form.Element.Serializers.textarea(element, value); - } - }, - - inputSelector: function(element, value) { - if (Object.isUndefined(value)) return element.checked ? element.value : null; - else element.checked = !!value; - }, - - textarea: function(element, value) { - if (Object.isUndefined(value)) return element.value; - else element.value = value; - }, - - select: function(element, value) { - if (Object.isUndefined(value)) - return this[element.type == 'select-one' ? - 'selectOne' : 'selectMany'](element); - else { - var opt, currentValue, single = !Object.isArray(value); - for (var i = 0, length = element.length; i < length; i++) { - opt = element.options[i]; - currentValue = this.optionValue(opt); - if (single) { - if (currentValue == value) { - opt.selected = true; - return; - } - } - else opt.selected = value.include(currentValue); - } - } - }, - - selectOne: function(element) { - var index = element.selectedIndex; - return index >= 0 ? this.optionValue(element.options[index]) : null; - }, - - selectMany: function(element) { - var values, length = element.length; - if (!length) return null; - - for (var i = 0, values = []; i < length; i++) { - var opt = element.options[i]; - if (opt.selected) values.push(this.optionValue(opt)); - } - return values; - }, - - optionValue: function(opt) { - // extend element because hasAttribute may not be native - return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; - } -}; - -/*--------------------------------------------------------------------------*/ - -Abstract.TimedObserver = Class.create(PeriodicalExecuter, { - initialize: function($super, element, frequency, callback) { - $super(callback, frequency); - this.element = $(element); - this.lastValue = this.getValue(); - }, - - execute: function() { - var value = this.getValue(); - if (Object.isString(this.lastValue) && Object.isString(value) ? - this.lastValue != value : String(this.lastValue) != String(value)) { - this.callback(this.element, value); - this.lastValue = value; - } - } -}); - -Form.Element.Observer = Class.create(Abstract.TimedObserver, { - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.Observer = Class.create(Abstract.TimedObserver, { - getValue: function() { - return Form.serialize(this.element); - } -}); - -/*--------------------------------------------------------------------------*/ - -Abstract.EventObserver = Class.create({ - initialize: function(element, callback) { - this.element = $(element); - this.callback = callback; - - this.lastValue = this.getValue(); - if (this.element.tagName.toLowerCase() == 'form') - this.registerFormCallbacks(); - else - this.registerCallback(this.element); - }, - - onElementEvent: function() { - var value = this.getValue(); - if (this.lastValue != value) { - this.callback(this.element, value); - this.lastValue = value; - } - }, - - registerFormCallbacks: function() { - Form.getElements(this.element).each(this.registerCallback, this); - }, - - registerCallback: function(element) { - if (element.type) { - switch (element.type.toLowerCase()) { - case 'checkbox': - case 'radio': - Event.observe(element, 'click', this.onElementEvent.bind(this)); - break; - default: - Event.observe(element, 'change', this.onElementEvent.bind(this)); - break; - } - } - } -}); - -Form.Element.EventObserver = Class.create(Abstract.EventObserver, { - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.EventObserver = Class.create(Abstract.EventObserver, { - getValue: function() { - return Form.serialize(this.element); - } -}); -if (!window.Event) var Event = { }; - -Object.extend(Event, { - KEY_BACKSPACE: 8, - KEY_TAB: 9, - KEY_RETURN: 13, - KEY_ESC: 27, - KEY_LEFT: 37, - KEY_UP: 38, - KEY_RIGHT: 39, - KEY_DOWN: 40, - KEY_DELETE: 46, - KEY_HOME: 36, - KEY_END: 35, - KEY_PAGEUP: 33, - KEY_PAGEDOWN: 34, - KEY_INSERT: 45, - - cache: { }, - - relatedTarget: function(event) { - var element; - switch(event.type) { - case 'mouseover': element = event.fromElement; break; - case 'mouseout': element = event.toElement; break; - default: return null; - } - return Element.extend(element); - } -}); - -Event.Methods = (function() { - var isButton; - - if (Prototype.Browser.IE) { - var buttonMap = { 0: 1, 1: 4, 2: 2 }; - isButton = function(event, code) { - return event.button == buttonMap[code]; - }; - - } else if (Prototype.Browser.WebKit) { - isButton = function(event, code) { - switch (code) { - case 0: return event.which == 1 && !event.metaKey; - case 1: return event.which == 1 && event.metaKey; - default: return false; - } - }; - - } else { - isButton = function(event, code) { - return event.which ? (event.which === code + 1) : (event.button === code); - }; - } - - return { - isLeftClick: function(event) { return isButton(event, 0) }, - isMiddleClick: function(event) { return isButton(event, 1) }, - isRightClick: function(event) { return isButton(event, 2) }, - - element: function(event) { - event = Event.extend(event); - - var node = event.target, - type = event.type, - currentTarget = event.currentTarget; - - if (currentTarget && currentTarget.tagName) { - // Firefox screws up the "click" event when moving between radio buttons - // via arrow keys. It also screws up the "load" and "error" events on images, - // reporting the document as the target instead of the original image. - if (type === 'load' || type === 'error' || - (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' - && currentTarget.type === 'radio')) - node = currentTarget; - } - if (node.nodeType == Node.TEXT_NODE) node = node.parentNode; - return Element.extend(node); - }, - - findElement: function(event, expression) { - var element = Event.element(event); - if (!expression) return element; - var elements = [element].concat(element.ancestors()); - return Selector.findElement(elements, expression, 0); - }, - - pointer: function(event) { - var docElement = document.documentElement, - body = document.body || { scrollLeft: 0, scrollTop: 0 }; - return { - x: event.pageX || (event.clientX + - (docElement.scrollLeft || body.scrollLeft) - - (docElement.clientLeft || 0)), - y: event.pageY || (event.clientY + - (docElement.scrollTop || body.scrollTop) - - (docElement.clientTop || 0)) - }; - }, - - pointerX: function(event) { return Event.pointer(event).x }, - pointerY: function(event) { return Event.pointer(event).y }, - - stop: function(event) { - Event.extend(event); - event.preventDefault(); - event.stopPropagation(); - event.stopped = true; - } - }; -})(); - -Event.extend = (function() { - var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { - m[name] = Event.Methods[name].methodize(); - return m; - }); - - if (Prototype.Browser.IE) { - Object.extend(methods, { - stopPropagation: function() { this.cancelBubble = true }, - preventDefault: function() { this.returnValue = false }, - inspect: function() { return "[object Event]" } - }); - - return function(event) { - if (!event) return false; - if (event._extendedByPrototype) return event; - - event._extendedByPrototype = Prototype.emptyFunction; - var pointer = Event.pointer(event); - Object.extend(event, { - target: event.srcElement, - relatedTarget: Event.relatedTarget(event), - pageX: pointer.x, - pageY: pointer.y - }); - return Object.extend(event, methods); - }; - - } else { - Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__']; - Object.extend(Event.prototype, methods); - return Prototype.K; - } -})(); - -Object.extend(Event, (function() { - var cache = Event.cache; - - function getEventID(element) { - if (element._prototypeEventID) return element._prototypeEventID[0]; - arguments.callee.id = arguments.callee.id || 1; - return element._prototypeEventID = [++arguments.callee.id]; - } - - function getDOMEventName(eventName) { - if (eventName && eventName.include(':')) return "dataavailable"; - return eventName; - } - - function getCacheForID(id) { - return cache[id] = cache[id] || { }; - } - - function getWrappersForEventName(id, eventName) { - var c = getCacheForID(id); - return c[eventName] = c[eventName] || []; - } - - function createWrapper(element, eventName, handler) { - var id = getEventID(element); - var c = getWrappersForEventName(id, eventName); - if (c.pluck("handler").include(handler)) return false; - - var wrapper = function(event) { - if (!Event || !Event.extend || - (event.eventName && event.eventName != eventName)) - return false; - - Event.extend(event); - handler.call(element, event); - }; - - wrapper.handler = handler; - c.push(wrapper); - return wrapper; - } - - function findWrapper(id, eventName, handler) { - var c = getWrappersForEventName(id, eventName); - return c.find(function(wrapper) { return wrapper.handler == handler }); - } - - function destroyWrapper(id, eventName, handler) { - var c = getCacheForID(id); - if (!c[eventName]) return false; - c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); - } - - function destroyCache() { - for (var id in cache) - for (var eventName in cache[id]) - cache[id][eventName] = null; - } - - - // Internet Explorer needs to remove event handlers on page unload - // in order to avoid memory leaks. - if (window.attachEvent) { - window.attachEvent("onunload", destroyCache); - } - - // Safari has a dummy event handler on page unload so that it won't - // use its bfcache. Safari <= 3.1 has an issue with restoring the "document" - // object when page is returned to via the back button using its bfcache. - if (Prototype.Browser.WebKit) { - window.addEventListener('unload', Prototype.emptyFunction, false); - } - - return { - observe: function(element, eventName, handler) { - element = $(element); - var name = getDOMEventName(eventName); - - var wrapper = createWrapper(element, eventName, handler); - if (!wrapper) return element; - - if (element.addEventListener) { - element.addEventListener(name, wrapper, false); - } else { - element.attachEvent("on" + name, wrapper); - } - - return element; - }, - - stopObserving: function(element, eventName, handler) { - element = $(element); - var id = getEventID(element), name = getDOMEventName(eventName); - - if (!handler && eventName) { - getWrappersForEventName(id, eventName).each(function(wrapper) { - element.stopObserving(eventName, wrapper.handler); - }); - return element; - - } else if (!eventName) { - Object.keys(getCacheForID(id)).each(function(eventName) { - element.stopObserving(eventName); - }); - return element; - } - - var wrapper = findWrapper(id, eventName, handler); - if (!wrapper) return element; - - if (element.removeEventListener) { - element.removeEventListener(name, wrapper, false); - } else { - element.detachEvent("on" + name, wrapper); - } - - destroyWrapper(id, eventName, handler); - - return element; - }, - - fire: function(element, eventName, memo) { - element = $(element); - if (element == document && document.createEvent && !element.dispatchEvent) - element = document.documentElement; - - var event; - if (document.createEvent) { - event = document.createEvent("HTMLEvents"); - event.initEvent("dataavailable", true, true); - } else { - event = document.createEventObject(); - event.eventType = "ondataavailable"; - } - - event.eventName = eventName; - event.memo = memo || { }; - - if (document.createEvent) { - element.dispatchEvent(event); - } else { - element.fireEvent(event.eventType, event); - } - - return Event.extend(event); - } - }; -})()); - -Object.extend(Event, Event.Methods); - -Element.addMethods({ - fire: Event.fire, - observe: Event.observe, - stopObserving: Event.stopObserving -}); - -Object.extend(document, { - fire: Element.Methods.fire.methodize(), - observe: Element.Methods.observe.methodize(), - stopObserving: Element.Methods.stopObserving.methodize(), - loaded: false -}); - -(function() { - /* Support for the DOMContentLoaded event is based on work by Dan Webb, - Matthias Miller, Dean Edwards and John Resig. */ - - var timer; - - function fireContentLoadedEvent() { - if (document.loaded) return; - if (timer) window.clearInterval(timer); - document.fire("dom:loaded"); - document.loaded = true; - } - - if (document.addEventListener) { - if (Prototype.Browser.WebKit) { - timer = window.setInterval(function() { - if (/loaded|complete/.test(document.readyState)) - fireContentLoadedEvent(); - }, 0); - - Event.observe(window, "load", fireContentLoadedEvent); - - } else { - document.addEventListener("DOMContentLoaded", - fireContentLoadedEvent, false); - } - - } else { - document.write(" - - - - -

    Press a key, the details will be displayed below

    -
    - - - diff --git a/tryruby/public/stylesheets/site.css b/tryruby/public/stylesheets/site.css deleted file mode 100755 index 307347e..0000000 --- a/tryruby/public/stylesheets/site.css +++ /dev/null @@ -1,211 +0,0 @@ -body { - font-family: verdana, arial, sans-serif; - font-size: 14px; - text-align: center; -} -h1, h2, h3, h4 { - font-family: georgia, serif; - margin: 10px 16px; padding: 0; -} -h1 { - color: #333; - font-size: 48px; - font-weight: normal; - margin: 10px 0px; -} -h3 { - color: white; - font-size: 21px; - font-weight: normal; -} -a,a:link,a:visited { - text-decoration: none; - color: #57ad11; -} -a:hover { - text-decoration: underline; - color: #57ad11; -} -a:active { - text-decoration: underline; - color: #dddddd; -} -input.keyboard-selector-input { - position: fixed; - top: 0; - _position: absolute; - _top: expression(eval(document.body.scrollTop)); - left: -300px; -} -#container { - width: 678px; - margin: 0 auto; - text-align: left; -} -#content { - width: 712px; - background: url(/images/tile.png) repeat-y; -} -#lilBrowser { - position: absolute; - background-color: white; - top: -540px; - left: -440px; - width: 510px; - height: 430px; - padding: 5px; - border: solid 1px #444; - z-index: 100; -} -h3#lbTitle { - color: #444; - font-family: verdana, arial, sans-serif; - font-size: small; - float:left; - line-height: 90%; - margin: 0; padding: 3px; -} -p#lbClose { - font-size: small; - float: right; - margin: 0; padding: 3px; -} -p#lbClose a { - display: inline; -} -#shellwin { - width: 712px; - padding-left: 12px; - background: url(/images/background.png) no-repeat; -} - -/* tutorial panes */ -.stretcher { - color: #f1f1ff; - display: none; - margin: 0; - padding: 0; - padding-left: 12px; - background: url(/images/tile.png) repeat-y; -} -.stretcher a, .stretcher a:link, .stretcher a:visited, .stretcher a:active { - text-decoration: none; - color: #a7ed91; -} -.stretcher a:hover { - text-decoration: underline; - color: #b7fd91; -} -.stretcher p { - margin: 10px 16px; -} -.stretcher dl, .stretcher ul { - background-color: white; - color: #333; - padding: 4px 8px; - font-size: 12px; - margin-left: 16px; - list-style: none; -} -.stretcher li { - margin: 6px; -} -.stretcher p code { - background-color: #874a20; - color: #fedeec; - padding: 1px 4px; -} -.stretcher p code.cmd { - background-color: #eeeeec; - color: #204a87; -} -.stretcher dt { - font-weight: bold; -} - -.chapmark { - padding: 6px 0; - margin-left: 12px; - margin-right: 22px; - color: #553; - background: #efefe1; -} -.chapmark h3 { - color: #335; -} -.chapmark a, .chapmark a:link, .chapmark a:visited, .chapmark a:active { - text-decoration: none; - color: #372d61; -} -.chapmark a:hover { - text-decoration: underline; - color: #477d51; -} -.note { color: #ddc; text-align: center; font-size: xx-small; } -ul li strong { color: #286; border-bottom: solid 2px #cca; } -ul li code { background-color: #f1f1f1; padding: 1px 3px; border-bottom: solid 2px #ddd; } -ul li code.faded { color: #899; } -code strong { background-color: #dcffb9; padding: 1px 3px; } -ul.commands li strong { display: block; float: left; width: 60px; border: none; } - -/* irb terminal */ -#terminal { - background-color: #f2f2f0; - border: solid 1px #204a87; - width: 678px; - height: 240px; - overflow: auto; -} -#irb { - visibility: hidden; - padding: 4px; margin: 0; - font-family: "Andale Mono", courier, fixed, monospace; - font-size: 14px; - line-height: 16px; - color: #204a87; - text-align: left; -} -#irb div { - margin: 0; padding: 0; -} -#irb div b { - background-color: #874a20; - color: #fedeac; -} -div.answer, div.stdout, div.no_answer, div.load { - display: none; -} - -/* terminal escape colors */ -span.fore_black { color: #2e3436; } -span.fore_dark_gray { color: #888a85; } -span.fore_gray { color: #babdb6; } -span.fore_white { color: #eeeeec; } -span.fore_blue { color: #204a87; } -span.fore_lt_blue { color: #729fcf; } -span.fore_green { color: #788600; font-weight: bold; } -span.fore_lt_green { color: #cbe134; } -span.fore_cyan { color: #c4a000; } /* using cyan for yellows */ -span.fore_lt_cyan { color: #fc994f; } -span.fore_red { color: #a40000; } -span.fore_lt_red { color: #ef2929; font-weight: bold; } -span.fore_purple { color: #5c3566; } -span.fore_lt_purple { color: #ad7fa8; } -span.fore_brown { color: #8f5972; } -span.fore_lt_brown { color: #b9b9de; } -span.back_black { background-color: #2e3436; } -span.back_dark_gray { background-color: #888a85; } -span.back_gray { background-color: #babdb6; } -span.back_white { background-color: #eeeeec; } -span.back_blue { background-color: #204a87; } -span.back_lt_blue { background-color: #729fcf; } -span.back_green { background-color: #788600; } -span.back_lt_green { background-color: #cbe134; } -span.back_cyan { background-color: #c4a000; } /* using cyan for yellows */ -span.back_lt_cyan { background-color: #fce94f; } -span.back_red { background-color: #a40000; } -span.back_lt_red { background-color: #ef2929; } -span.back_purple { background-color: #5c3566; } -span.back_lt_purple { background-color: #ad7fa8; } -span.back_brown { background-color: #8f5902; } -span.back_lt_brown { background-color: #b9b96e; } diff --git a/tryruby/script/about b/tryruby/script/about deleted file mode 100755 index 1eeb6eb..0000000 --- a/tryruby/script/about +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../config/boot', __FILE__) -$LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info" -require 'commands/about' diff --git a/tryruby/script/console b/tryruby/script/console deleted file mode 100755 index 235a1f2..0000000 --- a/tryruby/script/console +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../config/boot', __FILE__) -require 'commands/console' diff --git a/tryruby/script/dbconsole b/tryruby/script/dbconsole deleted file mode 100755 index 83c8436..0000000 --- a/tryruby/script/dbconsole +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../config/boot', __FILE__) -require 'commands/dbconsole' diff --git a/tryruby/script/destroy b/tryruby/script/destroy deleted file mode 100755 index 88d295f..0000000 --- a/tryruby/script/destroy +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../config/boot', __FILE__) -require 'commands/destroy' diff --git a/tryruby/script/generate b/tryruby/script/generate deleted file mode 100755 index 62a8a4c..0000000 --- a/tryruby/script/generate +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../config/boot', __FILE__) -require 'commands/generate' diff --git a/tryruby/script/performance/benchmarker b/tryruby/script/performance/benchmarker deleted file mode 100755 index 3bff809..0000000 --- a/tryruby/script/performance/benchmarker +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../../config/boot', __FILE__) -require 'commands/performance/benchmarker' diff --git a/tryruby/script/performance/profiler b/tryruby/script/performance/profiler deleted file mode 100755 index 0764057..0000000 --- a/tryruby/script/performance/profiler +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../../config/boot', __FILE__) -require 'commands/performance/profiler' diff --git a/tryruby/script/plugin b/tryruby/script/plugin deleted file mode 100755 index b82201f..0000000 --- a/tryruby/script/plugin +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../config/boot', __FILE__) -require 'commands/plugin' diff --git a/tryruby/script/runner b/tryruby/script/runner deleted file mode 100755 index be4c5d4..0000000 --- a/tryruby/script/runner +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../config/boot', __FILE__) -require 'commands/runner' diff --git a/tryruby/script/server b/tryruby/script/server deleted file mode 100755 index b9fcb71..0000000 --- a/tryruby/script/server +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../config/boot', __FILE__) -require 'commands/server' diff --git a/tryruby/test/functional/tryruby_controller_test.rb b/tryruby/test/functional/tryruby_controller_test.rb deleted file mode 100644 index 0b2649e..0000000 --- a/tryruby/test/functional/tryruby_controller_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' - -class TryrubyControllerTest < ActionController::TestCase - test "run" do - get :run, :cmd => "2 + 6" - assert_response :success - assert_equal "=> \e[1;20m8", @response.body - end -end diff --git a/tryruby/test/test_helper.rb b/tryruby/test/test_helper.rb deleted file mode 100644 index 24632d8..0000000 --- a/tryruby/test/test_helper.rb +++ /dev/null @@ -1,60 +0,0 @@ -ENV["RAILS_ENV"] = "test" -require File.expand_path(File.dirname(__FILE__) + "/../config/environment") -require 'test_help' -require 'rexml/document' -require 'hpricot' - -class Integer - def weeks - self * 7*24*60*60 - end -end - - -def run_script(session,line) - TryRuby.run_line(line) -end - -class TryRubyTestSession < TryRuby::Session - def initialize - @current_includes = [] - end - - attr_accessor :start_time, :current_statement - attr_accessor :nesting_level, :past_commands, :current_includes -end - -class ActiveSupport::TestCase - # Transactional fixtures accelerate your tests by wrapping each test method - # in a transaction that's rolled back on completion. This ensures that the - # test database remains unchanged so your fixtures don't have to be reloaded - # between every test method. Fewer database queries means faster tests. - # - # Read Mike Clark's excellent walkthrough at - # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting - # - # Every Active Record database supports transactions except MyISAM tables - # in MySQL. Turn off transactional fixtures in this case; however, if you - # don't care one way or the other, switching from MyISAM to InnoDB tables - # is recommended. - # - # The only drawback to using transactional fixtures is when you actually - # need to test transactions. Since your test is bracketed by a transaction, - # any transactions started in your code will be automatically rolled back. - #self.use_transactional_fixtures = true - - # Instantiated fixtures are slow, but give you @david where otherwise you - # would need people(:david). If you don't want to migrate your existing - # test cases which use the @david style and don't mind the speed hit (each - # instantiated fixtures translates to a database query per test method), - # then set this back to true. - #self.use_instantiated_fixtures = false - - # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. - # - # Note: You'll currently still have to declare fixtures explicitly in integration tests - # -- they do not yet inherit this setting - #fixtures :all - - # Add more helper methods to be used by all tests here... -end diff --git a/tryruby/test/unit/fake_f_s_test.rb b/tryruby/test/unit/fake_f_s_test.rb deleted file mode 100644 index deaa7b9..0000000 --- a/tryruby/test/unit/fake_f_s_test.rb +++ /dev/null @@ -1,94 +0,0 @@ -require 'test/test_helper' - -class FakeFSTest < Test::Unit::TestCase - def setup - FakeFS.activate! - FakeFS::FileSystem.clear - end - - def teardown - FakeFS.deactivate! - end - - def test_file_system_current_dir - assert_equal({}, FakeFS::FileSystem.current_dir) - Dir.mkdir "Home" - assert_equal({"Home"=>{}}, FakeFS::FileSystem.current_dir) - FakeFS::FileSystem.chdir "Home" do - Dir.mkdir "Sub" - assert_equal({"Sub" => {}}, FakeFS::FileSystem.current_dir) - end - assert_equal({"Home" => {"Sub" => {}}}, FakeFS::FileSystem.current_dir) - end - - def test_dir_entries_for_current_dir - assert_equal [".", ".."], Dir.entries(".") - end - - def test_dir_entries_for_root - assert_equal [".", ".."], Dir.entries("/") - end - - def test_dir_entries2 - Dir.mkdir "Home" - assert_equal [".", "..", "Home"], Dir.entries(".") - end - - def test_dir_entries3 - ["Home", "Libraries", "MouseHole", "Programs", "Tutorials"].each {|dir| Dir.mkdir dir } - assert_equal [".", "..", "Home", "Libraries", "MouseHole", "Programs", "Tutorials"], Dir.entries("/") - end - - def test_fake_dir_to_s_for_root_dir - assert_equal("/", FakeFS::FileSystem.current_dir.to_s) - end - - def test_fake_dir_to_s_for_sub_dir - Dir.mkdir "Home" - FakeFS::FileSystem.chdir "Home" do - assert_equal("/Home", FakeFS::FileSystem.current_dir.to_s) - end - end - - def test_fake_dir_to_s_for_sub_sub_dir - Dir.mkdir "Home" - FakeFS::FileSystem.chdir "Home" do - Dir.mkdir "Sub" - FakeFS::FileSystem.chdir "Sub" do - assert_equal("/Home/Sub", FakeFS::FileSystem.current_dir.to_s) - end - end - end - - def test_file_inspect - assert_equal("#", File.open("/comics.txt", "a") { |f| f << "a"}.inspect) - end - - def test_file_foreach - File.open("/comics.txt", "a") { |f| f << "a"} - lines = [] - File.foreach("/comics.txt") do |l| lines << l - end - assert_equal(["a"], lines) - end - - def test_file_expand_path_for_current_dir - assert_equal "/", File.expand_path(".") - Dir.mkdir "Home" - FakeFS::FileSystem.chdir "Home" do - assert_equal "/Home", File.expand_path(".") - end - end - - def test_file_expand_path_for_root_dir - assert_equal RealFile.expand_path("/"), File.expand_path("/") - end - - def test_file_expand_path_for_user_dir - assert_equal "/~", File.expand_path("~") - end - - def test_file_expand_path_for_user_dir_with_sub_dir - assert_equal "/~/test", File.expand_path("~/test") - end -end \ No newline at end of file diff --git a/tryruby/test/unit/nesting_level_test.rb b/tryruby/test/unit/nesting_level_test.rb deleted file mode 100644 index ea45dda..0000000 --- a/tryruby/test/unit/nesting_level_test.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'test_helper' - -# This tests the TryRubyBaseSession#calculate_nesting_level function -class NestingLevelTest < Test::Unit::TestCase - def test_finished_statement_should_return_0 - assert_equal(0, TryRuby.calculate_nesting_level("42")) - end - - def test_missing_end_should_return_1 - assert_equal(1, TryRuby.calculate_nesting_level("3.times do")) - end - - def test_missing_close_brace_should_return_1 - assert_equal(1, TryRuby.calculate_nesting_level("3.times {")) - end - - def test_open_class_should_return_1 - assert_equal(1, TryRuby.calculate_nesting_level("class BlogEntry")) - end - - def test_closed_class_should_return_0 - assert_equal(0, TryRuby.calculate_nesting_level("class BlogEntry\nend")) - end - - def test_2_unclosed_dos_should_return_2 - assert_equal(2, TryRuby.calculate_nesting_level("3.times do\n4.times do")) - end - - def test_mix_of_opened_statements - test_str = <<-EOF - class MyClass - def mymethod - 3.times do - 8.times { - EOF - assert_equal(4, TryRuby.calculate_nesting_level(test_str)) - end - - def test_open_and_close_on_same_line_should_return_0 - assert_equal(0, TryRuby.calculate_nesting_level("3.times { puts 'lol' }")) - assert_equal(0, TryRuby.calculate_nesting_level("3.times do |v| puts 'lol'; end")) - end - - def test_half_statement - assert_equal(1, TryRuby.calculate_nesting_level('true and')) - assert_equal(1, TryRuby.calculate_nesting_level('amethod(')) - end - -end diff --git a/tryruby/test/unit/output_test.rb b/tryruby/test/unit/output_test.rb deleted file mode 100644 index 3c5ef55..0000000 --- a/tryruby/test/unit/output_test.rb +++ /dev/null @@ -1,55 +0,0 @@ -require 'test_helper' - -# tests if the TryRubyOutput translation, for use with mouseapp_2.js and similar -# is working correctly -class OutputTest < Test::Unit::TestCase - def test_simple_result - t = TryRuby::Output.standard(result: [12,24]) - assert_equal("=> \033[1;20m[12, 24]", t.format) - end - - def test_result_and_output - t = TryRuby::Output.standard(result: 333, output: "hello") - assert_equal("hello=> \033[1;20m333", t.format) - end - - def test_error - begin - 40.reverse - rescue Exception => e - t = TryRuby::Output.error(error: e) - end - assert_equal("\033[1;33mNoMethodError: undefined method `reverse' for 40:Fixnum", - t.format) - end - - def test_error_with_output - begin - 40.reverse - rescue Exception => e - t = TryRuby::Output.error(error: e, output: "hello\nworld") - end - assert_equal("hello\nworld\033[1;33mNoMethodError: undefined method `reverse' for 40:Fixnum", - t.format) - end - - def test_illegal - t = TryRuby::Output.illegal - assert_equal("\033[1;33mYou aren't allowed to run that command!", - t.format) - end - - - - def test_line_continuation - t = TryRuby::Output.line_continuation(3) - assert_equal(".." * 3, t.format) - end - - def xtest_javascript - t = TryRuby::Output.javascript(javascript: 'alert("hello")') - # expected ends in a space to stop a visual problem in mouseapp - assert_equal("\033[1;JSmalert(\"hello\")\033[m ", t.format) - end - -end \ No newline at end of file diff --git a/tryruby/test/unit/try_ruby_test.rb b/tryruby/test/unit/try_ruby_test.rb deleted file mode 100644 index 35da8e6..0000000 --- a/tryruby/test/unit/try_ruby_test.rb +++ /dev/null @@ -1,500 +0,0 @@ -require 'test_helper' - -class FakeFS::File - def self.foreach(path) - self.read(path).each_line {|line| yield(line) } - end -end -$session = nil - -class TryRubyTest < Test::Unit::TestCase - # a test helper that simplifies testing the tryruby interpretor. - # It takes one optional argument session, which is the session to use for - # the test. - # It then takes a block. In the block a method input is available which is used - # to send a line to the interpretor and test the output. - # - # input takes one mandatory argument, the line, and some optional args: - # - output: The output of line should match this (defaults to "") - # - javascript: The line generated a javascript function, and should match this - # - error: The line generated an error and should match this - # - result: The line didn't generate an error or javascript, and should match this - # (defaults to nil if not supplied) - # - line_continuation: The line didn't complete, the current indent level - # should be equal to this (use true if you aren't interested in testing this) - # output, error, javascript and result can take either a: - # - Class: Tests if is the given type - # - Proc: Proc should take one param (the ) and run assertions with that value - # - Regexp: Tests (a string) against the regexp - # - : should equal - def tryruby_session(session = TryRubyTestSession.new, &block) - input_o = TryRubySession_Input.new - input_o.session = session - input_o.test = self - input_o.instance_eval(&block) - - end - - - def test_lesson1 - tryruby_session do - input '2 + 6' , result: 8 - input '"Jimmy"' , result: "Jimmy" - input '"Jimmy".reverse' , result: "ymmiJ" - input '"Jimmy".length' , result: 5 - input '"Jimmy" * 5' , result: "JimmyJimmyJimmyJimmyJimmy" - end - end - - # tests statements that should end with a line continuation - def test_line_continuations - tryruby_session do - input '3.times do |v|' , line_continuation: 1 - input '3.times { |v|', line_continuation: 2 - input '}; end', result: Proc.new {true} - input 'class MyClass', line_continuation: 1 - input "def mymethod", line_continuation: 2 - input "end; end", result: Proc.new{true} - end - end - - def test_lines_with_semicolons - tryruby_session do - input '3; 4', result: 4 - input 'puts "hello"; a = 4; 5', output: "hello\n", result: 5 - input 'a', result: 4 - input '; 42', result: 42 - end - end - - - def test_errors - tryruby_session do - input 'asdf', error: NameError, output: "" - input 'print "hello"; asdf', error: NameError, output: "hello" - end - end - - # tests statements that shouldn't end with a line continuation - def test_shouldnt_have_line_continuation - tryruby_session do - input "'helloclassend'", result: "helloclassend" - input "3.class", result: Class - end - end - - def test_illegal_ops - $session = TryRubyTestSession.new - tryruby_session $session do - input '`cat /etc/passwd`', illegal: true - input 'require "popup"', result: Proc.new{} - input '%x(cat /etc/passwd)', illegal: true - end - end - - - def test_lesson2 - tryruby_session do - input '40.reverse', error: NoMethodError - input '40.to_s.reverse', result: "04" - input '[]', result: [] - input '[12,47,35]', result: [12,47,35] - input '[12,47,35].max', result: 47 - input 'ticket = [12,47,35]', result: [12,47,35] - input 'ticket', result: [12,47,35] - input 'ticket.sort!', result: [12,35,47] - end - end - - def test_lesson3 - poem = <<-EOF -My toast has flown from my hand -And my toast has gone to the -moon. -But when I saw it on television, -Planting our flag on Halley's -comet, -More still did I want to eat it. -EOF - - tryruby_session do - # the below line should be done automatically by the interpretor - # when help 3 is loaded - input "poem = #{poem.inspect}", result: Proc.new{} - input 'print poem', output: poem - input "poem['toast'] = 'honeydew'", result: "honeydew" - input 'print poem', output: (poem['toast'] = 'honeydew'; poem) - input 'poem.reverse', result: poem.reverse - input 'poem.lines.to_a.reverse', result: poem.lines.to_a.reverse - input 'print poem.lines.to_a.reverse.join', output: poem.lines.to_a.reverse.join - end - end - - def test_lesson4 - tryruby_session do - input 'books = {}', result: {} - - input 'books["Gravity\'s Rainbow"] = :splendid', - result: :splendid - - input 'books["a"] = :mediocre', result: :mediocre - input 'books["b"] = :mediocre', result: :mediocre - input 'books["c"] = :mediocre', result: :mediocre - input 'books.length', result: 4 - input 'books["Gravity\'s Rainbow"]', result: :splendid - input 'books.keys', result: ["Gravity's Rainbow", "a", "b", "c"] - input 'ratings = Hash.new {0}', result: {} - - input 'books.values.each { |rate| ratings[rate] += 1 }', - result: [:splendid, :mediocre, :mediocre, :mediocre] - - input '5.times {print "Odelay!" }', - result: 5, - output: 'Odelay!Odelay!Odelay!Odelay!Odelay!' - - end - end - - def test_lesson5 - tryruby_session do - input 'Dir.entries "/"', - result: [".", "..", "Home", "Libraries", "MouseHole", "Programs", "Tutorials", - "comics.txt"] - input 'Dir["/*.txt"]', result: ["/comics.txt"] - comics_txt_text = <", value.inspect - } - input 'File.mtime("/Home/comics.txt")', result: Time - input 'File.mtime("/Home/comics.txt").hour', result: Fixnum - end - - end - - def test_lesson6 - $session = TryRubyTestSession.new - tryruby_session $session do - input 'def load_comics( path )', line_continuation: 1 - input 'comics = {}', line_continuation: 1 - input 'File.foreach(path) do |line|', line_continuation: 2 - input "name, url = line.split(': ')", line_continuation: 2 - input 'comics[name] = url.strip', line_continuation: 2 - input 'end', line_continuation: 1 - input 'comics', line_continuation: 1 - input 'end', result: nil - - input "comics = load_comics('/comics.txt')", result: - {"Achewood"=>"http://achewood.com/", - "Dinosaur Comics"=>"http://qwantz.com/", - "Perry Bible Fellowship"=>"http://cheston.com/pbf/archive.html" , - "Get Your War On"=>"http://mnftiu.cc/"} - - input "require 'popup'", result: Proc.new {} - input 'Popup.goto "http://google.com/"', :output => nil, - javascript: "window.irb.options.popup_goto(\"http://google.com/\")" - - input 'Popup.make {', line_continuation: 1 - input 'h1 "My Links"', line_continuation: 1 - input 'link "Go to Google", "http://google.com/"', line_continuation: 1 - input '}', :output => nil, javascript: - "window.irb.options.popup_make" + - "(\"

    My Links

    " + - "Go to Google\")" - - input 'Popup.make do', line_continuation: 1 - input 'h1 "Things To Do"', line_continuation: 1 - input 'list do', line_continuation: 2 - input 'p "Try out Ruby"', line_continuation: 2 - input 'p "Ride a tiger"', line_continuation: 2 - input 'p "(down River Euphrates)"', line_continuation: 2 - input 'end', line_continuation: 1 - expected = <Things To Do -
    • Try out Ruby
    • -
    • Ride a tiger
    • -
    • (down River Euphrates) -
    ") -EOF - input 'end', :output => nil, javascript: expected.gsub("\n", "") - -input 'Popup.make do', line_continuation: 1 -input 'h1 "Comics on the Web"', line_continuation: 1 -input 'list do', line_continuation: 2 -input 'comics.each do |name, url|', line_continuation: 3 -input 'link name, url', line_continuation: 3 -input 'end', line_continuation: 2 -input 'end', line_continuation: 1 -input 'end', :output => nil, javascript: Proc.new { |v| - matches = v.match(/^window.irb.options.popup_make\("(.*)"\)/) - assert_not_nil(matches) - html = matches[1].gsub(/\\(.)/, '\1') - doc = Hpricot(html) - assert_equal("Comics on the Web", (doc/:h1).inner_html) - list_items = (doc/:li/:a).map do |elem| - {href: elem.attributes['href'], title: elem.inner_html} - end - assert_equal({href: "http://achewood.com/", title: "Achewood"}, - list_items[0]) - assert_equal({href: "http://qwantz.com/", title: "Dinosaur Comics"}, - list_items[1]) - assert_equal({href: "http://cheston.com/pbf/archive.html", - title: "Perry Bible Fellowship"}, - list_items[2]) - assert_equal({href: "http://mnftiu.cc/", title: "Get Your War On"}, - list_items[3]) - - #assert_equal("", html) - } - - - - - end - - end - - - - - def test_lesson7_and_8 - $session = TryRubyTestSession.new - tryruby_session $session do - input 'Hash.new', result: {} - input 'class BlogEntry', line_continuation: 1 - input 'attr_accessor :title, :time, :fulltext, :mood', line_continuation: 1 - input 'end', result: nil - - # can't test for BlogEntry directly, as it isn't defined in this scope - input 'entry = BlogEntry.new', result: Proc.new {|v| - assert_equal("BlogEntry", v.class.name, - "line should result in a BlogEntry") - } - - input 'entry.title = "Today Mt. Hood Was Stolen!"', result: "Today Mt. Hood Was Stolen!" - - input 'entry.time = Time.now', result: Time - - input 'entry.mood = :sick', result: :sick - - str = <

    My Blog

    -
      -
    • Today Mt. Hood Was Stolen!

    • -
    • I can't believe Mt. Hood was stolen! I am speechless! - It was stolen by a giraffe who drove away in his - Cadillac Seville very nonchalant!!
    • -
    • I Left my Hoodie on the Mountain!

    • -
    • I am never going back to that mountain and - I hope a giraffe steals it.
    • -
    - EOF - - expected_xml = REXML::Document.new(expected_str.strip) - assert_match(/^window.irb.options.popup_make\(".*"\)?/m, - v, - "testing that Popup calls the correct javascript function") - actual_str = v.match(/^window.irb.options.popup_make\("(.*)"\);?/m)[1] - actual_str = "#{actual_str}" - actual_xml = REXML::Document.new(actual_str.strip) - assert_equal(expected_xml.write(StringIO.new).to_s, - actual_xml.write(StringIO.new).to_s, - "testing that html used with the javascript popup " + - "function is correct") - } - - input 'Time.now - 2.weeks', result: Time - - input 'File.read("/MouseHole/flickrpedia.user.rb")', result: String - - end # tryruby_session - - end # lesson6_and_7 - - - - - # class that performs the tests with the input sections of the - # tryrubysession test helper - class TryRubySession_Input - attr_accessor :session, :test - - # performs an assertion - # actual is the value being tested - # expected should either be a - # - Class: the class of actual should be expected - # - Regexp: actual (a string) should match the expected Regexp - # - Proc: A proc that takes one argument, actual. Assertions can - # be used inside that proc, eg assert_equal, assert. - # - : actual should equal expected - # name is the name of the input (result, output, javascript, error), - # used in messages - # line is the line being tested - def do_assert(actual, expected, name, line) - case expected - when Class - @test.assert_kind_of(expected, actual, - "Testing line `#{line}': #{name} should be a #{expected}") - when Regexp - @test.assert_match(expected, actual, - "Testing line `#{line}': #{name} should match #{expected}") - when Proc - @backtrace_pos = 0 - @test.instance_exec(actual, &expected) - - # @test.assert(expected.call(actual), - # "Testing line `#{line}': #{message} \n[#{actual}]") - else - @test.assert_equal(expected,actual, - "Testing line `#{line}' for correct #{name}") - end - end - - # runs the line using this objects session. - def do_test(line) - o = Object.new - # store the initial constants of Object - initial_constants = Object.constants - session = @session - - thread = Thread.new do - o.instance_eval do - result = run_script(session, line) - end - end - - result = thread.value - # restores require to its old functionality - #def Object.require(str) - # old_require(str) - #end - - # next 4 lines will revert Object to the way it was before - # run_script - diff_constants = Object.constants - initial_constants - diff_constants.each do |constant| - Object.send(:remove_const, constant) rescue nil - end - result - end - - - # The input function, used with the tryruby_session test helper - # see tryruby_session for more details - def input(line, params = {}) - @backtrace_pos = 2 - params[:output] = "" unless params.has_key?(:output) - params[:result] ||= nil - params[:error] ||= nil - result = do_test(line) - begin - if params[:illegal] then - @test.assert_equal(:illegal, result.type, - "Testing if line `#{line}' resulted in an illegal operation") - elsif params[:error] then - @test.assert_equal(:error, result.type, - "Testing if line `#{line}` resulted in an error") - do_assert(result.error, params[:error], "error", line) - do_assert(result.output, params[:output], "output", line) - elsif params[:javascript] then - @test.assert_nil(result.error, - "Testing line `#{line}' to ensure there was no error") - do_assert(result.javascript, params[:javascript], "javascript", line) - do_assert(result.output, params[:output], "output", line) - elsif params[:line_continuation] - @test.assert_equal(:line_continuation, result.type, - "Testing if line `#{line}' resulted in a line continuation") - if params[:line_continuation] != true then - @test.assert_equal(result.indent_level, params[:line_continuation], - "Testing if line `#{line}' triggered enough autoindent") - end - else - @test.assert_nil(result.error, - "Testing line `#{line}' to ensure there was no error") - do_assert(result.result, params[:result], "result" , line) - do_assert(result.output, params[:output], "output", line) - end - - rescue Test::Unit::AssertionFailedError => e - p e - - new_bt = Test::Unit::Util::BacktraceFilter.filter_backtrace(e.backtrace) - new_bt = new_bt[@backtrace_pos..@backtrace_pos] - e.set_backtrace(new_bt) - raise e - - end - end - end -end - diff --git a/tryruby/tmp/pids/server.pid b/tryruby/tmp/pids/server.pid deleted file mode 100644 index ade6218..0000000 --- a/tryruby/tmp/pids/server.pid +++ /dev/null @@ -1 +0,0 @@ -4772 \ No newline at end of file diff --git a/vendor/plugins/.gitkeep b/vendor/plugins/.gitkeep new file mode 100644 index 0000000..e69de29