diff options
Diffstat (limited to 'railties')
80 files changed, 2543 insertions, 1368 deletions
diff --git a/railties/README b/railties/README index ffed467872..e4de4a5cb2 100644 --- a/railties/README +++ b/railties/README @@ -250,7 +250,7 @@ app/views/layouts app/helpers Holds view helpers that should be named like weblogs_helper.rb. These are generated - for you automatically when using rails generate for controllers. Helpers can be used to + for you automatically when using <tt>rails generate</tt> for controllers. Helpers can be used to wrap functionality for your views into methods. config @@ -277,7 +277,7 @@ script Helper scripts for automation and generation. test - Unit and functional tests along with fixtures. When using the rails generate command, template + Unit and functional tests along with fixtures. When using the <tt>rails generate</tt> scripts, template test files will be generated for you and placed in this directory. vendor diff --git a/railties/Rakefile b/railties/Rakefile index ea0b8ef8fb..f32a794544 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -90,11 +90,11 @@ end # Publishing ------------------------------------------------------- -desc "Publish the rails gem" -task :pgem => [:gem] do - require 'rake/contrib/sshpublisher' - Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload - `ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'` +desc "Release to gemcutter" +task :release => :package do + require 'rake/gemcutter' + Rake::Gemcutter::Tasks.new(spec).define + Rake::Task['gem:push'].invoke end desc "Publish the guides" @@ -105,15 +105,3 @@ task :pguides => :generate_guides do Rake::SshFilePublisher.new("web.rubyonrails.org", "/u/sites/guides.rubyonrails.org/public", "pkg", "guides.gz").upload `ssh web.rubyonrails.org 'cd /u/sites/guides.rubyonrails.org/public/ && tar -xvzf guides.gz && mv guides/output/* . && rm -rf guides*'` end - -desc "Publish the release files to RubyForge." -task :release => [ :package ] do - require 'rake/contrib/rubyforgepublisher' - require 'rubyforge' - - packages = %w( gem ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" } - - rubyforge = RubyForge.new - rubyforge.login - rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages) -end diff --git a/railties/guides/images/edge_badge.png b/railties/guides/images/edge_badge.png Binary files differnew file mode 100644 index 0000000000..cddd46c4b8 --- /dev/null +++ b/railties/guides/images/edge_badge.png diff --git a/railties/guides/rails_guides/generator.rb b/railties/guides/rails_guides/generator.rb index b931dd6c28..7c8d26a1ea 100644 --- a/railties/guides/rails_guides/generator.rb +++ b/railties/guides/rails_guides/generator.rb @@ -1,4 +1,5 @@ require 'set' +require 'fileutils' module RailsGuides class Generator @@ -49,16 +50,19 @@ module RailsGuides if guide =~ /\.textile\.erb$/ # Generate the erb pages with textile formatting - e.g. index/authors result = view.render(:layout => 'layout', :file => guide) - f.write textile(result) + result = textile(result) else body = File.read(File.join(view_path, guide)) body = set_header_section(body, @view) body = set_index(body, @view) result = view.render(:layout => 'layout', :text => textile(body).html_safe) - f.write result + warn_about_broken_links(result) if ENV.key?("WARN_BROKEN_LINKS") end + + result = insert_edge_badge(result) if ENV.key?('INSERT_EDGE_BADGE') + f.write result end end @@ -168,5 +172,9 @@ module RailsGuides end end end + + def insert_edge_badge(html) + html.sub(/<body[^>]*>/, '\&<img src="images/edge_badge.png" style="position:fixed; right:0px; top:0px; border:none; z-index:100"/>') + end end end diff --git a/railties/guides/source/3_0_release_notes.textile b/railties/guides/source/3_0_release_notes.textile index 7b85e90238..73479b3c0f 100644 --- a/railties/guides/source/3_0_release_notes.textile +++ b/railties/guides/source/3_0_release_notes.textile @@ -25,8 +25,9 @@ TIP: To install the Rails 3 prerelease beta using rubygems you have to install a <shell> # Use sudo if your setup requires it gem install tzinfo builder i18n memcache-client rack \ - rake rack-test rack-mount erubis mail text-format \ + rake rack-test erubis mail text-format \ thor bundler +gem install rack-mount -v=0.4 gem install rails --pre </shell> @@ -50,8 +51,8 @@ h4. script/* replaced by script/rails The new <tt>script/rails</tt> replaces all the scripts that used to be in the <tt>script</tt> directory. You do not run <tt>script/rails</tt> directly though, the +rails+ command detects it is being invoked in the root of a Rails application and runs the script for you. Intended usage is: <shell> -rails console # => ./script/console -rails g scaffold post title:string # => ./script/generate scaffold post title:string +rails console # instead of script/console +rails g scaffold post title:string # instead of script/generate scaffold post title:string </shell> Run <tt>rails --help</tt> for a list of all the options. @@ -529,8 +530,8 @@ The security patch for REXML remains in Active Support because early patchlevels The following methods have been removed because they are no longer used in the framework: * +Kernel#daemonize+ -* <tt>Object#remove_subclasses_of</tt>, <tt>Object#subclasses_of</tt>, <tt>Object#extend_with_included_modules_from</tt>, <tt>Object#extended_by</tt> -* <tt>Class#subclasses</tt>, <tt>Class#reachable?</tt>, <tt>Class#remove_class</tt> +* <tt>Object#remove_subclasses_of</tt> <tt>Object#extend_with_included_modules_from</tt>, <tt>Object#extended_by</tt> +* <tt>Class#remove_class</tt> * <tt>Regexp#number_of_captures</tt>, <tt>Regexp.unoptionalize</tt>, <tt>Regexp.optionalize</tt>, <tt>Regexp#number_of_captures</tt> diff --git a/railties/guides/source/action_mailer_basics.textile b/railties/guides/source/action_mailer_basics.textile index 9e5e01ef2f..e5ba8380e8 100644 --- a/railties/guides/source/action_mailer_basics.textile +++ b/railties/guides/source/action_mailer_basics.textile @@ -19,7 +19,7 @@ h4. Walkthrough to Generating a Mailer h5. Create the Mailer <shell> -./script/generate mailer UserMailer +rails generate mailer UserMailer create app/mailers/user_mailer.rb invoke erb create app/views/user_mailer @@ -111,7 +111,7 @@ Let's see how we would go about wiring it up using an observer. First off, we need to create a simple +User+ scaffold: <shell> -$ script/generate scaffold user name:string email:string login:string +$ rails generate scaffold user name:string email:string login:string $ rake db:migrate </shell> @@ -333,7 +333,7 @@ Receiving and parsing emails with Action Mailer can be a rather complex endeavou * Implement a +receive+ method in your mailer. -* Configure your email server to forward emails from the address(es) you would like your app to receive to +/path/to/app/script/runner 'UserMailer.receive(STDIN.read)'+. +* Configure your email server to forward emails from the address(es) you would like your app to receive to +/path/to/app/script/rails runner 'UserMailer.receive(STDIN.read)'+. Once a method called +receive+ is defined in any mailer, Action Mailer will parse the raw incoming email into an email object, decode it, instantiate a new mailer, and pass the email object to the mailer +receive+ instance method. Here's an example: diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index 4aa43a8f3c..a517193cea 100644 --- a/railties/guides/source/action_view_overview.textile +++ b/railties/guides/source/action_view_overview.textile @@ -33,13 +33,13 @@ gem install actionpack gem install rack </shell> -Now we'll create a simple "Hello World" application that uses the +titleize+ method provided by Action View. +Now we'll create a simple "Hello World" application that uses the +titleize+ method provided by Active Support. *hello_world.rb:* <ruby> require 'rubygems' -require 'action_view' +require 'active_support/core_ext/string/inflections' require 'rack' def hello_world(env) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 2d05e66307..11f0bc956f 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -6,6 +6,62 @@ By referring to this guide you will learn the extensions to the Ruby core classe endprologue. +h3. How to Load Core Extensions + +In order to have a near zero default footprint, Active Support does not load anything by default. It is broken in small pieces so that you may load just what you need, and also has some convenience entry points to load related extensions in one shot, even everything. + +Thus, after a simple require like: + +<ruby> +require 'active_support' +</ruby> + +objects do not even respond to +blank?+, let's see how to load its definition. + +h4. Cherry-picking a Definition + +The most lightweight way to get +blank?+ is to cherry-pick the file that defines it. + +For every single method defined as a core extension this guide has a note that says where is such a method defined. In the case of +blank?+ the note reads: + +NOTE: Defined in +active_support/core_ext/object/blank.rb+. + +That means that this single call is enough: + +<ruby> +require 'active_support/core_ext/object/blank' +</ruby> + +Active Support has been carefully revised so that cherry-picking a file loads only strictly needed dependencies, if any. + +h4. Loading Grouped Core Extensions + +The next level is to simply load all extensions to +Object+. As a rule of thumb, extensions to +SomeClass+ are available in one shot by loading +active_support/core_ext/some_class+. + +Thus, if that would do, to have +blank?+ available we could just load all extensions to +Object+: + +<ruby> +require 'active_support/core_ext/object' +</ruby> + +h4. Loading All Core Extensions + +You may prefer just to load all core extensions, there is a file for that: + +<ruby> +require 'active_support/core_ext' +</ruby> + +h4. Loading All Active Support + +And finally, if you want to have all Active Support available just issue: + +<ruby> +require 'active_support/all' +</ruby> + +That does not even put the entire Active Support in memory upfront indeed, some stuff is configured via +autoload+, so it is only loaded if used. + h3. Extensions to All Objects h4. +blank?+ and +present?+ @@ -138,16 +194,19 @@ end NOTE: Defined in +active_support/core_ext/object/try.rb+. -h4. +metaclass+ +h4. +singleton_class+ -The method +metaclass+ returns the singleton class on any object: +The method +singleton_class+ returns the singleton class of the receiver: <ruby> -String.metaclass # => #<Class:String> -String.new.metaclass # => #<Class:#<String:0x17a1d1c>> +String.singleton_class # => #<Class:String> +String.new.singleton_class # => #<Class:#<String:0x17a1d1c>> </ruby> -NOTE: Defined in +active_support/core_ext/object/metaclass.rb+. +WARNING: Fixnums and symbols have no singleton classes, +singleton_class+ +raises +TypeError+ on them. + +NOTE: Defined in +active_support/core_ext/object/singleton_class.rb+. h4. +class_eval(*args, &block)+ @@ -168,7 +227,7 @@ class Proc end </ruby> -NOTE: Defined in +active_support/core_ext/object/metaclass.rb+. +NOTE: Defined in +active_support/core_ext/object/singleton_class.rb+. h4. +acts_like?(duck)+ @@ -323,6 +382,40 @@ TIP: Since +with_options+ forwards calls to its receiver they can be nested. Eac NOTE: Defined in +active_support/core_ext/object/with_options.rb+. +h5. +subclasses_of+ + +The method +subclasses_of+ receives an arbitrary number of class objects and returns all their anonymous or reachable descendants as a single array: + +<ruby> +class C; end +subclasses_of(C) # => [] + +subclasses_of(Integer) # => [Bignum, Fixnum] + +module M + class A; end + class B1 < A; end + class B2 < A; end +end + +module N + class C < M::B1; end +end + +subclasses_of(M::A) # => [N::C, M::B2, M::B1] +</ruby> + +The order in which these classes are returned is unspecified. The returned collection may have duplicates: + +<ruby> +subclasses_of(Numeric, Integer) +# => [Bignum, Float, Fixnum, Integer, Date::Infinity, Rational, BigDecimal, Bignum, Fixnum] +</ruby> + +See also +Class#subclasses+ in "Extensions to +Class+ FIX THIS LINK":FIXME. + +NOTE: Defined in +active_support/core_ext/object/extending.rb+. + h4. Instance Variables Active Support provides several methods to ease access to instance variables. @@ -341,7 +434,7 @@ end C.new(0, 1).instance_variable_names # => ["@y", "@x"] </ruby> -WARNING: The order in which the names are returned is unespecified, and it indeed depends on the version of the interpreter. +WARNING: The order in which the names are returned is unspecified, and it indeed depends on the version of the interpreter. NOTE: Defined in +active_support/core_ext/object/instance_variables.rb+. @@ -815,6 +908,85 @@ The method receives the name of an action, and a +:with+ option with code. The c NOTE: Defined in +active_support/core_ext/module/synchronization.rb+. +h4. Reachable + +A named module is reachable if it is stored in its correspoding constant. It means you can reach the module object via the constant. + +That is what ordinarily happens, if a module is called "M", the +M+ constant exists and holds it: + +<ruby> +module M +end + +M.reachable? # => true +</ruby> + +But since constants and modules are indeed kind of decoupled, module objects can become unreachable: + +<ruby> +module M +end + +orphan = Object.send(:remove_const, :M) + +# The module object is orphan now but it still has a name. +orphan.name # => "M" + +# You cannot reach it via the constant M because it does not even exist. +orphan.reachable? # => false + +# Let's define a module called "M" again. +module M +end + +# The constant M exists now again, and it stores a module +# object called "M", but it is a new instance. +orphan.reachable? # => false +</ruby> + +NOTE: Defined in +active_support/core_ext/module/reachable.rb+. + +h4. Anonymous + +A module may or may not have a name: + +<ruby> +module M +end +M.name # => "M" + +N = Module.new +N.name # => "N" + +Module.new.name # => "" in 1.8, nil in 1.9 +</ruby> + +You can check whether a module has a name with the predicate +anonymous?+: + +<ruby> +module M +end +M.anonymous? # => false + +Module.new.anonymous? # => true +</ruby> + +Note that being unreachable does not imply being anonymous: + +<ruby> +module M +end + +m = Object.send(:remove_const, :M) + +m.reachable? # => false +m.anonymous? # => false +</ruby> + +though an anonymous module is unreachable by definition. + +NOTE: Defined in +active_support/core_ext/module/anonymous.rb+. + h3. Extensions to +Class+ h4. Class Attributes @@ -943,6 +1115,39 @@ If for whatever reason an application loads the definition of a mailer class and NOTE: Defined in +active_support/core_ext/class/delegating_attributes.rb+. +h4. Descendants + +h5. +subclasses+ + +The +subclasses+ method returns the names of all the anonymous or reachable descendants of its receiver as an array of strings: + +<ruby> +class C; end +C.subclasses # => [] + +Integer.subclasses # => ["Bignum", "Fixnum"] + +module M + class A; end + class B1 < A; end + class B2 < A; end +end + +module N + class C < M::B1; end +end + +M::A.subclasses # => ["N::C", "M::B2", "M::B1"] +</ruby> + +The order in which these class names are returned is unspecified. + +See also +Object#subclasses_of+ in "Extensions to All Objects FIX THIS LINK":FIXME. + +WARNING: This method is redefined in some Rails core classes. + +NOTE: Defined in +active_support/core_ext/class/subclasses.rb+. + h3. Extensions to +String+ h4. Output Safety @@ -1100,6 +1305,338 @@ The call +str.last(n)+ is equivalent to +str.from(-n)+ if +n+ > 0, and returns a NOTE: Defined in +active_support/core_ext/string/access.rb+. +h4. Inflections + +h5. +pluralize+ + +The method +pluralize+ returns the plural of its receiver: + +<ruby> +"table".pluralize # => "tables" +"ruby".pluralize # => "rubies" +"equipment".pluralize # => "equipment" +</ruby> + +As the previous example shows, Active Support knows some irregular plurals and uncountable nouns. Builtin rules can be extended in +config/initializers/inflections.rb+. That file is generated by the +rails+ command and has instructions in comments. + +Active Record uses this method to compute the default table name that corresponds to a model: + +<ruby> +# active_record/base.rb +def undecorated_table_name(class_name = base_class.name) + table_name = class_name.to_s.demodulize.underscore + table_name = table_name.pluralize if pluralize_table_names + table_name +end +</ruby> + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +singularize+ + +The inverse of +pluralize+: + +<ruby> +"tables".singularize # => "table" +"rubies".singularize # => "ruby" +"equipment".singularize # => "equipment" +</ruby> + +Associations compute the name of the corresponding default associated class using this method: + +<ruby> +# active_record/reflection.rb +def derive_class_name + class_name = name.to_s.camelize + class_name = class_name.singularize if collection? + class_name +end +</ruby> + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +camelize+ + +The method +camelize+ returns its receiver in camel case: + +<ruby> +"product".camelize # => "Product" +"admin_user".camelize # => "AdminUser" +</ruby> + +As a rule of thumb you can think of this method as the one that transforms paths into Ruby class or module names, where slashes separate namespaces: + +<ruby> +"backoffice/session".camelize # => "Backoffice::Session" +</ruby> + +For example, Action Pack uses this method to load the class that provides a certain session store: + +<ruby> +# action_controller/metal/session_management.rb +def session_store=(store) + if store == :active_record_store + self.session_store = ActiveRecord::SessionStore + else + @@session_store = store.is_a?(Symbol) ? + ActionDispatch::Session.const_get(store.to_s.camelize) : + store + end +end +</ruby> + ++camelize+ accepts an optional argument, it can be +:upper+ (default), or +:lower+. With the latter the first letter becomes lowercase: + +<ruby> +"visual_effect".camelize(:lower) # => "visualEffect" +</ruby> + +That may be handy to compute method names in a language that follows that convention, for example JavaScript. + ++camelize+ is aliased to +camelcase+. + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +underscore+ + +The method +underscore+ is the inverse of +camelize+, explained above: + +<ruby> +"Product".underscore # => "product" +"AdminUser".underscore # => "admin_user" +</ruby> + +Also converts "::" back to "/": + +<ruby> +"Backoffice::Session".underscore # => "backoffice/session" +</ruby> + +and understands strings that start with lowercase: + +<ruby> +"visualEffect".underscore # => "visual_effect" +</ruby> + ++underscore+ accepts no argument though. + +Rails class and module autoloading uses +underscore+ to infer the relative path without extension of a file that would define a given missing constant: + +<ruby> +# active_support/dependencies.rb +def load_missing_constant(from_mod, const_name) + ... + qualified_name = qualified_name_for from_mod, const_name + path_suffix = qualified_name.underscore + ... +end +</ruby> + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +titleize+ + +The method +titleize+ capitalizes the words in the receiver: + +<ruby> +"alice in wonderland".titleize # => "Alice In Wonderland" +"fermat's enigma".titleize # => "Fermat's Enigma" +</ruby> + ++titleize+ is aliased to +titlecase+. + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +dasherize+ + +The method +dasherize+ replaces the underscores in the receiver with dashes: + +<ruby> +"name".dasherize # => "name" +"contact_data".dasherize # => "contact-data" +</ruby> + +The XML serializer of models uses this method to dasherize node names: + +<ruby> +# active_model/serializers/xml.rb +def reformat_name(name) + name = name.camelize if camelize? + dasherize? ? name.dasherize : name +end +</ruby> + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +demodulize+ + +Given a string with a qualified constant reference expression, +demodulize+ returns the very constant name, that is, the rightmost part of it: + +<ruby> +"Product".demodulize # => "Product" +"Backoffice::UsersController".demodulize # => "UsersController" +"Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils" +</ruby> + +Active Record for example uses this method to compute the name of a counter cache column: + +<ruby> +# active_record/reflection.rb +def counter_cache_column + if options[:counter_cache] == true + "#{active_record.name.demodulize.underscore.pluralize}_count" + elsif options[:counter_cache] + options[:counter_cache] + end +end +</ruby> + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +parameterize+ + +The method +parameterize+ normalizes its receiver in a way that can be used in pretty URLs. + +<ruby> +"John Smith".parameterize # => "john-smith" +"Kurt Gödel".parameterize # => "kurt-godel" +</ruby> + +In fact, the result string is wrapped in an instance of +ActiveSupport::Multibyte::Chars+. + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +tableize+ + +The method +tableize+ is +underscore+ followed by +pluralize+. + +<ruby> +"Person".tableize # => "people" +"Invoice".tableize # => "invoices" +"InvoiceLine".tableize # => "invoice_lines" +</ruby> + +As a rule of thumb, +tableize+ returns the table name that corresponds to a given model for simple cases. The actual implementation in Active Record is not straight +tableize+ indeed, because it also demodulizes de class name and checks a few options that may affect the returned string. + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +classify+ + +The method +classify+ is the inverse of +tableize+. It gives you the class name corresponding to a table name: + +<ruby> +"people".classify # => "Person" +"invoices".classify # => "Invoice" +"invoice_lines".classify # => "InvoiceLine" +</ruby> + +The method understands qualified table names: + +<ruby> +"highrise_production.companies".classify # => "Company" +</ruby> + +Note that +classify+ returns a class name as a string. You can get the actual class object invoking +constantize+ on it, explained next. + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +constantize+ + +The method +constantize+ resolves the constant reference expression in its receiver: + +<ruby> +"Fixnum".constantize # => Fixnum + +module M + X = 1 +end +"M::X".constantize # => 1 +</ruby> + +If the string evaluates to no known constant, or its content is not even a valid constant name, +constantize+ raises +NameError+. + +Constant name resolution by +constantize+ starts always at the top-level +Object+ even if there is no leading "::". + +<ruby> +X = :in_Object +module M + X = :in_M + + X # => :in_M + "::X".constantize # => :in_Object + "X".constantize # => :in_Object (!) +end +</ruby> + +So, it is in general not equivalent to what Ruby would do in the same spot, had a real constant be evaluated. + +Mailer test cases obtain the mailer being tested from the name of the test class using +constantize+: + +<ruby> +# action_mailer/test_case.rb +def determine_default_mailer(name) + name.sub(/Test$/, '').constantize +rescue NameError => e + raise NonInferrableMailerError.new(name) +end +</ruby> + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +humanize+ + +The method +humanize+ gives you a sensible name for display out of an attribute name. To do so it replaces underscores with spaces, removes any "_id" suffix, and capitalizes the first word: + +<ruby> +"name".humanize # => "Name" +"author_id".humanize # => "Author" +"comments_count".humanize # => "Comments count" +</ruby> + +The helper method +full_messages+ uses +humanize+ as a fallback to include attribute names: + +<ruby> +def full_messages + full_messages = [] + + each do |attribute, messages| + ... + attr_name = attribute.to_s.gsub('.', '_').humanize + attr_name = @base.class.human_attribute_name(attribute, :default => attr_name) + ... + end + + full_messages +end +</ruby> + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +foreign_key+ + +The method +foreign_key+ gives a foreign key column name from a class name. To do so it demodulizes, underscores, and adds "_id": + +<ruby> +"User".foreign_key # => "user_id" +"InvoiceLine".foreign_key # => "invoice_line_id" +"Admin::Session".foreign_key # => "session_id" +</ruby> + +Pass a false argument if you do not want the underscore in "_id": + +<ruby> +"User".foreign_key(false) # => "userid" +</ruby> + +Associations use this method to infer foreign keys, for example +has_one+ and +has_many+ do this: + +<ruby> +# active_record/associations.rb +foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key +</ruby> + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + h3. Extensions to +Numeric+ h4. Bytes diff --git a/railties/guides/source/activerecord_validations_callbacks.textile b/railties/guides/source/activerecord_validations_callbacks.textile index 9d0ee29ff2..83c85402ec 100644 --- a/railties/guides/source/activerecord_validations_callbacks.textile +++ b/railties/guides/source/activerecord_validations_callbacks.textile @@ -44,7 +44,7 @@ class Person < ActiveRecord::Base end </ruby> -We can see how it works by looking at some script/console output: +We can see how it works by looking at some +rails console+ output: <shell> >> p = Person.new(:name => "John Doe") @@ -240,7 +240,7 @@ end The +validates_exclusion_of+ helper has an option +:in+ that receives the set of values that will not be accepted for the validated attributes. The +:in+ option has an alias called +:within+ that you can use for the same purpose, if you'd like to. This example uses the +:message+ option to show how you can include the attribute's value. -The default error message for +validates_exclusion_of+ is "_is not included in the list_". +The default error message for +validates_exclusion_of+ is "_is reserved_". h4. +validates_format_of+ diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile index ac6f944457..e27c2a6dc6 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -152,7 +152,7 @@ expire_fragment('all_available_products') h4. Sweepers -Cache sweeping is a mechanism which allows you to get around having a ton of +expire_{page,action,fragment}+ calls in your code. It does this by moving all the work required to expire cached content into a +ActionController::Caching::Sweeper+ class. This class is an Observer and looks for changes to an object via callbacks, and when a change occurs it expires the caches associated with that object in an around or after filter. +Cache sweeping is a mechanism which allows you to get around having a ton of +expire_{page,action,fragment}+ calls in your code. It does this by moving all the work required to expire cached content into an +ActionController::Caching::Sweeper+ subclass. This class is an observer and looks for changes to an object via callbacks, and when a change occurs it expires the caches associated with that object in an around or after filter. Continuing with our Product controller example, we could rewrite it with a sweeper like this: diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index 1a571358a1..a84e928731 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -58,7 +58,7 @@ Without any prodding of any kind, +server+ will run our new shiny Rails app: <shell> $ cd commandsapp -$ ./script/server +$ rails server => Booting WEBrick... => Rails 2.2.0 application started on http://0.0.0.0:3000 => Ctrl-C to shutdown server; call with --help for options @@ -76,8 +76,8 @@ h4. +generate+ The +generate+ command uses templates to create a whole lot of things. You can always find out what's available by running +generate+ by itself. Let's do that: <shell> -$ ./script/generate -Usage: ./script/generate generator [options] [args] +$ rails generate +Usage: rails generate generator [options] [args] ... ... @@ -95,17 +95,17 @@ Using generators will save you a large amount of time by writing *boilerplate co Let's make our own controller with the controller generator. But what command should we use? Let's ask the generator: -INFO: All Rails console utilities have help text. As with most *NIX utilities, you can try adding +--help+ or +-h+ to the end, for example +./script/server --help+. +INFO: All Rails console utilities have help text. As with most *NIX utilities, you can try adding +--help+ or +-h+ to the end, for example +rails server --help+. <shell> -$ ./script/generate controller -Usage: ./script/generate controller ControllerName [options] +$ rails generate controller +Usage: rails generate controller ControllerName [options] ... ... Example: - ./script/generate controller CreditCard open debit credit close + rails generate controller CreditCard open debit credit close Credit card controller with URLs like /credit_card/debit. Controller: app/controllers/credit_card_controller.rb @@ -114,7 +114,7 @@ Example: Test: test/functional/credit_card_controller_test.rb Modules Example: - ./script/generate controller 'admin/credit_card' suspend late_fee + rails generate controller 'admin/credit_card' suspend late_fee Credit card admin controller with URLs /admin/credit_card/suspend. Controller: app/controllers/admin/credit_card_controller.rb @@ -126,7 +126,7 @@ Modules Example: Ah, the controller generator is expecting parameters in the form of +generate controller ControllerName action1 action2+. Let's make a +Greetings+ controller with an action of *hello*, which will say something nice to us. <shell> -$ ./script/generate controller Greetings hello +$ rails generate controller Greetings hello exists app/controllers/ exists app/helpers/ create app/views/greetings @@ -157,10 +157,10 @@ Then the view, to display our nice message (in +app/views/greetings/hello.html.e <p><%= @message %></p> </html> -Deal. Go check it out in your browser. Fire up your server. Remember? +./script/server+ at the root of your Rails application should do it. +Deal. Go check it out in your browser. Fire up your server. Remember? +rails server+ at the root of your Rails application should do it. <shell> -$ ./script/server +$ rails server => Booting WEBrick... </shell> @@ -173,13 +173,13 @@ INFO: With a normal, plain-old Rails application, your URLs will generally follo "What about data, though?", you ask over a cup of coffee. Rails comes with a generator for data models too. Can you guess its generator name? <shell> -$ ./script/generate model -Usage: ./script/generate model ModelName [field:type, field:type] +$ rails generate model +Usage: rails generate model ModelName [field:type, field:type] ... Examples: - ./script/generate model account + rails generate model account creates an Account model, test, fixture, and migration: Model: app/models/account.rb @@ -187,7 +187,7 @@ Examples: Fixtures: test/fixtures/accounts.yml Migration: db/migrate/XXX_add_accounts.rb - ./script/generate model post title:string body:text published:boolean + rails generate model post title:string body:text published:boolean creates a Post model with a string title, text body, and published flag. </shell> @@ -197,7 +197,7 @@ But instead of generating a model directly (which we'll be doing later), let's s Let's set up a simple resource called "HighScore" that will keep track of our highest score on video games we play. <shell> -$ ./script/generate scaffold HighScore game:string score:integer +$ rails generate scaffold HighScore game:string score:integer exists app/models/ exists app/controllers/ exists app/helpers/ @@ -244,13 +244,13 @@ $ rake db:migrate INFO: Let's talk about unit tests. Unit tests are code that tests and makes assertions about code. In unit testing, we take a little part of code, say a method of a model, and test its inputs and outputs. Unit tests are your friend. The sooner you make peace with the fact that your quality of life will drastically increase when you unit test your code, the better. Seriously. We'll make one in a moment. -Let's see the interface Rails created for us. ./script/server; http://localhost:3000/high_scores +Let's see the interface Rails created for us. rails server; http://localhost:3000/high_scores We can create new high scores (55,160 on Space Invaders!) h4. +console+ -The +console+ command lets you interact with your Rails application from the command line. On the underside, +script/console+ uses IRB, so if you've ever used it, you'll be right at home. This is useful for testing out quick ideas with code and changing data server-side without touching the website. +The +console+ command lets you interact with your Rails application from the command line. On the underside, +rails console+ uses IRB, so if you've ever used it, you'll be right at home. This is useful for testing out quick ideas with code and changing data server-side without touching the website. h4. +dbconsole+ @@ -265,7 +265,7 @@ Let's say you're creating a website for a client who wants a small accounting sy There is such a thing! The plugin we're installing is called "acts_as_paranoid", and it lets models implement a "deleted_at" column that gets set when you call destroy. Later, when calling find, the plugin will tack on a database check to filter out "deleted" things. <shell> -$ ./script/plugin install http://svn.techno-weenie.net/projects/plugins/acts_as_paranoid +$ rails plugin install http://svn.techno-weenie.net/projects/plugins/acts_as_paranoid + ./CHANGELOG + ./MIT-LICENSE ... @@ -277,7 +277,7 @@ h4. +runner+ <tt>runner</tt> runs Ruby code in the context of Rails non-interactively. For instance: <shell> -$ ./script/runner "Model.long_running_method" +$ rails runner "Model.long_running_method" </shell> h4. +destroy+ @@ -285,7 +285,7 @@ h4. +destroy+ Think of +destroy+ as the opposite of +generate+. It'll figure out what generate did, and undo it. Believe you-me, the creation of this tutorial used this command many times! <shell> -$ ./script/generate model Oops +$ rails generate model Oops exists app/models/ exists test/unit/ exists test/fixtures/ @@ -294,7 +294,7 @@ $ ./script/generate model Oops create test/fixtures/oops.yml exists db/migrate create db/migrate/20081221040817_create_oops.rb -$ ./script/destroy model Oops +$ rails destroy model Oops notempty db/migrate notempty db rm db/migrate/20081221040817_create_oops.rb @@ -314,7 +314,7 @@ h4. +about+ Check it: Version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version! +about+ is useful when you need to ask for help, check if a security patch might affect you, or when you need some stats for an existing Rails installation. <shell> -$ ./script/about +$ rails about About your application's environment Ruby version 1.8.6 (i486-linux) RubyGems version 1.3.1 @@ -399,7 +399,7 @@ Many people have created a large number different web servers in Ruby, and many NOTE: For more details on the Rack integration, see "Rails on Rack":rails_on_rack.html. -To use a different server, just install its gem, then use its name for the first parameter to +script/server+: +To use a different server, just install its gem, then use its name for the first parameter to +rails server+: <shell> $ sudo gem install mongrel @@ -412,9 +412,9 @@ Successfully installed mongrel-1.1.5 ... ... Installing RDoc documentation for mongrel-1.1.5... -$ script/server mongrel -=> Booting Mongrel (use 'script/server webrick' to force WEBrick) -=> Rails 2.2.0 application starting on http://0.0.0.0:3000 +$ rails server mongrel +=> Booting Mongrel (use 'rails server webrick' to force WEBrick) +=> Rails 3.0.0 application starting on http://0.0.0.0:3000 ... </shell> @@ -481,7 +481,7 @@ I got assigned some args: Then we'll make sure it got included in the list of available generators: <shell> -$ ./script/generate +$ rails generate ... ... Installed Generators @@ -491,7 +491,7 @@ Installed Generators SWEET! Now let's generate some text, yeah! <shell> -$ ./script/generate tutorial_test arg1 arg2 arg3 +$ rails generate tutorial_test arg1 arg2 arg3 exists public create public/tutorial.txt </shell> diff --git a/railties/guides/source/contribute.textile b/railties/guides/source/contribute.textile index 5087c2f385..a48edef79d 100644 --- a/railties/guides/source/contribute.textile +++ b/railties/guides/source/contribute.textile @@ -12,7 +12,7 @@ h3. How to Contribute? * All images are in the railties/guides/images directory. * Sample format : "Active Record Associations":http://github.com/lifo/docrails/blob/3e56a3832415476fdd1cb963980d0ae390ac1ed3/railties/guides/source/association_basics.textile * Sample output : "Active Record Associations":http://guides.rails.info/association_basics.html -* You can build the Guides during testing by running +rake guides+ in the +railties+ directory. +* You can build the Guides during testing by running +rake generate_guides+ in the +railties+ directory. h3. What to Contribute? diff --git a/railties/guides/source/debugging_rails_applications.textile b/railties/guides/source/debugging_rails_applications.textile index 94411a560e..cd0098d686 100644 --- a/railties/guides/source/debugging_rails_applications.textile +++ b/railties/guides/source/debugging_rails_applications.textile @@ -247,9 +247,9 @@ If you see the message in the console or logs: Make sure you have started your web server with the option +--debugger+: <shell> -~/PathTo/rails_project$ script/server --debugger -=> Booting Mongrel (use 'script/server webrick' to force WEBrick) -=> Rails 2.2.0 application starting on http://0.0.0.0:3000 +~/PathTo/rails_project$ rails server --debugger +=> Booting Mongrel (use 'rails server webrick' to force WEBrick) +=> Rails 3.0.0 application starting on http://0.0.0.0:3000 => Debugger enabled ... </shell> @@ -472,10 +472,10 @@ class Author < ActiveRecord::Base end </ruby> -TIP: You can use ruby-debug while using script/console. Just remember to +require "ruby-debug"+ before calling the +debugger+ method. +TIP: You can use ruby-debug while using +rails console+. Just remember to +require "ruby-debug"+ before calling the +debugger+ method. <shell> -/PathTo/project $ script/console +/PathTo/project $ rails console Loading development environment (Rails 2.1.0) >> require "ruby-debug" => [] @@ -636,7 +636,7 @@ require 'bleak_house' if ENV['BLEAK_HOUSE'] Start a server instance with BleakHouse integration: <shell> -RAILS_ENV=production BLEAK_HOUSE=1 ruby-bleak-house ./script/server +RAILS_ENV=production BLEAK_HOUSE=1 ruby-bleak-house rails server </shell> Make sure to run a couple hundred requests to get better data samples, then press +CTRL-C+. The server will stop and Bleak House will produce a dumpfile in +/tmp+: diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile index 0a76a0f06f..d33bb4b4ff 100644 --- a/railties/guides/source/form_helpers.textile +++ b/railties/guides/source/form_helpers.textile @@ -293,7 +293,7 @@ form_for(@article) ## Editing an existing article # long-style: -form_for(:article, @article, :url => article_path(@article), :method => "put") +form_for(:article, @article, :url => article_path(@article), :html => { :method => "put" }) # short-style: form_for(@article) </ruby> diff --git a/railties/guides/source/generators.textile b/railties/guides/source/generators.textile index fcd91f8956..4387fe3bd5 100644 --- a/railties/guides/source/generators.textile +++ b/railties/guides/source/generators.textile @@ -17,18 +17,18 @@ NOTE: This guide is about Rails generators for versions >= 3.0. Rails generators h3. First contact -When you create an application using the +rails+ command, you are in fact using a Rails generator. After that, you can get a list of all available generators by just invoking +script/generate+: +When you create an application using the +rails+ command, you are in fact using a Rails generator. After that, you can get a list of all available generators by just invoking +rails generate+: <shell> $ rails myapp $ cd myapp -$ ruby script/generate +$ rails generate </shell> You will get a list of all generators that comes with Rails. If you need a detailed description, for instance about the helper generator, you can simply do: <shell> -$ ruby script/generate helper --help +$ rails generate helper --help </shell> h3. Creating your first generator @@ -50,13 +50,13 @@ Our new generator is quite simple: it inherits from +Rails::Generators::Base+ an To invoke our new generator, we just need to do: <shell> -$ ruby script/generate initializer +$ rails generate initializer </shell> Before we go on, let's see our brand new generator description: <shell> -$ ruby script/generate initializer --help +$ rails generate initializer --help </shell> Rails usually is able to generate good descriptions if a generator is namespaced, as +ActiveRecord::Generators::ModelGenerator+, but not in this particular case. We can solve this problem in two ways. The first one is calling +desc+ inside our generator: @@ -77,7 +77,7 @@ h3. Creating generators with generators A faster way to create a generator is using the generator's generator: <shell> -$ ruby script/generate generator initializer +$ rails generate generator initializer create lib/generators/initializer create lib/generators/initializer/initializer_generator.rb create lib/generators/initializer/USAGE @@ -99,9 +99,9 @@ At first, we can notice that we are inheriting from +Rails::Generators::NamedBas We can see that by invoking the description of this new generator (don't forget to delete the old generator file): <shell> -$ ruby script/generate initializer --help +$ rails generate initializer --help Usage: - script/generate initializer NAME [options] + rails generate initializer NAME [options] </shell> We can also see in our new generator that it has a class method called +source_root+. This method points to where our generator templates will be placed and by default it points to the created directory under +RAILS_APP/lib/generators/initializer/templates+. In order to understand what a generator template means, let's create a file at +RAILS_APP/lib/generators/initializer/templates/initializer.rb+ with the following content: @@ -128,7 +128,7 @@ end And let's execute our generator: <shell> -$ ruby script/generate initializer foo +$ rails generate initializer foo </shell> We can see that now a initializer named foo was created at +config/initializers/foo.rb+ with the contents of our template. That means that copy_file copied a file in our source root to the destination path we gave. The method +file_name+ is automatically created when we inherit from +Rails::Generators::NamedBase+. @@ -166,7 +166,7 @@ end Before we customize our workflow, let's first see how our scaffold looks like: <shell> -$ ruby script/generate scaffold User name:string +$ rails generate scaffold User name:string invoke active_record create db/migrate/20091120125558_create_users.rb create app/models/user.rb @@ -212,7 +212,7 @@ If we generate another resource on scaffold, we can notice that neither styleshe To show that, we are going to create a new helper generator that simply adds some instance variable readers. First, we create a generator: <shell> -$ ruby script/generate generator my_helper +$ rails generate generator my_helper </shell> After that, we can delete both templates directory and the +source_root+ class method from our new generators, because we are not going to need them. So our new generator looks like the following: @@ -232,7 +232,7 @@ end We can try out our new generator by creating a helper for users: <shell> -$ ruby script/generate my_helper users +$ rails generate my_helper users </shell> And it will generate the following helper file in app/helpers: @@ -258,7 +258,7 @@ end And see it in action when invoking generator once again: <shell> -$ ruby script/generate scaffold Post body:text +$ rails generate scaffold Post body:text [...] invoke my_helper create app/helpers/posts_helper.rb @@ -343,7 +343,7 @@ Rails::Generators.fallbacks[:shoulda] = :test_unit Now, if create a Comment scaffold, you will see that shoulda generators are being invoked, and at the end, they are just falling back to test unit generators: <shell> -$ ruby script/generate scaffold Comment body:text +$ rails generate scaffold Comment body:text invoke active_record create db/migrate/20091120151323_create_comments.rb create app/models/comment.rb diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index ea8154cccc..a231713991 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -431,7 +431,7 @@ Now you're ready to start working with posts. To do that, navigate to "http://lo !images/posts_index.png(Posts Index screenshot)! -This is the result of Rails rendering the +index+ view of your posts. There aren't currently any posts in the database, but if you click the +New Post+ link you can create one. After that, you'll find that you can edit posts, look at their details, or destroy them. All of the logic and HTML to handle this was built by the single +script/generate scaffold+ command. +This is the result of Rails rendering the +index+ view of your posts. There aren't currently any posts in the database, but if you click the +New Post+ link you can create one. After that, you'll find that you can edit posts, look at their details, or destroy them. All of the logic and HTML to handle this was built by the single +rails generate scaffold+ command. TIP: In development mode (which is what you're working in by default), Rails reloads your application with every browser request, so there's no need to stop and restart the web server. diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile index 771c3e2523..558cbb4771 100644 --- a/railties/guides/source/migrations.textile +++ b/railties/guides/source/migrations.textile @@ -105,7 +105,7 @@ h4. Creating a Model The model and scaffold generators will create migrations appropriate for adding a new model. This migration will already contain instructions for creating the relevant table. If you tell Rails what columns you want then statements for adding those will also be created. For example, running <shell> -ruby script/generate model Product name:string description:text +rails generate model Product name:string description:text </shell> will create a migration that looks like this @@ -135,7 +135,7 @@ h4. Creating a Standalone Migration If you are creating migrations for other purposes (for example to add a column to an existing table) then you can use the migration generator: <shell> -ruby script/generate migration AddPartNumberToProducts +rails generate migration AddPartNumberToProducts </shell> This will create an empty but appropriately named migration: @@ -153,7 +153,7 @@ end If the migration name is of the form "AddXXXToYYY" or "RemoveXXXFromYYY" and is followed by a list of column names and types then a migration containing the appropriate +add_column+ and +remove_column+ statements will be created. <shell> -ruby script/generate migration AddPartNumberToProducts part_number:string +rails generate migration AddPartNumberToProducts part_number:string </shell> will generate @@ -173,7 +173,7 @@ end Similarly, <shell> -ruby script/generate migration RemovePartNumberFromProducts part_number:string +rails generate migration RemovePartNumberFromProducts part_number:string </shell> generates @@ -193,7 +193,7 @@ end You are not limited to one magically generated column, for example <shell> -ruby script/generate migration AddDetailsToProducts part_number:string price:decimal +rails generate migration AddDetailsToProducts part_number:string price:decimal </shell> generates diff --git a/railties/guides/source/performance_testing.textile b/railties/guides/source/performance_testing.textile index f0dc9acbb8..5c760a5966 100644 --- a/railties/guides/source/performance_testing.textile +++ b/railties/guides/source/performance_testing.textile @@ -37,7 +37,7 @@ h4. Generating Performance Tests Rails provides a generator called +performance_test+ for creating new performance tests: <shell> -script/generate performance_test homepage +rails generate performance_test homepage </shell> This generates +homepage_test.rb+ in the +test/performance+ directory: @@ -381,19 +381,19 @@ h4. +benchmarker+ Usage: <shell> -$ script/performance/benchmarker [times] 'Person.expensive_way' 'Person.another_expensive_way' ... +$ rails benchmarker [times] 'Person.expensive_way' 'Person.another_expensive_way' ... </shell> Examples: <shell> -$ script/performance/benchmarker 10 'Item.all' 'CouchItem.all' +$ rails benchmarker 10 'Item.all' 'CouchItem.all' </shell> If the +[times]+ argument is omitted, supplied methods are run just once: <shell> -$ script/performance/benchmarker 'Item.first' 'Item.last' +$ rails benchmarker 'Item.first' 'Item.last' </shell> h4. +profiler+ @@ -403,19 +403,19 @@ h4. +profiler+ Usage: <shell> -$ script/performance/profiler 'Person.expensive_method(10)' [times] [flat|graph|graph_html] +$ rails profiler 'Person.expensive_method(10)' [times] [flat|graph|graph_html] </shell> Examples: <shell> -$ script/performance/profiler 'Item.all' +$ rails profiler 'Item.all' </shell> This will profile +Item.all+ in +RubyProf::WALL_TIME+ measure mode. By default, it prints flat output to the shell. <shell> -$ script/performance/profiler 'Item.all' 10 graph +$ rails profiler 'Item.all' 10 graph </shell> This will profile +10.times { Item.all }+ with +RubyProf::WALL_TIME+ measure mode and print graph output to the shell. @@ -423,7 +423,7 @@ This will profile +10.times { Item.all }+ with +RubyProf::WALL_TIME+ measure mod If you want to store the output in a file: <shell> -$ script/performance/profiler 'Item.all' 10 graph 2> graph.txt +$ rails profiler 'Item.all' 10 graph 2> graph.txt </shell> h3. Helper Methods diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile index 06d0d493e4..2db421aa91 100644 --- a/railties/guides/source/plugins.textile +++ b/railties/guides/source/plugins.textile @@ -39,9 +39,9 @@ The examples in this guide require that you have a working rails application. T gem install rails rails yaffle_guide cd yaffle_guide -script/generate scaffold bird name:string +rails generate scaffold bird name:string rake db:migrate -script/server +rails server </pre> Then navigate to http://localhost:3000/birds. Make sure you have a functioning rails app before continuing. @@ -57,16 +57,16 @@ This creates a plugin in 'vendor/plugins' including an 'init.rb' and 'README' as Examples: <pre> -./script/generate plugin yaffle -./script/generate plugin yaffle --with-generator +rails generate plugin yaffle +rails generate plugin yaffle --with-generator </pre> -To get more detailed help on the plugin generator, type +./script/generate plugin+. +To get more detailed help on the plugin generator, type +rails generate plugin+. Later on this guide will describe how to work with generators, so go ahead and generate your plugin with the +--with-generator+ option now: <pre> -./script/generate plugin yaffle --with-generator +rails generate plugin yaffle --with-generator </pre> You should see the following output: @@ -104,7 +104,7 @@ To make it easy to organize your files and to make the plugin more compatible wi `-- init.rb </pre> -*vendor/plugins/yaffle/rails/init.rb* +*vendor/plugins/yaffle/init.rb* <ruby> require 'yaffle' @@ -334,20 +334,22 @@ end To test that your method does what it says it does, run the unit tests with +rake+ from your plugin directory. To see this in action, fire up a console and start squawking: <shell> -$ ./script/console +$ rails console >> "Hello World".to_squawk => "squawk! Hello World" </shell> h4. Working with +init.rb+ -When rails loads plugins it looks for the file named 'init.rb' or 'rails/init.rb'. However, when the plugin is initialized, 'init.rb' is invoked via +eval+ (not +require+) so it has slightly different behavior. +When Rails loads plugins it looks for a file named +init.rb+. However, when the plugin is initialized, +init.rb+ is invoked via +eval+ (not +require+) so it has slightly different behavior. -Under certain circumstances if you reopen classes or modules in 'init.rb' you may inadvertently create a new class, rather than reopening an existing class. A better alternative is to reopen the class in a different file, and require that file from +init.rb+, as shown above. +NOTE: The plugins loader also looks for +rails/init.rb+, but that one is deprecated in favor of the top-level +init.rb+ aforementioned. + +Under certain circumstances if you reopen classes or modules in +init.rb+ you may inadvertently create a new class, rather than reopening an existing class. A better alternative is to reopen the class in a different file, and require that file from +init.rb+, as shown above. If you must reopen a class in +init.rb+ you can use +module_eval+ or +class_eval+ to avoid any issues: -* *vendor/plugins/yaffle/rails/init.rb* +* *vendor/plugins/yaffle/init.rb* <ruby> Hash.class_eval do @@ -359,7 +361,7 @@ end Another way is to explicitly define the top-level module space for all modules and classes, like +::Hash+: -* *vendor/plugins/yaffle/rails/init.rb* +* *vendor/plugins/yaffle/init.rb* <ruby> class ::Hash @@ -871,7 +873,7 @@ If you plan to distribute your plugin, developers will expect at least a minimum Rails ships with several built-in generators. You can see all of the generators available to you by typing the following at the command line: <shell> -./script/generate +rails generate </shell> You should see something like this: @@ -882,7 +884,7 @@ Installed Generators Builtin: controller, integration_test, mailer, migration, model, observer, plugin, resource, scaffold, session_migration </shell> -When you run +script/generate yaffle_definition -h+ you should see the contents of your 'vendor/plugins/yaffle/generators/yaffle_definition/USAGE'. +When you run +rails generate yaffle_definition -h+ you should see the contents of your 'vendor/plugins/yaffle/generators/yaffle_definition/USAGE'. For this plugin, update the USAGE file could look like this: @@ -1111,11 +1113,11 @@ end To see this work, type: <shell> -./script/generate yaffle_route -./script/destroy yaffle_route +rails generate yaffle_route +rails destroy yaffle_route </shell> -NOTE: If you haven't set up the custom route from above, 'script/destroy' will fail and you'll have to remove it manually. +NOTE: If you haven't set up the custom route from above, 'rails destroy' will fail and you'll have to remove it manually. h3. Migrations @@ -1195,7 +1197,7 @@ h4. Generate Migrations Generating migrations has several advantages over other methods. Namely, you can allow other developers to more easily customize the migration. The flow looks like this: - * call your script/generate script and pass in whatever options they need + * call your rails generate script and pass in whatever options they need * examine the generated migration, adding/removing columns or other options as necessary This example will demonstrate how to use one of the built-in generator methods named 'migration_template' to create a migration file. Extending the rails migration generator requires a somewhat intimate knowledge of the migration generator internals, so it's best to write a test first: @@ -1289,7 +1291,7 @@ It's courteous to check to see if table names are being pluralized whenever you To run the generator, type the following at the command line: <shell> -./script/generate yaffle_migration bird +rails generate yaffle_migration bird </shell> and you will see a new file: @@ -1333,15 +1335,15 @@ yaffle:squawk # Prints out the word 'Yaffle' You can add as many files as you want in the tasks directory, and if they end in .rake Rails will pick them up. -Note that tasks from 'vendor/plugins/yaffle/Rakefile' are not available to the main app. +Note that tasks from +vendor/plugins/yaffle/Rakefile+ are not available to the main app. -h3. PluginGems +h3. Plugins as Gems Turning your rails plugin into a gem is a simple and straightforward task. This section will cover how to turn your plugin into a gem. It will not cover how to distribute that gem. -Historically rails plugins loaded the plugin's 'init.rb' file. In fact some plugins contain all of their code in that one file. To be compatible with plugins, 'init.rb' was moved to 'rails/init.rb'. +The initialization file has to be called +rails/init.rb+, the root +init.rb+ file, if any, is ignored by Rails. Also, the name of the plugin now is relevant since +config.gem+ tries to load it. Either name the main file after your gem, or document that users should use the +:lib+ option. -It's common practice to put any developer-centric rake tasks (such as tests, rdoc and gem package tasks) in 'Rakefile'. A rake task that packages the gem might look like this: +It's common practice to put any developer-centric rake tasks (such as tests, rdoc and gem package tasks) in +Rakefile+. A rake task that packages the gem might look like this: * *vendor/plugins/yaffle/Rakefile:* @@ -1383,7 +1385,7 @@ rake gem sudo gem install pkg/yaffle-0.0.1.gem </shell> -To test this, create a new rails app, add 'config.gem "yaffle"' to environment.rb and all of your plugin's functionality will be available to you. +To test this, create a new rails app, add +config.gem "yaffle"+ to +config/environment.rb+ and all of your plugin's functionality will be available to you. h3. RDoc Documentation diff --git a/railties/guides/source/rails_on_rack.textile b/railties/guides/source/rails_on_rack.textile index df93580e89..eef44d9ec9 100644 --- a/railties/guides/source/rails_on_rack.textile +++ b/railties/guides/source/rails_on_rack.textile @@ -30,11 +30,11 @@ h4. Rails Application's Rack Object <tt>ActionController::Dispatcher.new</tt> is the primary Rack application object of a Rails application. Any Rack compliant web server should be using +ActionController::Dispatcher.new+ object to serve a Rails application.</p> -h4. +script/server+ +h4. +rails server+ -<tt>script/server</tt> does the basic job of creating a +Rack::Builder+ object and starting the webserver. This is Rails' equivalent of Rack's +rackup+ script. +<tt>rails server</tt> does the basic job of creating a +Rack::Builder+ object and starting the webserver. This is Rails' equivalent of Rack's +rackup+ script. -Here's how +script/server+ creates an instance of +Rack::Builder+ +Here's how +rails server+ creates an instance of +Rack::Builder+ <ruby> app = Rack::Builder.new { @@ -54,7 +54,7 @@ Middlewares used in the code above are primarily useful only in the development h4. +rackup+ -To use +rackup+ instead of Rails' +script/server+, you can put the following inside +config.ru+ of your Rails application's root directory: +To use +rackup+ instead of Rails' +rails server+, you can put the following inside +config.ru+ of your Rails application's root directory: <ruby> # RAILS_ROOT/config.ru @@ -233,7 +233,7 @@ h4. Generating a Metal Application Rails provides a generator called +metal+ for creating a new Metal application: <shell> -$ script/generate metal poller +$ rails generate metal poller </shell> This generates +poller.rb+ in the +app/metal+ directory: diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile index c7b475899f..ac9fb4276e 100644 --- a/railties/guides/source/testing.textile +++ b/railties/guides/source/testing.textile @@ -56,7 +56,7 @@ h5. What are Fixtures? _Fixtures_ is a fancy word for sample data. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent and assume one of two formats: *YAML* or *CSV*. In this guide we will use *YAML* which is the preferred format. -You'll find fixtures under your +test/fixtures+ directory. When you run +script/generate model+ to create a new model, fixture stubs will be automatically created and placed in this directory. +You'll find fixtures under your +test/fixtures+ directory. When you run +rails generate model+ to create a new model, fixture stubs will be automatically created and placed in this directory. h5. YAML @@ -144,10 +144,10 @@ For this guide we will be using Rails _scaffolding_. It will create the model, a NOTE: For more information on Rails _scaffolding_, refer to "Getting Started with Rails":getting_started.html -When you use +script/generate scaffold+, for a resource among other things it creates a test stub in the +test/unit+ folder: +When you use +rails generate scaffold+, for a resource among other things it creates a test stub in the +test/unit+ folder: <pre> -$ script/generate scaffold post title:string body:text +$ rails generate scaffold post title:string body:text ... create app/models/post.rb create test/unit/post_test.rb @@ -604,7 +604,7 @@ Integration tests are used to test the interaction among any number of controlle Unlike Unit and Functional tests, integration tests have to be explicitly created under the 'test/integration' folder within your application. Rails provides a generator to create an integration test skeleton for you. <shell> -$ script/generate integration_test user_flows +$ rails generate integration_test user_flows exists test/integration/ create test/integration/user_flows_test.rb </shell> diff --git a/railties/lib/generators/erb/scaffold/templates/layout.html.erb b/railties/lib/generators/erb/scaffold/templates/layout.html.erb index 7aa049fe80..420d17f33c 100644 --- a/railties/lib/generators/erb/scaffold/templates/layout.html.erb +++ b/railties/lib/generators/erb/scaffold/templates/layout.html.erb @@ -3,6 +3,8 @@ <head> <title><%= controller_class_name %>: <%%= controller.action_name %></title> <%%= stylesheet_link_tag 'scaffold' %> + <%%= javascript_include_tag :defaults %> + <%%= csrf_meta_tag %> </head> <body> diff --git a/railties/lib/generators/rails/app/templates/Gemfile b/railties/lib/generators/rails/app/templates/Gemfile index f51edc4d2f..f4bce8646d 100644 --- a/railties/lib/generators/rails/app/templates/Gemfile +++ b/railties/lib/generators/rails/app/templates/Gemfile @@ -1,34 +1,34 @@ -# Edit this Gemfile to bundle your application's dependencies. source 'http://gemcutter.org' -<% if !dev_or_edge? %> -gem "rails", "<%= Rails::VERSION::STRING %>" -<% end -%> - -## Bundle edge rails: <%- if options.dev? -%> -path "<%= Rails::Generators::RAILS_DEV_PATH %>", :glob => "{*/,}*.gemspec" -gem "rails", "<%= Rails::VERSION::STRING %>" +gem 'rails', :path => '<%= Rails::Generators::RAILS_DEV_PATH %>' +<%- elsif options.edge? -%> +gem 'rails', :git => 'git://github.com/rails/rails.git' <%- else -%> -<%= "# " unless options.edge? %>gem "rails", :git => "git://github.com/rails/rails.git" +gem 'rails', '<%= Rails::VERSION::STRING %>' + +# Bundle edge Rails instead: +# gem 'rails', :git => 'git://github.com/rails/rails.git' <%- end -%> <% unless options[:skip_activerecord] -%> -<% if options[:database] == 'sqlite3' -%> -# ActiveRecord requires a database adapter. By default, -# Rails has selected sqlite3. -<% end -%> -gem "<%= gem_for_database %>"<% if require_for_database %>, :require => "<%= require_for_database %>"<% end %> +gem '<%= gem_for_database %>'<% if require_for_database %>, :require => '<%= require_for_database %>'<% end %> <% end -%> -## Bundle the gems you use: -# gem "bj" -# gem "hpricot", "0.6" -# gem "sqlite3-ruby", :require => "sqlite3" -# gem "aws-s3", :require => "aws/s3" +# Use mongrel as the web server +# gem 'mongrel' + +# Deploy with Capistrano +# gem 'capistrano' + +# Bundle the extra gems: +# gem 'bj' +# gem 'hpricot', '0.6' +# gem 'sqlite3-ruby', :require => 'sqlite3' +# gem 'aws-s3', :require => 'aws/s3' -## Bundle gems used only in certain environments: -# gem "rspec", :group => :test +# Bundle gems for certain environments: +# gem 'rspec', :group => :test # group :test do -# gem "webrat" +# gem 'webrat' # end diff --git a/railties/lib/generators/rails/app/templates/config/application.rb b/railties/lib/generators/rails/app/templates/config/application.rb index 78a355d2f4..7c555c2542 100644 --- a/railties/lib/generators/rails/app/templates/config/application.rb +++ b/railties/lib/generators/rails/app/templates/config/application.rb @@ -1,5 +1,19 @@ require File.expand_path('../boot', __FILE__) +<% unless options[:skip_activerecord] -%> +require 'rails/all' +<% else -%> +# Pick the frameworks you want: +# require "active_record/railtie" +require "action_controller/railtie" +require "action_mailer/railtie" +require "active_resource/railtie" +require "rails/test_unit/railtie" +<% end -%> + +# Auto-require default libraries and those for the current Rails environment. +Bundler.require :default, Rails.env + module <%= app_const_base %> class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. diff --git a/railties/lib/generators/rails/app/templates/config/boot.rb b/railties/lib/generators/rails/app/templates/config/boot.rb index 03396113e8..29c9d506e5 100644 --- a/railties/lib/generators/rails/app/templates/config/boot.rb +++ b/railties/lib/generators/rails/app/templates/config/boot.rb @@ -15,32 +15,3 @@ rescue LoadError # # require 'rubygems' end - -# Auto-require all bundled libraries. -Bundler.require - -<% unless options[:skip_activerecord] -%> -require 'rails/all' - -# To pick the frameworks you want, remove 'require "rails/all"' -# and list the framework railties that you want: -# -# require "active_support/railtie" -# require "active_model/railtie" -# require "active_record/railtie" -# require "action_controller/railtie" -# require "action_view/railtie" -# require "action_mailer/railtie" -# require "active_resource/railtie" -# require "rails/test_unit/railtie" -<% else -%> -# Pick the frameworks you want: -# require "active_model/railtie" -# require "active_record/railtie" -require "active_support/railtie" -require "action_controller/railtie" -require "action_view/railtie" -require "action_mailer/railtie" -require "active_resource/railtie" -require "rails/test_unit/railtie" -<% end -%> diff --git a/railties/lib/generators/rails/app/templates/config/databases/ibm_db.yml b/railties/lib/generators/rails/app/templates/config/databases/ibm_db.yml index 2cecb5c879..2784a949fb 100644 --- a/railties/lib/generators/rails/app/templates/config/databases/ibm_db.yml +++ b/railties/lib/generators/rails/app/templates/config/databases/ibm_db.yml @@ -4,23 +4,32 @@ # http://rubyforge.org/projects/rubyibm/ # # To install the ibm_db gem: +# # On Linux: -# Source the db2profile file and set the necessary environment variables: +# . /home/db2inst1/sqllib/db2profile +# export IBM_DB_INCLUDE=/opt/ibm/db2/V9.7/include +# export IBM_DB_LIB=/opt/ibm/db2/V9.7/lib32 +# gem install ibm_db # +# On Mac OS X 10.5: # . /home/db2inst1/sqllib/db2profile -# export IBM_DB_DIR=/opt/ibm/db2/V9.1 -# export IBM_DB_LIB=/opt/ibm/db2/V9.1/lib32 +# export IBM_DB_INCLUDE=/opt/ibm/db2/V9.7/include +# export IBM_DB_LIB=/opt/ibm/db2/V9.7/lib32 +# export ARCHFLAGS="-arch i386" +# gem install ibm_db # -# Then issue the command: gem install ibm_db +# On Mac OS X 10.6: +# . /home/db2inst1/sqllib/db2profile +# export IBM_DB_INCLUDE=/opt/ibm/db2/V9.7/include +# export IBM_DB_LIB=/opt/ibm/db2/V9.7/lib64 +# export ARCHFLAGS="-arch x86_64" +# gem install ibm_db # # On Windows: -# Issue the command: gem install ibm_db -# If prompted, select the mswin32 option -# -# For more details on the installation refer to http://rubyforge.org/docman/view.php/2361/7682/IBM_DB_GEM.pdf +# Issue the command: gem install ibm_db # -# For more details on the connection parameters below refer to: -# http://rubyibm.rubyforge.org/docs/adapter/0.9.0/rdoc/classes/ActiveRecord/ConnectionAdapters/IBM_DBAdapter.html +# For more details on the installation and the connection parameters below, +# please refer to the latest documents at http://rubyforge.org/docman/?group_id=2361 development: adapter: ibm_db @@ -52,7 +61,7 @@ production: adapter: ibm_db username: db2inst1 password: - database: <%= app_name[0,4] %>_prd + database: <%= app_name[0,8] %> #schema: db2inst1 #host: localhost #port: 50000 diff --git a/railties/lib/generators/rails/app/templates/public/javascripts/controls.js b/railties/lib/generators/rails/app/templates/public/javascripts/controls.js index ca29aefdd1..7392fb664c 100644 --- a/railties/lib/generators/rails/app/templates/public/javascripts/controls.js +++ b/railties/lib/generators/rails/app/templates/public/javascripts/controls.js @@ -1,6 +1,8 @@ -// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan) -// (c) 2005-2008 Jon Tirsen (http://www.tirsen.com) +// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005-2009 Jon Tirsen (http://www.tirsen.com) // Contributors: // Richard Livsey // Rahul Bhargava diff --git a/railties/lib/generators/rails/app/templates/public/javascripts/dragdrop.js b/railties/lib/generators/rails/app/templates/public/javascripts/dragdrop.js index 07229f986f..15c6dbca68 100644 --- a/railties/lib/generators/rails/app/templates/public/javascripts/dragdrop.js +++ b/railties/lib/generators/rails/app/templates/public/javascripts/dragdrop.js @@ -1,5 +1,6 @@ -// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) +// script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ @@ -311,7 +312,7 @@ var Draggable = Class.create({ tag_name=='TEXTAREA')) return; var pointer = [Event.pointerX(event), Event.pointerY(event)]; - var pos = Position.cumulativeOffset(this.element); + var pos = this.element.cumulativeOffset(); this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); Draggables.activate(this); @@ -454,7 +455,7 @@ var Draggable = Class.create({ }, draw: function(point) { - var pos = Position.cumulativeOffset(this.element); + var pos = this.element.cumulativeOffset(); if(this.options.ghosting) { var r = Position.realOffset(this.element); pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; @@ -730,7 +731,7 @@ var Sortable = { } // keep reference - this.sortables[element.id] = options; + this.sortables[element.identify()] = options; // for onupdate Draggables.addObserver(new SortableObserver(element, options.onUpdate)); @@ -825,7 +826,7 @@ var Sortable = { hide().addClassName('dropmarker').setStyle({position:'absolute'}); document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); } - var offsets = Position.cumulativeOffset(dropon); + var offsets = dropon.cumulativeOffset(); Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); if(position=='after') diff --git a/railties/lib/generators/rails/app/templates/public/javascripts/effects.js b/railties/lib/generators/rails/app/templates/public/javascripts/effects.js index 5a639d2dea..066ee5909c 100644 --- a/railties/lib/generators/rails/app/templates/public/javascripts/effects.js +++ b/railties/lib/generators/rails/app/templates/public/javascripts/effects.js @@ -1,4 +1,6 @@ -// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // Contributors: // Justin Palmer (http://encytemedia.com/) // Mark Pilgrim (http://diveintomark.org/) @@ -145,14 +147,13 @@ var Effect = { 'blind': ['BlindDown','BlindUp'], 'appear': ['Appear','Fade'] }, - toggle: function(element, effect) { + toggle: function(element, effect, options) { element = $(element); - effect = (effect || 'appear').toLowerCase(); - var options = Object.extend({ + effect = (effect || 'appear').toLowerCase(); + + return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({ queue: { position:'end', scope:(element.id || 'global'), limit: 1 } - }, arguments[2] || { }); - Effect[element.visible() ? - Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + }, options || {})); } }; @@ -228,12 +229,6 @@ Effect.Queue = Effect.Queues.get('global'); Effect.Base = Class.create({ position: null, start: function(options) { - function codeForEvent(options,eventName){ - return ( - (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') + - (options[eventName] ? 'this.options.'+eventName+'(this);' : '') - ); - } if (options && options.transition === false) options.transition = Effect.Transitions.linear; this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { }); this.currentFrame = 0; diff --git a/railties/lib/generators/rails/app/templates/public/javascripts/prototype.js b/railties/lib/generators/rails/app/templates/public/javascripts/prototype.js index dfe8ab4e13..9fe6e1243b 100644 --- a/railties/lib/generators/rails/app/templates/public/javascripts/prototype.js +++ b/railties/lib/generators/rails/app/templates/public/javascripts/prototype.js @@ -1,5 +1,5 @@ -/* Prototype JavaScript framework, version 1.6.0.3 - * (c) 2005-2008 Sam Stephenson +/* Prototype JavaScript framework, version 1.6.1 + * (c) 2005-2009 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/ @@ -7,26 +7,43 @@ *--------------------------------------------------------------------------*/ 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/) - }, + Version: '1.6.1', + + 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.*Safari/.test(ua) + } + })(), BrowserFeatures: { XPath: !!document.evaluate, SelectorsAPI: !!document.querySelector, - ElementExtensions: !!window.HTMLElement, - SpecificElementExtensions: - document.createElement('div')['__proto__'] && - document.createElement('div')['__proto__'] !== - document.createElement('form')['__proto__'] + 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'); + var form = document.createElement('form'); + var isSupported = false; + + if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { + isSupported = true; + } + + div = form = null; + + return isSupported; + })() }, ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>', @@ -40,9 +57,30 @@ 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 = { - create: function() { + +var Class = (function() { + function subclass() {}; + function create() { var parent = null, properties = $A(arguments); if (Object.isFunction(properties[0])) parent = properties.shift(); @@ -56,7 +94,6 @@ var Class = { klass.subclasses = []; if (parent) { - var subclass = function() { }; subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); @@ -69,18 +106,19 @@ var Class = { klass.prototype.initialize = Prototype.emptyFunction; klass.prototype.constructor = klass; - return klass; } -}; -Class.Methods = { - addMethods: function(source) { + function addMethods(source) { var ancestor = this.superclass && this.superclass.prototype; var properties = Object.keys(source); - if (!Object.keys({ toString: true }).length) - properties.push("toString", "valueOf"); + if (!Object.keys({ toString: true }).length) { + 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]; @@ -88,7 +126,7 @@ Class.Methods = { value.argumentNames().first() == "$super") { var method = value; value = (function(m) { - return function() { return ancestor[m].apply(this, arguments) }; + return function() { return ancestor[m].apply(this, arguments); }; })(property).wrap(method); value.valueOf = method.valueOf.bind(method); @@ -99,29 +137,36 @@ Class.Methods = { return this; } -}; -var Abstract = { }; + return { + create: create, + Methods: { + addMethods: addMethods + } + }; +})(); +(function() { -Object.extend = function(destination, source) { - for (var property in source) - destination[property] = source[property]; - return destination; -}; + var _toString = Object.prototype.toString; -Object.extend(Object, { - inspect: function(object) { + function extend(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; + } + + function inspect(object) { try { - if (Object.isUndefined(object)) return 'undefined'; + 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; } - }, + } - toJSON: function(object) { + function toJSON(object) { var type = typeof object; switch (type) { case 'undefined': @@ -132,131 +177,180 @@ Object.extend(Object, { if (object === null) return 'null'; if (object.toJSON) return object.toJSON(); - if (Object.isElement(object)) return; + if (isElement(object)) return; var results = []; for (var property in object) { - var value = Object.toJSON(object[property]); - if (!Object.isUndefined(value)) + var value = toJSON(object[property]); + if (!isUndefined(value)) results.push(property.toJSON() + ': ' + value); } return '{' + results.join(', ') + '}'; - }, + } - toQueryString: function(object) { + function toQueryString(object) { return $H(object).toQueryString(); - }, + } - toHTML: function(object) { + function toHTML(object) { return object && object.toHTML ? object.toHTML() : String.interpret(object); - }, + } - keys: function(object) { - var keys = []; + function keys(object) { + var results = []; for (var property in object) - keys.push(property); - return keys; - }, + results.push(property); + return results; + } - values: function(object) { - var values = []; + function values(object) { + var results = []; for (var property in object) - values.push(object[property]); - return values; - }, + results.push(object[property]); + return results; + } - clone: function(object) { - return Object.extend({ }, object); - }, + function clone(object) { + return extend({ }, object); + } - isElement: function(object) { + function isElement(object) { return !!(object && object.nodeType == 1); - }, + } + + function isArray(object) { + return _toString.call(object) == "[object Array]"; + } - isArray: function(object) { - return object != null && typeof object == "object" && - 'splice' in object && 'join' in object; - }, - isHash: function(object) { + function isHash(object) { return object instanceof Hash; - }, + } - isFunction: function(object) { - return typeof object == "function"; - }, + function isFunction(object) { + return typeof object === "function"; + } - isString: function(object) { - return typeof object == "string"; - }, + function isString(object) { + return _toString.call(object) == "[object String]"; + } - isNumber: function(object) { - return typeof object == "number"; - }, + function isNumber(object) { + return _toString.call(object) == "[object Number]"; + } - isUndefined: function(object) { - return typeof object == "undefined"; + function isUndefined(object) { + return typeof object === "undefined"; } -}); -Object.extend(Function.prototype, { - argumentNames: function() { - var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1] + extend(Object, { + extend: extend, + inspect: inspect, + toJSON: toJSON, + toQueryString: toQueryString, + toHTML: toHTML, + 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; - }, + } - bind: function() { + function bind(context) { if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; - var __method = this, args = $A(arguments), object = args.shift(); + var __method = this, args = slice.call(arguments, 1); return function() { - return __method.apply(object, args.concat($A(arguments))); + var a = merge(args, arguments); + return __method.apply(context, a); } - }, + } - bindAsEventListener: function() { - var __method = this, args = $A(arguments), object = args.shift(); + function bindAsEventListener(context) { + var __method = this, args = slice.call(arguments, 1); return function(event) { - return __method.apply(object, [event || window.event].concat(args)); + var a = update([event || window.event], args); + return __method.apply(context, a); } - }, + } - curry: function() { + function curry() { if (!arguments.length) return this; - var __method = this, args = $A(arguments); + var __method = this, args = slice.call(arguments, 0); return function() { - return __method.apply(this, args.concat($A(arguments))); + var a = merge(args, arguments); + return __method.apply(this, a); } - }, + } - delay: function() { - var __method = this, args = $A(arguments), timeout = args.shift() * 1000; + function delay(timeout) { + var __method = this, args = slice.call(arguments, 1); + timeout = timeout * 1000 return window.setTimeout(function() { return __method.apply(__method, args); }, timeout); - }, + } - defer: function() { - var args = [0.01].concat($A(arguments)); + function defer() { + var args = update([0.01], arguments); return this.delay.apply(this, args); - }, + } - wrap: function(wrapper) { + function wrap(wrapper) { var __method = this; return function() { - return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); + var a = update([__method.bind(this)], arguments); + return wrapper.apply(this, a); } - }, + } - methodize: function() { + function methodize() { if (this._methodized) return this._methodized; var __method = this; return this._methodized = function() { - return __method.apply(null, [this].concat($A(arguments))); + 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 + } +})()); + Date.prototype.toJSON = function() { return '"' + this.getUTCFullYear() + '-' + @@ -267,30 +361,12 @@ Date.prototype.toJSON = function() { 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; @@ -319,8 +395,10 @@ var PeriodicalExecuter = Class.create({ try { this.currentlyExecuting = true; this.execute(); - } finally { this.currentlyExecuting = false; + } catch(e) { + this.currentlyExecuting = false; + throw e; } } } @@ -339,10 +417,25 @@ Object.extend(String, { } }); -Object.extend(String.prototype, { - gsub: function(pattern, replacement) { +Object.extend(String.prototype, (function() { + + 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 = arguments.callee.prepareReplacement(replacement); + 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)) { @@ -354,69 +447,64 @@ Object.extend(String.prototype, { } } return result; - }, + } - sub: function(pattern, replacement, count) { - replacement = this.gsub.prepareReplacement(replacement); + 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); }); - }, + } - scan: function(pattern, iterator) { + function scan(pattern, iterator) { this.gsub(pattern, iterator); return String(this); - }, + } - truncate: function(length, truncation) { + 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); - }, + } - strip: function() { + function strip() { return this.replace(/^\s+/, '').replace(/\s+$/, ''); - }, + } - stripTags: function() { - return this.replace(/<\/?[^>]+>/gi, ''); - }, + function stripTags() { + return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); + } - stripScripts: function() { + function stripScripts() { return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); - }, + } - extractScripts: function() { + function extractScripts() { 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() { + function evalScripts() { return this.extractScripts().map(function(script) { return eval(script) }); - }, + } - escapeHTML: function() { - var self = arguments.callee; - self.text.data = this; - return self.div.innerHTML; - }, + function escapeHTML() { + return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + } + + function unescapeHTML() { + return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); + } - 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) { + function toQueryParams(separator) { var match = this.strip().match(/([^?#]*)(#.*)?$/); if (!match) return { }; @@ -434,22 +522,22 @@ Object.extend(String.prototype, { } return hash; }); - }, + } - toArray: function() { + function toArray() { return this.split(''); - }, + } - succ: function() { + function succ() { return this.slice(0, this.length - 1) + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); - }, + } - times: function(count) { + function times(count) { return count < 1 ? '' : new Array(count + 1).join(this); - }, + } - camelize: function() { + function camelize() { var parts = this.split('-'), len = parts.length; if (len == 1) return parts[0]; @@ -461,101 +549,117 @@ Object.extend(String.prototype, { camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); return camelized; - }, + } - capitalize: function() { + function capitalize() { 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(); - }, + 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(); + } - dasherize: function() { - return this.gsub(/_/,'-'); - }, + function dasherize() { + return this.replace(/_/g, '-'); + } - 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); + 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, '\\\'') + "'"; - }, + } - toJSON: function() { + function toJSON() { return this.inspect(true); - }, + } - unfilterJSON: function(filter) { - return this.sub(filter || Prototype.JSONFilter, '#{1}'); - }, + function unfilterJSON(filter) { + return this.replace(filter || Prototype.JSONFilter, '$1'); + } - isJSON: function() { + function isJSON() { 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) { + function evalJSON(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) { + function include(pattern) { return this.indexOf(pattern) > -1; - }, + } - startsWith: function(pattern) { + function startsWith(pattern) { return this.indexOf(pattern) === 0; - }, + } - endsWith: function(pattern) { + function endsWith(pattern) { var d = this.length - pattern.length; return d >= 0 && this.lastIndexOf(pattern) === d; - }, + } - empty: function() { + function empty() { return this == ''; - }, + } - blank: function() { + function blank() { 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,'<').replace(/>/g,'>'); - }, - unescapeHTML: function() { - return this.stripTags().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + function interpolate(object, pattern) { + return new Template(this, pattern).evaluate(object); } -}); - -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); + return { + gsub: gsub, + sub: sub, + scan: scan, + truncate: truncate, + strip: String.prototype.trim ? 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, + toJSON: toJSON, + unfilterJSON: unfilterJSON, + isJSON: isJSON, + evalJSON: evalJSON, + include: include, + startsWith: startsWith, + endsWith: endsWith, + empty: empty, + blank: blank, + interpolate: interpolate + }; +})()); var Template = Class.create({ initialize: function(template, pattern) { @@ -564,11 +668,11 @@ var Template = Class.create({ }, evaluate: function(object) { - if (Object.isFunction(object.toTemplateReplacements)) + if (object && Object.isFunction(object.toTemplateReplacements)) object = object.toTemplateReplacements(); return this.template.gsub(this.pattern, function(match) { - if (object == null) return ''; + if (object == null) return (match[1] + ''); var before = match[1] || ''; if (before == '\\') return match[2]; @@ -579,7 +683,7 @@ var Template = Class.create({ if (match == null) return before; while (match != null) { - var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; + 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); @@ -594,8 +698,8 @@ Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; var $break = { }; -var Enumerable = { - each: function(iterator, context) { +var Enumerable = (function() { + function each(iterator, context) { var index = 0; try { this._each(function(value) { @@ -605,17 +709,17 @@ var Enumerable = { if (e != $break) throw e; } return this; - }, + } - eachSlice: function(number, iterator, context) { + 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); - }, + } - all: function(iterator, context) { + function all(iterator, context) { iterator = iterator || Prototype.K; var result = true; this.each(function(value, index) { @@ -623,9 +727,9 @@ var Enumerable = { if (!result) throw $break; }); return result; - }, + } - any: function(iterator, context) { + function any(iterator, context) { iterator = iterator || Prototype.K; var result = false; this.each(function(value, index) { @@ -633,18 +737,18 @@ var Enumerable = { throw $break; }); return result; - }, + } - collect: function(iterator, context) { + function collect(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) { + function detect(iterator, context) { var result; this.each(function(value, index) { if (iterator.call(context, value, index)) { @@ -653,32 +757,32 @@ var Enumerable = { } }); return result; - }, + } - findAll: function(iterator, context) { + function findAll(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) { + function grep(filter, iterator, context) { iterator = iterator || Prototype.K; var results = []; if (Object.isString(filter)) - filter = new RegExp(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; - }, + } - include: function(object) { + function include(object) { if (Object.isFunction(this.indexOf)) if (this.indexOf(object) != -1) return true; @@ -690,31 +794,31 @@ var Enumerable = { } }); return found; - }, + } - inGroupsOf: function(number, fillWith) { + 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; }); - }, + } - inject: function(memo, iterator, context) { + function inject(memo, iterator, context) { this.each(function(value, index) { memo = iterator.call(context, memo, value, index); }); return memo; - }, + } - invoke: function(method) { + function invoke(method) { var args = $A(arguments).slice(1); return this.map(function(value) { return value[method].apply(value, args); }); - }, + } - max: function(iterator, context) { + function max(iterator, context) { iterator = iterator || Prototype.K; var result; this.each(function(value, index) { @@ -723,9 +827,9 @@ var Enumerable = { result = value; }); return result; - }, + } - min: function(iterator, context) { + function min(iterator, context) { iterator = iterator || Prototype.K; var result; this.each(function(value, index) { @@ -734,9 +838,9 @@ var Enumerable = { result = value; }); return result; - }, + } - partition: function(iterator, context) { + function partition(iterator, context) { iterator = iterator || Prototype.K; var trues = [], falses = []; this.each(function(value, index) { @@ -744,26 +848,26 @@ var Enumerable = { trues : falses).push(value); }); return [trues, falses]; - }, + } - pluck: function(property) { + function pluck(property) { var results = []; this.each(function(value) { results.push(value[property]); }); return results; - }, + } - reject: function(iterator, context) { + function reject(iterator, context) { var results = []; this.each(function(value, index) { if (!iterator.call(context, value, index)) results.push(value); }); return results; - }, + } - sortBy: function(iterator, context) { + function sortBy(iterator, context) { return this.map(function(value, index) { return { value: value, @@ -773,13 +877,13 @@ var Enumerable = { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }).pluck('value'); - }, + } - toArray: function() { + function toArray() { return this.map(); - }, + } - zip: function() { + function zip() { var iterator = Prototype.K, args = $A(arguments); if (Object.isFunction(args.last())) iterator = args.pop(); @@ -788,130 +892,152 @@ var Enumerable = { return this.map(function(value, index) { return iterator(collections.pluck(index)); }); - }, + } - size: function() { + function size() { return this.toArray().length; - }, + } - inspect: function() { + function inspect() { return '#<Enumerable:' + this.toArray().inspect() + '>'; } -}; -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 -}); + + + + + + + + + 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 (iterable.toArray) return iterable.toArray(); + 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; } -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; - }; +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; } Array.from = $A; -Object.extend(Array.prototype, Enumerable); -if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; +(function() { + var arrayProto = Array.prototype, + slice = arrayProto.slice, + _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available -Object.extend(Array.prototype, { - _each: function(iterator) { + function each(iterator) { for (var i = 0, length = this.length; i < length; i++) iterator(this[i]); - }, + } + if (!_each) _each = each; - clear: function() { + function clear() { this.length = 0; return this; - }, + } - first: function() { + function first() { return this[0]; - }, + } - last: function() { + function last() { return this[this.length - 1]; - }, + } - compact: function() { + function compact() { return this.select(function(value) { return value != null; }); - }, + } - flatten: function() { + function flatten() { return this.inject([], function(array, value) { - return array.concat(Object.isArray(value) ? - value.flatten() : [value]); + if (Object.isArray(value)) + return array.concat(value.flatten()); + array.push(value); + return array; }); - }, + } - without: function() { - var values = $A(arguments); + function without() { + var values = slice.call(arguments, 0); return this.select(function(value) { return !values.include(value); }); - }, + } - reverse: function(inline) { + function reverse(inline) { return (inline !== false ? this : this.toArray())._reverse(); - }, - - reduce: function() { - return this.length > 1 ? this : this[0]; - }, + } - uniq: function(sorted) { + 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; }); - }, + } - intersect: function(array) { + function intersect(array) { return this.uniq().findAll(function(item) { return array.detect(function(value) { return item === value }); }); - }, + } - clone: function() { - return [].concat(this); - }, - size: function() { + function clone() { + return slice.call(this, 0); + } + + function size() { return this.length; - }, + } - inspect: function() { + function inspect() { return '[' + this.map(Object.inspect).join(', ') + ']'; - }, + } - toJSON: function() { + function toJSON() { var results = []; this.each(function(object) { var value = Object.toJSON(object); @@ -919,205 +1045,270 @@ Object.extend(Array.prototype, { }); 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 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 $w(string) { - if (!Object.isString(string)) return []; - string = string.strip(); - return string ? string.split(/\s+/) : []; -} + 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; + } -if (Prototype.Browser.Opera){ - Array.prototype.concat = function() { - var array = []; - for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); + function concat() { + var array = slice.call(this, 0), item; 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]); + 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(arguments[i]); + array.push(item); } } 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; - }, + 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, + toJSON: toJSON + }); - toPaddedString: function(length, radix) { - var string = this.toString(radix || 10); - return '0'.times(length - string.length) + string; - }, + var CONCAT_ARGUMENTS_BUGGY = (function() { + return [].concat(arguments)[0][0] !== 1; + })(1,2) - toJSON: function() { - return isFinite(this) ? this.toString() : 'null'; - } -}); + if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; -$w('abs round ceil floor').each(function(method){ - Number.prototype[method] = Math[method].methodize(); -}); + 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)); } - return { - initialize: function(object) { - this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); - }, + function toQueryString() { + return this.inject([], function(results, pair) { + var key = encodeURIComponent(pair.key), values = pair.value; - _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; - }, + 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('&'); + } - get: function(key) { - // simulating poorly supported hasOwnProperty - if (this._object[key] !== Object.prototype[key]) - return this._object[key]; - }, + function inspect() { + return '#<Hash:{' + this.map(function(pair) { + return pair.map(Object.inspect).join(': '); + }).join(', ') + '}>'; + } - unset: function(key) { - var value = this._object[key]; - delete this._object[key]; - return value; - }, + function toJSON() { + return Object.toJSON(this.toObject()); + } - toObject: function() { - return Object.clone(this._object); - }, + function clone() { + return new Hash(this); + } - keys: function() { - return this.pluck('key'); - }, + 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: toJSON, + clone: clone + }; +})()); - values: function() { - return this.pluck('value'); - }, +Hash.from = $H; +Object.extend(Number.prototype, (function() { + function toColorPart() { + return this.toPaddedString(2, 16); + } - index: function(value) { - var match = this.detect(function(pair) { - return pair.value === value; - }); - return match && match.key; - }, + function succ() { + return this + 1; + } - merge: function(object) { - return this.clone().update(object); - }, + function times(iterator, context) { + $R(0, this, true).each(iterator, context); + return this; + } - update: function(object) { - return new Hash(object).inject(this, function(result, pair) { - result.set(pair.key, pair.value); - return result; - }); - }, + function toPaddedString(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + } - toQueryString: function() { - return this.inject([], function(results, pair) { - var key = encodeURIComponent(pair.key), values = pair.value; + function toJSON() { + return isFinite(this) ? this.toString() : 'null'; + } - 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 abs() { + return Math.abs(this); + } - inspect: function() { - return '#<Hash:{' + this.map(function(pair) { - return pair.map(Object.inspect).join(': '); - }).join(', ') + '}>'; - }, + function round() { + return Math.round(this); + } - toJSON: function() { - return Object.toJSON(this.toObject()); - }, + function ceil() { + return Math.ceil(this); + } - clone: function() { - return new Hash(this); - } + function floor() { + return Math.floor(this); } + + return { + toColorPart: toColorPart, + succ: succ, + times: times, + toPaddedString: toPaddedString, + toJSON: toJSON, + abs: abs, + round: round, + ceil: ceil, + floor: floor + }; })()); -Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; -Hash.from = $H; -var ObjectRange = Class.create(Enumerable, { - initialize: function(start, end, exclusive) { +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; - }, + } - _each: function(iterator) { + function _each(iterator) { var value = this.start; while (this.include(value)) { iterator(value); value = value.succ(); } - }, + } - include: function(value) { + function include(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); -}; + return { + initialize: initialize, + _each: _each, + include: include + }; +})()); + + var Ajax = { getTransport: function() { @@ -1164,7 +1355,6 @@ Ajax.Responders.register({ onCreate: function() { Ajax.activeRequestCount++ }, onComplete: function() { Ajax.activeRequestCount-- } }); - Ajax.Base = Class.create({ initialize: function(options) { this.options = { @@ -1186,7 +1376,6 @@ Ajax.Base = Class.create({ this.options.parameters = this.options.parameters.toObject(); } }); - Ajax.Request = Class.create(Ajax.Base, { _complete: false, @@ -1202,7 +1391,6 @@ Ajax.Request = Class.create(Ajax.Base, { 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'; } @@ -1210,7 +1398,6 @@ Ajax.Request = Class.create(Ajax.Base, { 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)) @@ -1269,7 +1456,6 @@ Ajax.Request = Class.create(Ajax.Base, { headers['Connection'] = 'close'; } - // user-defined headers if (typeof this.options.requestHeaders == 'object') { var extras = this.options.requestHeaders; @@ -1323,7 +1509,6 @@ Ajax.Request = Class.create(Ajax.Base, { } if (state == 'Complete') { - // avoid memory leak in MSIE: clean up this.transport.onreadystatechange = Prototype.emptyFunction; } }, @@ -1340,7 +1525,7 @@ Ajax.Request = Class.create(Ajax.Base, { getHeader: function(name) { try { return this.transport.getResponseHeader(name) || null; - } catch (e) { return null } + } catch (e) { return null; } }, evalResponse: function() { @@ -1360,6 +1545,13 @@ Ajax.Request = Class.create(Ajax.Base, { Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + + + + + + + Ajax.Response = Class.create({ initialize: function(request){ this.request = request; @@ -1381,6 +1573,7 @@ Ajax.Response = Class.create({ }, status: 0, + statusText: '', getStatus: Ajax.Request.prototype.getStatus, @@ -1510,6 +1703,9 @@ Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { 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++) @@ -1537,7 +1733,6 @@ if (Prototype.BrowserFeatures.XPath) { if (!window.Node) var Node = { }; if (!Node.ELEMENT_NODE) { - // DOM level 2 ECMAScript Language Binding Object.extend(Node, { ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, @@ -1554,13 +1749,30 @@ if (!Node.ELEMENT_NODE) { }); } -(function() { - var element = this.Element; - this.Element = function(tagName, attributes) { + +(function(global) { + + var SETATTRIBUTE_IGNORES_NAME = (function(){ + var elForm = document.createElement("form"); + var elInput = document.createElement("input"); + var root = document.documentElement; + elInput.setAttribute("name", "test"); + elForm.appendChild(elInput); + root.appendChild(elForm); + var isBuggy = elForm.elements + ? (typeof elForm.elements.test == "undefined") + : null; + root.removeChild(elForm); + elForm = elInput = null; + return isBuggy; + })(); + + var element = global.Element; + global.Element = function(tagName, attributes) { attributes = attributes || { }; tagName = tagName.toLowerCase(); var cache = Element.cache; - if (Prototype.Browser.IE && attributes.name) { + if (SETATTRIBUTE_IGNORES_NAME && attributes.name) { tagName = '<' + tagName + ' name="' + attributes.name + '">'; delete attributes.name; return Element.writeAttribute(document.createElement(tagName), attributes); @@ -1568,11 +1780,12 @@ if (!Node.ELEMENT_NODE) { 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); + Object.extend(global.Element, element || { }); + if (element) global.Element.prototype = element.prototype; +})(this); Element.cache = { }; +Element.idCounter = 1; Element.Methods = { visible: function(element) { @@ -1585,6 +1798,7 @@ Element.Methods = { return element; }, + hide: function(element) { element = $(element); element.style.display = 'none'; @@ -1603,15 +1817,89 @@ Element.Methods = { 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; - }, + update: (function(){ + + var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ + var el = document.createElement("select"), + isBuggy = true; + el.innerHTML = "<option value=\"test\">test</option>"; + if (el.options && el.options[0]) { + isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; + } + el = null; + return isBuggy; + })(); + + var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ + try { + var el = document.createElement("table"); + if (el && el.tBodies) { + el.innerHTML = "<tbody><tr><td>test</td></tr></tbody>"; + var isBuggy = typeof el.tBodies[0] == "undefined"; + el = null; + return isBuggy; + } + } catch (e) { + return true; + } + })(); + + var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { + var s = document.createElement("script"), + isBuggy = false; + try { + s.appendChild(document.createTextNode("")); + isBuggy = !s.firstChild || + s.firstChild && s.firstChild.nodeType !== 3; + } catch (e) { + isBuggy = true; + } + s = null; + return isBuggy; + })(); + + function update(element, content) { + element = $(element); + + 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); @@ -1696,11 +1984,11 @@ Element.Methods = { }, ancestors: function(element) { - return $(element).recursivelyCollect('parentNode'); + return Element.recursivelyCollect(element, 'parentNode'); }, descendants: function(element) { - return $(element).select("*"); + return Element.select(element, "*"); }, firstDescendant: function(element) { @@ -1717,16 +2005,17 @@ Element.Methods = { }, previousSiblings: function(element) { - return $(element).recursivelyCollect('previousSibling'); + return Element.recursivelyCollect(element, 'previousSibling'); }, nextSiblings: function(element) { - return $(element).recursivelyCollect('nextSibling'); + return Element.recursivelyCollect(element, 'nextSibling'); }, siblings: function(element) { element = $(element); - return element.previousSiblings().reverse().concat(element.nextSiblings()); + return Element.previousSiblings(element).reverse() + .concat(Element.nextSiblings(element)); }, match: function(element, selector) { @@ -1738,22 +2027,22 @@ Element.Methods = { up: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(element.parentNode); - var ancestors = element.ancestors(); + var ancestors = Element.ancestors(element); 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] : + 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 (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); - var previousSiblings = element.previousSiblings(); + var previousSiblings = Element.previousSiblings(element); return Object.isNumber(expression) ? previousSiblings[expression] : Selector.findElement(previousSiblings, expression, index); }, @@ -1761,27 +2050,28 @@ Element.Methods = { next: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); - var nextSiblings = element.nextSiblings(); + var nextSiblings = Element.nextSiblings(element); return Object.isNumber(expression) ? nextSiblings[expression] : Selector.findElement(nextSiblings, expression, index); }, - select: function() { - var args = $A(arguments), element = $(args.shift()); + + select: function(element) { + var args = Array.prototype.slice.call(arguments, 1); return Selector.findChildElements(element, args); }, - adjacent: function() { - var args = $A(arguments), element = $(args.shift()); + adjacent: function(element) { + var args = Array.prototype.slice.call(arguments, 1); return Selector.findChildElements(element.parentNode, args).without(element); }, identify: function(element) { element = $(element); - var id = element.readAttribute('id'), self = arguments.callee; + var id = Element.readAttribute(element, 'id'); if (id) return id; - do { id = 'anonymous_element_' + self.counter++ } while ($(id)); - element.writeAttribute('id', id); + do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); + Element.writeAttribute(element, 'id', id); return id; }, @@ -1820,11 +2110,11 @@ Element.Methods = { }, getHeight: function(element) { - return $(element).getDimensions().height; + return Element.getDimensions(element).height; }, getWidth: function(element) { - return $(element).getDimensions().width; + return Element.getDimensions(element).width; }, classNames: function(element) { @@ -1840,7 +2130,7 @@ Element.Methods = { addClassName: function(element, className) { if (!(element = $(element))) return; - if (!element.hasClassName(className)) + if (!Element.hasClassName(element, className)) element.className += (element.className ? ' ' : '') + className; return element; }, @@ -1854,11 +2144,10 @@ Element.Methods = { toggleClassName: function(element, className) { if (!(element = $(element))) return; - return element[element.hasClassName(className) ? - 'removeClassName' : 'addClassName'](className); + return Element[Element.hasClassName(element, className) ? + 'removeClassName' : 'addClassName'](element, className); }, - // removes whitespace-only text node children cleanWhitespace: function(element) { element = $(element); var node = element.firstChild; @@ -1892,7 +2181,7 @@ Element.Methods = { scrollTo: function(element) { element = $(element); - var pos = element.cumulativeOffset(); + var pos = Element.cumulativeOffset(element); window.scrollTo(pos[0], pos[1]); return element; }, @@ -1940,18 +2229,17 @@ Element.Methods = { getDimensions: function(element) { element = $(element); - var display = element.getStyle('display'); + var display = Element.getStyle(element, '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'; + if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari + els.position = 'absolute'; els.display = 'block'; var originalWidth = element.clientWidth; var originalHeight = element.clientHeight; @@ -1967,8 +2255,6 @@ Element.Methods = { 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; @@ -2034,10 +2320,9 @@ Element.Methods = { absolutize: function(element) { element = $(element); - if (element.getStyle('position') == 'absolute') return element; - // Position.prepare(); // To be done manually by Scripty when it needs it. + if (Element.getStyle(element, 'position') == 'absolute') return element; - var offsets = element.positionedOffset(); + var offsets = Element.positionedOffset(element); var top = offsets[1]; var left = offsets[0]; var width = element.clientWidth; @@ -2058,8 +2343,7 @@ Element.Methods = { relativize: function(element) { element = $(element); - if (element.getStyle('position') == 'relative') return element; - // Position.prepare(); // To be done manually by Scripty when it needs it. + if (Element.getStyle(element, 'position') == 'relative') return element; element.style.position = 'relative'; var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); @@ -2101,7 +2385,6 @@ Element.Methods = { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; - // Safari fix if (element.offsetParent == document.body && Element.getStyle(element, 'position') == 'absolute') break; @@ -2128,28 +2411,22 @@ Element.Methods = { offsetLeft: 0 }, arguments[2] || { }); - // find page position of source source = $(source); - var p = source.viewportOffset(); + var p = Element.viewportOffset(source); - // 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(); + parent = Element.getOffsetParent(element); + delta = Element.viewportOffset(parent); } - // 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'; @@ -2158,10 +2435,9 @@ Element.Methods = { } }; -Element.Methods.identify.counter = 1; - Object.extend(Element.Methods, { getElementsBySelector: Element.Methods.select, + childElements: Element.Methods.immediateDescendants }); @@ -2182,11 +2458,8 @@ if (Prototype.Browser.Opera) { 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()]) @@ -2219,12 +2492,9 @@ if (Prototype.Browser.Opera) { } 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'); @@ -2244,8 +2514,6 @@ else if (Prototype.Browser.IE) { 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 }); @@ -2306,36 +2574,92 @@ else if (Prototype.Browser.IE) { 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(); + Element._attributeTranslations = (function(){ + + var classProp = 'className'; + var forProp = 'for'; + + var 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 }, - title: function(element) { - return element.title; + 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'); + el.onclick = Prototype.emptyFunction; + var value = el.getAttribute('onclick'); + var f; + + 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({ @@ -2363,8 +2687,8 @@ else if (Prototype.Browser.IE) { (function(v) { Object.extend(v, { - href: v._getAttr, - src: v._getAttr, + href: v._getAttr2, + src: v._getAttr2, type: v._getAttr, action: v._getAttrNode, disabled: v._flag, @@ -2391,6 +2715,26 @@ else if (Prototype.Browser.IE) { 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)) { @@ -2420,9 +2764,6 @@ else if (Prototype.Browser.WebKit) { 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 { @@ -2438,30 +2779,7 @@ else if (Prototype.Browser.WebKit) { }; } -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')) { +if ('outerHTML' in document.documentElement) { Element.Methods.replace = function(element, content) { element = $(element); @@ -2529,12 +2847,13 @@ Element._insertionTranslations = { }; (function() { - Object.extend(this.tags, { - THEAD: this.tags.TBODY, - TFOOT: this.tags.TBODY, - TH: this.tags.TD + var tags = Element._insertionTranslations.tags; + Object.extend(tags, { + THEAD: tags.TBODY, + TFOOT: tags.TBODY, + TH: tags.TD }); -}).call(Element._insertionTranslations); +})(); Element.Methods.Simulated = { hasAttribute: function(element, attribute) { @@ -2548,41 +2867,81 @@ 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; -} +(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() { - if (Prototype.BrowserFeatures.SpecificElementExtensions) + + function checkDeficiency(tagName) { + if (typeof window.Element != 'undefined') { + var proto = window.Element.prototype; + if (proto) { + var id = '_' + (Math.random()+'').slice(2); + var 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 || element._extendedByPrototype || + if (!element || typeof element._extendedByPrototype != 'undefined' || element.nodeType != 1 || element == window) return element; var methods = Object.clone(Methods), - tagName = element.tagName.toUpperCase(), property, value; + tagName = element.tagName.toUpperCase(); - // 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(); - } + extendElementWith(element, methods); 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); @@ -2661,14 +3020,18 @@ Element.addMethods = function(methods) { klass = 'HTML' + tagName.capitalize() + 'Element'; if (window[klass]) return window[klass]; - window[klass] = { }; - window[klass].prototype = document.createElement(tagName)['__proto__']; - return window[klass]; + var element = document.createElement(tagName); + var proto = element['__proto__'] || element.constructor.prototype; + element = null; + return proto; } + var elementPrototype = window.HTMLElement ? HTMLElement.prototype : + Element.prototype; + if (F.ElementExtensions) { - copy(Element.Methods, HTMLElement.prototype); - copy(Element.Methods.Simulated, HTMLElement.prototype, true); + copy(Element.Methods, elementPrototype); + copy(Element.Methods.Simulated, elementPrototype, true); } if (F.SpecificElementExtensions) { @@ -2686,38 +3049,109 @@ Element.addMethods = function(methods) { 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; - }, +document.viewport = { - getHeight: function() { - return this.getDimensions().height; + 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); + 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[0]; + } + + 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); + } +}); /* 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. */ @@ -2738,31 +3172,52 @@ var Selector = Class.create({ }, - shouldUseXPath: function() { - if (!Prototype.BrowserFeatures.XPath) return false; + shouldUseXPath: (function() { - var e = this.expression; + var IS_DESCENDANT_SELECTOR_BUGGY = (function(){ + var isBuggy = false; + if (document.evaluate && window.XPathResult) { + var el = document.createElement('div'); + el.innerHTML = '<ul><li></li></ul><div><ul><li></li></ul></div>'; - // Safari 3 chokes on :*-of-type and :empty - if (Prototype.Browser.WebKit && - (e.include("-of-type") || e.include(":empty"))) - return false; + var xpath = ".//*[local-name()='ul' or local-name()='UL']" + + "//*[local-name()='li' or local-name()='LI']"; - // XPath can't do namespaced attributes, nor can it read - // the "checked" property from DOM nodes - if ((/(\[[\w-]*?:|:checked)/).test(e)) - return false; + var result = document.evaluate(xpath, el, null, + XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); - return true; - }, + isBuggy = (result.snapshotLength !== 2); + el = null; + } + return isBuggy; + })(); + + return function() { + if (!Prototype.BrowserFeatures.XPath) return false; + + var e = this.expression; + + if (Prototype.Browser.WebKit && + (e.include("-of-type") || e.include(":empty"))) + return false; + + if ((/(\[[\w-]*?:|:checked)/).test(e)) + return false; + + if (IS_DESCENDANT_SELECTOR_BUGGY) return false; + + return true; + } + + })(), shouldUseSelectorsAPI: function() { if (!Prototype.BrowserFeatures.SelectorsAPI) return false; + if (Selector.CASE_INSENSITIVE_CLASS_NAMES) 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) { @@ -2774,7 +3229,7 @@ var Selector = Class.create({ compileMatcher: function() { var e = this.expression, ps = Selector.patterns, h = Selector.handlers, - c = Selector.criteria, le, p, m; + c = Selector.criteria, le, p, m, len = ps.length, name; if (Selector._cache[e]) { this.matcher = Selector._cache[e]; @@ -2786,11 +3241,12 @@ var Selector = Class.create({ while (e && le != e && (/\S/).test(e)) { le = e; - for (var i in ps) { - p = ps[i]; + for (var i = 0; i<len; i++) { + p = ps[i].re; + name = ps[i].name; if (m = e.match(p)) { - this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : - new Template(c[i]).evaluate(m)); + this.matcher.push(Object.isFunction(c[name]) ? c[name](m) : + new Template(c[name]).evaluate(m)); e = e.replace(m[0], ''); break; } @@ -2804,7 +3260,7 @@ var Selector = Class.create({ compileXPathMatcher: function() { var e = this.expression, ps = Selector.patterns, - x = Selector.xpath, le, m; + x = Selector.xpath, le, m, len = ps.length, name; if (Selector._cache[e]) { this.xpath = Selector._cache[e]; return; @@ -2813,10 +3269,11 @@ var Selector = Class.create({ 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)); + for (var i = 0; i<len; i++) { + name = ps[i].name; + if (m = e.match(ps[i].re)) { + this.matcher.push(Object.isFunction(x[name]) ? x[name](m) : + new Template(x[name]).evaluate(m)); e = e.replace(m[0], ''); break; } @@ -2833,11 +3290,9 @@ var Selector = Class.create({ 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(); + id = id.replace(/([\.:])/g, "\\$1"); e = "#" + id + " " + e; } @@ -2856,21 +3311,18 @@ var Selector = Class.create({ this.tokens = []; var e = this.expression, ps = Selector.patterns, as = Selector.assertions; - var le, p, m; + var le, p, m, len = ps.length, name; while (e && le !== e && (/\S/).test(e)) { le = e; - for (var i in ps) { - p = ps[i]; + for (var i = 0; i<len; i++) { + p = ps[i].re; + name = ps[i].name; 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)]); + if (as[name]) { + this.tokens.push([name, 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); } } @@ -2897,6 +3349,21 @@ var Selector = Class.create({ } }); +if (Prototype.BrowserFeatures.SelectorsAPI && + document.compatMode === 'BackCompat') { + Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){ + var div = document.createElement('div'), + span = document.createElement('span'); + + div.id = "prototype_test_id"; + span.className = 'Test'; + div.appendChild(span); + var isIgnored = (div.querySelector('#prototype_test_id .test') !== null); + div = span = null; + return isIgnored; + })(); +} + Object.extend(Selector, { _cache: { }, @@ -2946,14 +3413,15 @@ Object.extend(Selector, { 'enabled': "[not(@disabled) and (@type!='hidden')]", 'not': function(m) { var e = m[6], p = Selector.patterns, - x = Selector.xpath, le, v; + x = Selector.xpath, le, v, len = p.length, name; 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); + for (var i = 0; i<len; i++) { + name = p[i].name + if (m = e.match(p[i].re)) { + v = Object.isFunction(x[name]) ? x[name](m) : new Template(x[name]).evaluate(m); exclusion.push("(" + v.substring(1, v.length - 1) + ")"); e = e.replace(m[0], ''); break; @@ -3021,25 +3489,20 @@ Object.extend(Selector, { 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/, + patterns: [ + { name: 'laterSibling', re: /^\s*~\s*/ }, + { name: 'child', re: /^\s*>\s*/ }, + { name: 'adjacent', re: /^\s*\+\s*/ }, + { name: 'descendant', re: /^\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|([^'"][^\]]*?)))?\]/ - }, + { name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ }, + { name: 'id', re: /^#([\w\-\*]+)(\b|$)/ }, + { name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ }, + { name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ }, + { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ }, + { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ } + ], - // for Selector.match and Element#match assertions: { tagName: function(element, matches) { return matches[1].toUpperCase() == element.tagName.toUpperCase(); @@ -3064,15 +3527,12 @@ Object.extend(Selector, { }, 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++) @@ -3080,15 +3540,32 @@ Object.extend(Selector, { return nodes; }, - unmark: function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node._countedByPrototype = undefined; - return nodes; - }, + unmark: (function(){ + + var PROPERTIES_ATTRIBUTES_MAP = (function(){ + var el = document.createElement('div'), + isBuggy = false, + propName = '_countedByPrototype', + value = 'x' + el[propName] = value; + isBuggy = (el.getAttribute(propName) === value); + el = null; + return isBuggy; + })(); + + return PROPERTIES_ATTRIBUTES_MAP ? + function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node.removeAttribute('_countedByPrototype'); + return nodes; + } : + function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = void 0; + 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) { @@ -3102,19 +3579,17 @@ Object.extend(Selector, { } }, - // 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) { + if (typeof (n = nodes[i])._countedByPrototype == 'undefined') { 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++) @@ -3158,13 +3633,11 @@ Object.extend(Selector, { 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)); @@ -3180,8 +3653,19 @@ Object.extend(Selector, { id: function(nodes, root, id, combinator) { var targetNode = $(id), h = Selector.handlers; - if (!targetNode) return []; - if (!nodes && root == document) return [targetNode]; + + if (root == document) { + if (!targetNode) return []; + if (!nodes) return [targetNode]; + } else { + if (!root.sourceIndex || root.sourceIndex < 1) { + var nodes = root.getElementsByTagName('*'); + for (var j = 0, node; node = nodes[j]; j++) { + if (node.id === id) return [node]; + } + } + } + if (nodes) { if (combinator) { if (combinator == 'child') { @@ -3293,7 +3777,6 @@ Object.extend(Selector, { 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) { @@ -3302,7 +3785,6 @@ Object.extend(Selector, { }); }, - // 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'; @@ -3336,7 +3818,6 @@ Object.extend(Selector, { '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); } @@ -3379,8 +3860,6 @@ Object.extend(Selector, { '^=': 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() + '-'); } @@ -3423,19 +3902,10 @@ Object.extend(Selector, { 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; } }); } @@ -3443,9 +3913,11 @@ if (Prototype.Browser.IE) { function $$() { return Selector.findChildElements(document, $A(arguments)); } + var Form = { reset: function(form) { - $(form).reset(); + form = $(form); + form.reset(); return form; }, @@ -3460,7 +3932,6 @@ var Form = { 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); } @@ -3480,13 +3951,18 @@ Form.Methods = { }, 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; - } - ); + 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) { @@ -3526,7 +4002,7 @@ Form.Methods = { }).sortBy(function(element) { return element.tabIndex }).first(); return firstByIndex ? firstByIndex : elements.find(function(element) { - return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + return /^(?:input|select|textarea)$/i.test(element.tagName); }); }, @@ -3557,6 +4033,7 @@ Form.Methods = { /*--------------------------------------------------------------------------*/ + Form.Element = { focus: function(element) { $(element).focus(); @@ -3570,6 +4047,7 @@ Form.Element = { }; Form.Element.Methods = { + serialize: function(element) { element = $(element); if (!element.disabled && element.name) { @@ -3610,7 +4088,7 @@ Form.Element.Methods = { try { element.focus(); if (element.select && (element.tagName.toLowerCase() != 'input' || - !['button', 'reset', 'submit'].include(element.type))) + !(/^(?:button|reset|submit)$/i.test(element.type)))) element.select(); } catch (e) { } return element; @@ -3632,6 +4110,7 @@ Form.Element.Methods = { /*--------------------------------------------------------------------------*/ var Field = Form.Element; + var $F = Form.Element.Methods.getValue; /*--------------------------------------------------------------------------*/ @@ -3694,13 +4173,13 @@ Form.Element.Serializers = { }, 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); @@ -3782,354 +4261,441 @@ Form.EventObserver = Class.create(Abstract.EventObserver, { 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); - } -}); +(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: {} + }; -Event.Methods = (function() { - var isButton; + 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]; + _isButton = function(event, code) { + return event.button === buttonMap[code]; }; - } else if (Prototype.Browser.WebKit) { - isButton = function(event, code) { + _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) { + _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); - }, + function isLeftClick(event) { return _isButton(event, 0) } - 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); - }, + function isMiddleClick(event) { return _isButton(event, 1) } - 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)) - }; - }, + function isRightClick(event) { return _isButton(event, 2) } + + function element(event) { + event = Event.extend(event); - pointerX: function(event) { return Event.pointer(event).x }, - pointerY: function(event) { return Event.pointer(event).y }, + var node = event.target, type = event.type, + currentTarget = event.currentTarget; - stop: function(event) { - Event.extend(event); - event.preventDefault(); - event.stopPropagation(); - event.stopped = true; + 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; + var elements = [element].concat(element.ancestors()); + return Selector.findElement(elements, expression, 0); + } + + 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 }; -})(); -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) { + 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]" } + inspect: function() { return '[object Event]' } }); - return function(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, - relatedTarget: Event.relatedTarget(event), + target: event.srcElement || element, + relatedTarget: _relatedTarget(event), pageX: pointer.x, pageY: pointer.y }); + return Object.extend(event, methods); }; - } else { - Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__']; + Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; Object.extend(Event.prototype, methods); - return Prototype.K; + Event.extend = 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 _createResponder(element, eventName, handler) { + var registry = Element.retrieve(element, 'prototype_event_registry'); - function getDOMEventName(eventName) { - if (eventName && eventName.include(':')) return "dataavailable"; - return eventName; - } + if (Object.isUndefined(registry)) { + CACHE.push(element); + registry = Element.retrieve(element, 'prototype_event_registry', $H()); + } - function getCacheForID(id) { - return cache[id] = cache[id] || { }; - } + var respondersForEvent = registry.get(eventName); + if (Object.isUndefined(respondersForEvent)) { + respondersForEvent = []; + registry.set(eventName, respondersForEvent); + } - function getWrappersForEventName(id, eventName) { - var c = getCacheForID(id); - return c[eventName] = c[eventName] || []; - } + if (respondersForEvent.pluck('handler').include(handler)) return false; - function createWrapper(element, eventName, handler) { - var id = getEventID(element); - var c = getWrappersForEventName(id, eventName); - if (c.pluck("handler").include(handler)) return false; + var responder; + if (eventName.include(":")) { + responder = function(event) { + if (Object.isUndefined(event.eventName)) + return false; - var wrapper = function(event) { - if (!Event || !Event.extend || - (event.eventName && event.eventName != eventName)) + if (event.eventName !== eventName) return false; - Event.extend(event); - handler.call(element, event); - }; + 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; } + } - wrapper.handler = handler; - c.push(wrapper); - return wrapper; - } + if (parent === element) return; - function findWrapper(id, eventName, handler) { - var c = getWrappersForEventName(id, eventName); - return c.find(function(wrapper) { return wrapper.handler == handler }); - } + handler.call(element, event); + }; + } + } else { + responder = function(event) { + Event.extend(event, element); + handler.call(element, event); + }; + } + } - function destroyWrapper(id, eventName, handler) { - var c = getCacheForID(id); - if (!c[eventName]) return false; - c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); + responder.handler = handler; + respondersForEvent.push(responder); + return responder; } - function destroyCache() { - for (var id in cache) - for (var eventName in cache[id]) - cache[id][eventName] = null; + function _destroyCache() { + for (var i = 0, length = CACHE.length; i < length; i++) { + Event.stopObserving(CACHE[i]); + CACHE[i] = null; + } } + var CACHE = []; - // Internet Explorer needs to remove event handlers on page unload - // in order to avoid memory leaks. - if (window.attachEvent) { - window.attachEvent("onunload", destroyCache); - } + if (Prototype.Browser.IE) + 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) { + if (Prototype.Browser.WebKit) window.addEventListener('unload', Prototype.emptyFunction, false); + + + var _getDOMEventName = Prototype.K; + + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { + _getDOMEventName = function(eventName) { + var translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; + return eventName in translations ? translations[eventName] : eventName; + }; } - return { - observe: function(element, eventName, handler) { - element = $(element); - var name = getDOMEventName(eventName); + function observe(element, eventName, handler) { + element = $(element); - var wrapper = createWrapper(element, eventName, handler); - if (!wrapper) return element; + var responder = _createResponder(element, eventName, handler); - if (element.addEventListener) { - element.addEventListener(name, wrapper, false); - } else { - element.attachEvent("on" + name, wrapper); + 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); - return element; - }, + if (element.addEventListener) + element.addEventListener(actualEventName, responder, false); + else + element.attachEvent("on" + actualEventName, responder); + } - stopObserving: function(element, eventName, handler) { - element = $(element); - var id = getEventID(element), name = getDOMEventName(eventName); + return element; + } - if (!handler && eventName) { - getWrappersForEventName(id, eventName).each(function(wrapper) { - element.stopObserving(eventName, wrapper.handler); - }); - return element; + function stopObserving(element, eventName, handler) { + element = $(element); - } else if (!eventName) { - Object.keys(getCacheForID(id)).each(function(eventName) { - element.stopObserving(eventName); - }); - return element; - } + var registry = Element.retrieve(element, 'prototype_event_registry'); - var wrapper = findWrapper(id, eventName, handler); - if (!wrapper) return element; + if (Object.isUndefined(registry)) return element; - if (element.removeEventListener) { - element.removeEventListener(name, wrapper, false); - } else { - element.detachEvent("on" + name, wrapper); - } + if (eventName && !handler) { + var responders = registry.get(eventName); - destroyWrapper(id, eventName, handler); + if (Object.isUndefined(responders)) return element; + responders.each( function(r) { + Element.stopObserving(element, eventName, r.handler); + }); return element; - }, + } else if (!eventName) { + registry.each( function(pair) { + var eventName = pair.key, responders = pair.value; - fire: function(element, eventName, memo) { - element = $(element); - if (element == document && document.createEvent && !element.dispatchEvent) - element = document.documentElement; + responders.each( function(r) { + Element.stopObserving(element, eventName, r.handler); + }); + }); + return element; + } - var event; - if (document.createEvent) { - event = document.createEvent("HTMLEvents"); - event.initEvent("dataavailable", true, true); - } else { - event = document.createEventObject(); - event.eventType = "ondataavailable"; - } + var responders = registry.get(eventName); - event.eventName = eventName; - event.memo = memo || { }; + if (!responders) return; - if (document.createEvent) { - element.dispatchEvent(event); - } else { - element.fireEvent(event.eventType, event); + var responder = responders.find( function(r) { return r.handler === handler; }); + if (!responder) return element; + + var actualEventName = _getDOMEventName(eventName); + + if (eventName.include(':')) { + if (element.removeEventListener) + element.removeEventListener("dataavailable", responder, false); + else { + element.detachEvent("ondataavailable", responder); + element.detachEvent("onfilterchange", responder); } + } else { + if (element.removeEventListener) + element.removeEventListener(actualEventName, responder, false); + else + element.detachEvent('on' + actualEventName, responder); + } - return Event.extend(event); + 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'; } - }; -})()); -Object.extend(Event, Event.Methods); + event.eventName = eventName; + event.memo = memo || { }; -Element.addMethods({ - fire: Event.fire, - observe: Event.observe, - stopObserving: Event.stopObserving -}); + if (document.createEvent) + element.dispatchEvent(event); + else + element.fireEvent(event.eventType, event); -Object.extend(document, { - fire: Element.Methods.fire.methodize(), - observe: Element.Methods.observe.methodize(), - stopObserving: Element.Methods.stopObserving.methodize(), - loaded: false -}); + return Event.extend(event); + } + + + Object.extend(Event, Event.Methods); + + Object.extend(Event, { + fire: fire, + observe: observe, + stopObserving: stopObserving + }); + + Element.addMethods({ + fire: fire, + + observe: observe, + + stopObserving: stopObserving + }); + + Object.extend(document, { + fire: fire.methodize(), + + observe: observe.methodize(), + + stopObserving: stopObserving.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 and John Resig. */ + Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ var timer; function fireContentLoadedEvent() { if (document.loaded) return; - if (timer) window.clearInterval(timer); - document.fire("dom:loaded"); + if (timer) window.clearTimeout(timer); document.loaded = true; + document.fire('dom:loaded'); } - if (document.addEventListener) { - if (Prototype.Browser.WebKit) { - timer = window.setInterval(function() { - if (/loaded|complete/.test(document.readyState)) - fireContentLoadedEvent(); - }, 0); - - Event.observe(window, "load", fireContentLoadedEvent); + function checkReadyState() { + if (document.readyState === 'complete') { + document.stopObserving('readystatechange', checkReadyState); + fireContentLoadedEvent(); + } + } - } else { - document.addEventListener("DOMContentLoaded", - fireContentLoadedEvent, false); + function pollDoScroll() { + try { document.documentElement.doScroll('left'); } + catch(e) { + timer = pollDoScroll.defer(); + return; } + fireContentLoadedEvent(); + } + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); } else { - document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>"); - $("__onDOMContentLoaded").onreadystatechange = function() { - if (this.readyState == "complete") { - this.onreadystatechange = null; - fireContentLoadedEvent(); - } - }; + document.observe('readystatechange', checkReadyState); + if (window == top) + timer = pollDoScroll.defer(); } + + Event.observe(window, 'load', fireContentLoadedEvent); })(); + +Element.addMethods(); + /*------------------------------- DEPRECATED -------------------------------*/ Hash.toQueryString = Object.toQueryString; @@ -4158,16 +4724,9 @@ var Insertion = { var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); -// This should be moved to script.aculo.us; notice the deprecated methods -// further below, that map to the newer Element methods. var Position = { - // set to true if needed, warning: firefox performance problems - // NOT neeeded for page scrolling, only if draggable contained in - // scrollable elements includeScrollOffsets: false, - // must be called before calling withinIncludingScrolloffset, every time the - // page is scrolled prepare: function() { this.deltaX = window.pageXOffset || document.documentElement.scrollLeft @@ -4179,7 +4738,6 @@ var Position = { || 0; }, - // caches x/y coordinate pair to use with overlap within: function(element, x, y) { if (this.includeScrollOffsets) return this.withinIncludingScrolloffsets(element, x, y); @@ -4206,7 +4764,6 @@ var Position = { this.xcomp < this.offset[0] + element.offsetWidth); }, - // within must be called directly before overlap: function(mode, element) { if (!mode) return 0; if (mode == 'vertical') @@ -4217,7 +4774,6 @@ var Position = { element.offsetWidth; }, - // Deprecation layer -- use newer Element methods now (1.5.2). cumulativeOffset: Element.Methods.cumulativeOffset, @@ -4316,5 +4872,3 @@ Element.ClassNames.prototype = { Object.extend(Element.ClassNames.prototype, Enumerable); /*--------------------------------------------------------------------------*/ - -Element.addMethods();
\ No newline at end of file diff --git a/railties/lib/generators/rails/app/templates/public/javascripts/rails.js b/railties/lib/generators/rails/app/templates/public/javascripts/rails.js index 2a0a05d25f..f7ddba390a 100644 --- a/railties/lib/generators/rails/app/templates/public/javascripts/rails.js +++ b/railties/lib/generators/rails/app/templates/public/javascripts/rails.js @@ -1,4 +1,11 @@ document.observe("dom:loaded", function() { + var authToken = $$('meta[name=csrf-token]').first().readAttribute('content'), + authParam = $$('meta[name=csrf-param]').first().readAttribute('content'), + formTemplate = '<form method="#{method}" action="#{action}">\ + #{realmethod}<input name="#{param}" value="#{token}" type="hidden">\ + </form>', + realmethodTemplate = '<input name="_method" value="#{method}" type="hidden">'; + function handleRemote(element) { var method, url, params; @@ -34,20 +41,46 @@ document.observe("dom:loaded", function() { } $(document.body).observe("click", function(event) { + var message = event.element().readAttribute('data-confirm'); + if (message && !confirm(message)) { + event.stop(); + return false; + } + var element = event.findElement("a[data-remote=true]"); if (element) { handleRemote(element); event.stop(); } - }); - $(document.body).observe("ajax:before", function(event) { - var message = event.element().readAttribute('data-confirm'); - if (message && !confirm(message)) event.stop(); + var element = event.findElement("a[data-method]"); + if (element && element.readAttribute('data-remote') != 'true') { + var method = element.readAttribute('data-method'), + piggyback = method.toLowerCase() != 'post', + formHTML = formTemplate.interpolate({ + method: 'POST', + realmethod: piggyback ? realmethodTemplate.interpolate({ method: method }) : '', + action: element.readAttribute('href'), + token: authToken, + param: authParam + }); + + var form = new Element('div').update(formHTML).down().hide(); + this.insert({ bottom: form }); + + form.submit(); + event.stop(); + } }); // TODO: I don't think submit bubbles in IE $(document.body).observe("submit", function(event) { + var message = event.element().readAttribute('data-confirm'); + if (message && !confirm(message)) { + event.stop(); + return false; + } + var inputs = event.element().select("input[type=submit][data-disable-with]"); inputs.each(function(input) { input.disabled = true; diff --git a/railties/lib/generators/rails/app/templates/script/rails b/railties/lib/generators/rails/app/templates/script/rails index 2fcf6d09b5..b01d1ee183 100644 --- a/railties/lib/generators/rails/app/templates/script/rails +++ b/railties/lib/generators/rails/app/templates/script/rails @@ -3,7 +3,6 @@ ENV_PATH = File.expand_path('../../config/environment', __FILE__) BOOT_PATH = File.expand_path('../../config/boot', __FILE__) APP_PATH = File.expand_path('../../config/application', __FILE__) -ROOT_PATH = File.expand_path('../..', __FILE__) require BOOT_PATH require 'rails/commands' diff --git a/railties/lib/generators/rails/app/templates/test/test_helper.rb b/railties/lib/generators/rails/app/templates/test/test_helper.rb index 45b551fc7d..8bf1192ffe 100644 --- a/railties/lib/generators/rails/app/templates/test/test_helper.rb +++ b/railties/lib/generators/rails/app/templates/test/test_helper.rb @@ -1,5 +1,5 @@ ENV["RAILS_ENV"] = "test" -require File.expand_path(File.dirname(__FILE__) + "/../config/environment") +require File.expand_path('../../config/environment', __FILE__) require 'rails/test_help' class ActiveSupport::TestCase diff --git a/railties/lib/generators/rails/controller/USAGE b/railties/lib/generators/rails/controller/USAGE index 6ed4b2edfc..70618a3906 100644 --- a/railties/lib/generators/rails/controller/USAGE +++ b/railties/lib/generators/rails/controller/USAGE @@ -9,7 +9,7 @@ Description: template engine and test framework generators. Example: - `./script/generate controller CreditCard open debit credit close` + `rails generate controller CreditCard open debit credit close` Credit card controller with URLs like /credit_card/debit. Controller: app/controllers/credit_card_controller.rb diff --git a/railties/lib/generators/rails/generator/USAGE b/railties/lib/generators/rails/generator/USAGE index ca7d3f62d0..d8c3f7f634 100644 --- a/railties/lib/generators/rails/generator/USAGE +++ b/railties/lib/generators/rails/generator/USAGE @@ -3,7 +3,7 @@ Description: CamelCased or under_scored, as an argument. Example: - `./script/generate generator Awesome` + `rails generate generator Awesome` creates a standard awesome generator: lib/generators/awesome/ diff --git a/railties/lib/generators/rails/generator/templates/USAGE.tt b/railties/lib/generators/rails/generator/templates/USAGE.tt index ea9f4f12cc..1bb8df840d 100644 --- a/railties/lib/generators/rails/generator/templates/USAGE.tt +++ b/railties/lib/generators/rails/generator/templates/USAGE.tt @@ -2,7 +2,7 @@ Description: Explain the generator Example: - ./script/generate <%= file_name %> Thing + rails generate <%= file_name %> Thing This will create: what/will/it/create diff --git a/railties/lib/generators/rails/helper/USAGE b/railties/lib/generators/rails/helper/USAGE index 531c9b390a..c0ddb0f606 100644 --- a/railties/lib/generators/rails/helper/USAGE +++ b/railties/lib/generators/rails/helper/USAGE @@ -9,7 +9,7 @@ Description: test framework. Example: - `./script/generate helper CreditCard` + `rails generate helper CreditCard` Credit card helper. Helper: app/helpers/credit_card_helper.rb diff --git a/railties/lib/generators/rails/integration_test/USAGE b/railties/lib/generators/rails/integration_test/USAGE index b76c35a702..57ee3543e6 100644 --- a/railties/lib/generators/rails/integration_test/USAGE +++ b/railties/lib/generators/rails/integration_test/USAGE @@ -6,5 +6,5 @@ Description: TestUnit. Example: - `./script/generate integration_test GeneralStories` creates a GeneralStories + `rails generate integration_test GeneralStories` creates a GeneralStories integration test in test/integration/general_stories_test.rb diff --git a/railties/lib/generators/rails/mailer/USAGE b/railties/lib/generators/rails/mailer/USAGE index beae79fbf4..a08d459739 100644 --- a/railties/lib/generators/rails/mailer/USAGE +++ b/railties/lib/generators/rails/mailer/USAGE @@ -6,7 +6,7 @@ Description: engine and test framework generators. Example: - `./script/generate mailer Notifications signup forgot_password invoice` + `rails generate mailer Notifications signup forgot_password invoice` creates a Notifications mailer class, views, test, and fixtures: Mailer: app/mailers/notifications.rb diff --git a/railties/lib/generators/rails/metal/USAGE b/railties/lib/generators/rails/metal/USAGE index 123ec6c03f..c88325a444 100644 --- a/railties/lib/generators/rails/metal/USAGE +++ b/railties/lib/generators/rails/metal/USAGE @@ -2,7 +2,7 @@ Description: Cast some metal! Examples: - `./script/generate metal poller` + `rails generate metal poller` This will create: Metal: app/metal/poller.rb diff --git a/railties/lib/generators/rails/migration/USAGE b/railties/lib/generators/rails/migration/USAGE index d91127aac3..d21c81b760 100644 --- a/railties/lib/generators/rails/migration/USAGE +++ b/railties/lib/generators/rails/migration/USAGE @@ -8,12 +8,12 @@ Description: column lines from supplied attributes: AddColumnsToTable or RemoveColumnsFromTable Example: - `./script/generate migration AddSslFlag` + `rails generate migration AddSslFlag` If the current date is May 14, 2008 and the current time 09:09:12, this creates the AddSslFlag migration db/migrate/20080514090912_add_ssl_flag.rb - `./script/generate migration AddTitleBodyToPost title:string body:text published:boolean` + `rails generate migration AddTitleBodyToPost title:string body:text published:boolean` This will create the AddTitleBodyToPost in db/migrate/20080514090912_add_title_body_to_post.rb with this in the Up migration: diff --git a/railties/lib/generators/rails/model/USAGE b/railties/lib/generators/rails/model/USAGE index b056d5df8b..d97e9ac103 100644 --- a/railties/lib/generators/rails/model/USAGE +++ b/railties/lib/generators/rails/model/USAGE @@ -16,7 +16,7 @@ Description: created model. This allows you create Single Table Inheritance models. Examples: - `./script/generate model account` + `rails generate model account` For ActiveRecord and TestUnit it creates: @@ -25,6 +25,6 @@ Examples: Fixtures: test/fixtures/accounts.yml Migration: db/migrate/XXX_add_accounts.rb - `./script/generate model post title:string body:text published:boolean` + `rails generate model post title:string body:text published:boolean` Creates a Post model with a string title, text body, and published flag. diff --git a/railties/lib/generators/rails/observer/USAGE b/railties/lib/generators/rails/observer/USAGE index 9a20f55a89..d8f32a6a48 100644 --- a/railties/lib/generators/rails/observer/USAGE +++ b/railties/lib/generators/rails/observer/USAGE @@ -5,7 +5,7 @@ Description: This generator only invokes your ORM and test framework generators. Example: - `./script/generate observer Account` + `rails generate observer Account` For ActiveRecord and TestUnit it creates: Observer: app/models/account_observer.rb diff --git a/railties/lib/generators/rails/performance_test/USAGE b/railties/lib/generators/rails/performance_test/USAGE index ee82578cdb..9dc799559c 100644 --- a/railties/lib/generators/rails/performance_test/USAGE +++ b/railties/lib/generators/rails/performance_test/USAGE @@ -6,5 +6,5 @@ Description: TestUnit. Example: - `./script/generate performance_test GeneralStories` creates a GeneralStories + `rails generate performance_test GeneralStories` creates a GeneralStories performance test in test/performance/general_stories_test.rb diff --git a/railties/lib/generators/rails/plugin/USAGE b/railties/lib/generators/rails/plugin/USAGE index 8a17fa4dec..00a429c585 100644 --- a/railties/lib/generators/rails/plugin/USAGE +++ b/railties/lib/generators/rails/plugin/USAGE @@ -3,7 +3,7 @@ Description: CamelCased or under_scored, as an argument. Example: - `./script/generate plugin BrowserFilters` + `rails generate plugin BrowserFilters` creates a standard browser_filters plugin: vendor/plugins/browser_filters/README diff --git a/railties/lib/generators/rails/resource/USAGE b/railties/lib/generators/rails/resource/USAGE index 936619b0db..e359cd574f 100644 --- a/railties/lib/generators/rails/resource/USAGE +++ b/railties/lib/generators/rails/resource/USAGE @@ -18,6 +18,6 @@ Description: views or add any methods to the generated controller. Examples: - `./script/generate resource post` # no attributes - `./script/generate resource post title:string body:text published:boolean` - `./script/generate resource purchase order_id:integer amount:decimal` + `rails generate resource post` # no attributes + `rails generate resource post title:string body:text published:boolean` + `rails generate resource purchase order_id:integer amount:decimal` diff --git a/railties/lib/generators/rails/scaffold/USAGE b/railties/lib/generators/rails/scaffold/USAGE index 530ccdaf0a..be1d113ed8 100644 --- a/railties/lib/generators/rails/scaffold/USAGE +++ b/railties/lib/generators/rails/scaffold/USAGE @@ -21,9 +21,9 @@ Description: declaration in config/routes.rb. If you want to remove all the generated files, run - 'script/destroy scaffold ModelName'. + 'rails destroy scaffold ModelName'. Examples: - `./script/generate scaffold post` - `./script/generate scaffold post title:string body:text published:boolean` - `./script/generate scaffold purchase order_id:integer amount:decimal` + `rails generate scaffold post` + `rails generate scaffold post title:string body:text published:boolean` + `rails generate scaffold purchase order_id:integer amount:decimal` diff --git a/railties/lib/generators/rails/scaffold_controller/USAGE b/railties/lib/generators/rails/scaffold_controller/USAGE index d60a3c3680..673f69bc81 100644 --- a/railties/lib/generators/rails/scaffold_controller/USAGE +++ b/railties/lib/generators/rails/scaffold_controller/USAGE @@ -11,7 +11,7 @@ Description: template engine and test framework generators. Example: - `./script/generate scaffold_controller CreditCard` + `rails generate scaffold_controller CreditCard` Credit card controller with URLs like /credit_card/debit. Controller: app/controllers/credit_cards_controller.rb diff --git a/railties/lib/generators/rails/session_migration/USAGE b/railties/lib/generators/rails/session_migration/USAGE index e106f6ecc8..564d1ffd78 100644 --- a/railties/lib/generators/rails/session_migration/USAGE +++ b/railties/lib/generators/rails/session_migration/USAGE @@ -5,4 +5,4 @@ Description: Before invoking this generator, be sure that your ORM supports session stores. Example: - `./script/generate session_migration CreateSessionTable` + `rails generate session_migration CreateSessionTable` diff --git a/railties/lib/generators/rails/stylesheets/USAGE b/railties/lib/generators/rails/stylesheets/USAGE index d6a81e51d0..59e5495d0b 100644 --- a/railties/lib/generators/rails/stylesheets/USAGE +++ b/railties/lib/generators/rails/stylesheets/USAGE @@ -2,4 +2,4 @@ Description: Copies scaffold stylesheets to public/stylesheets/. Examples: - `./script/generate stylesheets` + `rails generate stylesheets` diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb index 1a0b4a8d73..82775b7e3b 100644 --- a/railties/lib/rails/all.rb +++ b/railties/lib/rails/all.rb @@ -1,11 +1,8 @@ require "rails" %w( - active_support - active_model active_record action_controller - action_view action_mailer active_resource rails/test_unit @@ -14,4 +11,4 @@ require "rails" require "#{framework}/railtie" rescue LoadError end -end
\ No newline at end of file +end diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb index 40198306f9..14eccd5a6a 100644 --- a/railties/lib/rails/backtrace_cleaner.rb +++ b/railties/lib/rails/backtrace_cleaner.rb @@ -12,7 +12,6 @@ module Rails add_filter { |line| line.sub('./', '/') } # for tests add_gem_filters - add_bundler_filters add_silencer { |line| !APP_DIRS.any? { |dir| line =~ /^#{dir}/ } } end @@ -27,16 +26,6 @@ module Rails } end end - - def add_bundler_filters - return unless defined? Bundler - add_filter { |line| - line.sub(%r{vendor/gems/[^/]+/[^/]+/gems/([^/]+)-([0-9.]+)/(.*)}, '\1 (\2) \3') - } - add_filter { |line| - line.sub(%r{vendor/gems/[^/]+/[^/]+/dirs/([^/]+)/(.*)}, '\1 \2') - } - end end # For installing the BacktraceCleaner in the test/unit diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index 4240038673..6972e25b29 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -34,8 +34,12 @@ when 'c', 'console' Rails::Console.start(Rails::Application) when 's', 'server' require 'rails/commands/server' - Dir.chdir(ROOT_PATH) - Rails::Server.start + # Initialize the server first, so environment options are set + server = Rails::Server.new + require APP_PATH + + Dir.chdir(Rails::Application.root) + server.start when 'db', 'dbconsole' require 'rails/commands/dbconsole' require APP_PATH @@ -57,11 +61,7 @@ when 'plugin' require 'rails/commands/plugin' when 'runner' require 'rails/commands/runner' - require ENV_PATH - -when '--version', '-v' - puts "Rails #{Rails::VERSION::STRING}" when '--help', '-h' puts HELP_TEXT when '--version', '-v' @@ -70,4 +70,4 @@ when '--version', '-v' else puts "Error: Command not recognized" puts HELP_TEXT -end
\ No newline at end of file +end diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb index a984eff6e2..50df6ba405 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console.rb @@ -19,7 +19,7 @@ module Rails opt.banner = "Usage: console [environment] [options]" opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |v| options[:sandbox] = v } opt.on("--debugger", 'Enable ruby-debugging for the console.') { |v| options[:debugger] = v } - opt.on('--irb') { |v| abort '--irb option is no longer supported. Invoke `/your/choice/of/ruby script/console` instead' } + opt.on('--irb', "DEPRECATED: Invoke `/your/choice/of/ruby script/rails console` instead") { |v| abort '--irb option is no longer supported. Invoke `/your/choice/of/ruby script/rails console` instead' } opt.parse!(ARGV) end diff --git a/railties/lib/rails/commands/destroy.rb b/railties/lib/rails/commands/destroy.rb index 92a06ebdd8..9023c61bf2 100644 --- a/railties/lib/rails/commands/destroy.rb +++ b/railties/lib/rails/commands/destroy.rb @@ -1,7 +1,7 @@ require 'rails/generators' -if ARGV.size == 0 - Rails::Generators.help +if [nil, "-h", "--help"].include?(ARGV.first) + Rails::Generators.help 'destroy' exit end diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb index 5e45d8ab46..7d05a30de8 100755 --- a/railties/lib/rails/commands/generate.rb +++ b/railties/lib/rails/commands/generate.rb @@ -1,7 +1,7 @@ require 'rails/generators' -if ARGV.size == 0 - Rails::Generators.help +if [nil, "-h", "--help"].include?(ARGV.first) + Rails::Generators.help 'generate' exit end diff --git a/railties/lib/rails/commands/performance/benchmarker.rb b/railties/lib/rails/commands/performance/benchmarker.rb index ad84d94dbf..0432261802 100644 --- a/railties/lib/rails/commands/performance/benchmarker.rb +++ b/railties/lib/rails/commands/performance/benchmarker.rb @@ -1,5 +1,5 @@ -if ARGV.empty? - puts "Usage: benchmarker [times] 'Person.expensive_way' 'Person.another_expensive_way' ..." +if [nil, "-h", "--help"].include?(ARGV.first) + puts "Usage: rails benchmarker [times] 'Person.expensive_way' 'Person.another_expensive_way' ..." exit 1 end diff --git a/railties/lib/rails/commands/performance/profiler.rb b/railties/lib/rails/commands/performance/profiler.rb index 50ae411166..6d9717b5cd 100644 --- a/railties/lib/rails/commands/performance/profiler.rb +++ b/railties/lib/rails/commands/performance/profiler.rb @@ -1,5 +1,5 @@ -if ARGV.empty? - $stderr.puts "Usage: profiler 'Person.expensive_method(10)' [times] [flat|graph|graph_html]" +if [nil, "-h", "--help"].include?(ARGV.first) + $stderr.puts "Usage: rails profiler 'Person.expensive_method(10)' [times] [flat|graph|graph_html]" exit(1) end diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb index af12df1425..8bcd92a33b 100644 --- a/railties/lib/rails/commands/plugin.rb +++ b/railties/lib/rails/commands/plugin.rb @@ -2,7 +2,7 @@ # # Installing plugins: # -# $ ./script/plugin install continuous_builder asset_timestamping +# $ rails plugin install continuous_builder asset_timestamping # # Specifying revisions: # @@ -319,13 +319,13 @@ module Commands o.separator "" o.separator "EXAMPLES" o.separator " Install a plugin:" - o.separator " #{@script_name} install continuous_builder\n" + o.separator " #{@script_name} plugin install continuous_builder\n" o.separator " Install a plugin from a subversion URL:" - o.separator " #{@script_name} install http://dev.rubyonrails.com/svn/rails/plugins/continuous_builder\n" + o.separator " #{@script_name} plugin install http://dev.rubyonrails.com/svn/rails/plugins/continuous_builder\n" o.separator " Install a plugin from a git URL:" - o.separator " #{@script_name} install git://github.com/SomeGuy/my_awesome_plugin.git\n" + o.separator " #{@script_name} plugin install git://github.com/SomeGuy/my_awesome_plugin.git\n" o.separator " Install a plugin and add a svn:externals entry to vendor/plugins" - o.separator " #{@script_name} install -x continuous_builder\n" + o.separator " #{@script_name} plugin install -x continuous_builder\n" end end @@ -381,7 +381,7 @@ module Commands "Exports the plugin, allowing you to check it into your local repository. Does not enable updates, or add an svn:externals entry.") { |v| @method = :export } o.on( "-q", "--quiet", "Suppresses the output from installation.", - "Ignored if -v is passed (./script/plugin -v install ...)") { |v| @options[:quiet] = true } + "Ignored if -v is passed (rails plugin -v install ...)") { |v| @options[:quiet] = true } o.on( "-r REVISION", "--revision REVISION", "Checks out the given revision from subversion or git.", "Ignored if subversion/git is not used.") { |v| @options[:revision] = v } diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index 606e04483f..1570b9ab0d 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -35,6 +35,8 @@ ARGV.delete(code_or_file) ENV["RAILS_ENV"] = options[:environment] +require ENV_PATH + begin if code_or_file.nil? $stderr.puts "Run '#{$0} -h' for help." diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index b21ae2a17b..c57660c0d1 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -6,20 +6,21 @@ module Rails class Server < ::Rack::Server class Options def parse!(args) - options = {} - args = args.dup + args, options = args.dup, {} + opt_parser = OptionParser.new do |opts| + opts.banner = "Usage: rails server [options]" opts.on("-p", "--port=port", Integer, - "Runs Rails on the specified port.", "Default: #{options[:Port]}") { |v| options[:Port] = v } + "Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v } opts.on("-b", "--binding=ip", String, - "Binds Rails to the specified ip.", "Default: #{options[:Host]}") { |v| options[:Host] = v } + "Binds Rails to the specified ip.", "Default: 0.0.0.0") { |v| options[:Host] = v } opts.on("-c", "--config=file", String, "Use custom rackup configuration file") { |v| options[:config] = v } opts.on("-d", "--daemon", "Make server run as a Daemon.") { options[:daemonize] = true } opts.on("-u", "--debugger", "Enable ruby-debugging for the server.") { options[:debugger] = true } opts.on("-e", "--environment=name", String, "Specifies the environment to run this server under (test/development/production).", - "Default: #{options[:environment]}") { |v| options[:environment] = v } + "Default: development") { |v| options[:environment] = v } opts.separator "" @@ -33,13 +34,20 @@ module Rails end end + def initialize(*) + super + set_environment + end + def opt_parser Options.new end - def start - ENV["RAILS_ENV"] = options[:environment] + def set_environment + ENV["RAILS_ENV"] ||= options[:environment] + end + def start puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" puts "=> Rails #{Rails.version} application starting in #{Rails.env} on http://#{options[:Host]}:#{options[:Port]}" puts "=> Call with -d to detach" unless options[:daemonize] @@ -48,7 +56,9 @@ module Rails super ensure - puts 'Exiting' unless options[:daemonize] + # The '-h' option calls exit before @options is set. + # If we call 'options' with it unset, we get double help banners. + puts 'Exiting' unless @options && options[:daemonize] end def middleware diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 80b72252f1..910b26f886 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -91,12 +91,13 @@ module Rails # Generators configuration which uses method missing to wrap it in a nifty DSL. # It also allows you to set generators fallbacks and aliases. class Generators #:nodoc: - attr_accessor :aliases, :options, :fallbacks, :colorize_logging + attr_accessor :aliases, :options, :templates, :fallbacks, :colorize_logging def initialize @aliases = Hash.new { |h,k| h[k] = {} } @options = Hash.new { |h,k| h[k] = {} } @fallbacks = {} + @templates = [] @colorize_logging = true end diff --git a/railties/lib/rails/deprecation.rb b/railties/lib/rails/deprecation.rb index f28da5a6b0..1eb6d804b6 100644 --- a/railties/lib/rails/deprecation.rb +++ b/railties/lib/rails/deprecation.rb @@ -2,6 +2,9 @@ require "active_support/string_inquirer" require "active_support/deprecation" RAILS_ROOT = (Class.new(ActiveSupport::Deprecation::DeprecationProxy) do + cattr_accessor :warned + self.warned = false + def target Rails.root end @@ -11,12 +14,17 @@ RAILS_ROOT = (Class.new(ActiveSupport::Deprecation::DeprecationProxy) do end def warn(callstack, called, args) - msg = "RAILS_ROOT is deprecated! Use Rails.root instead" - ActiveSupport::Deprecation.warn(msg, callstack) + unless warned + ActiveSupport::Deprecation.warn("RAILS_ROOT is deprecated! Use Rails.root instead", callstack) + self.warned = true + end end end).new RAILS_ENV = (Class.new(ActiveSupport::Deprecation::DeprecationProxy) do + cattr_accessor :warned + self.warned = false + def target Rails.env end @@ -26,12 +34,17 @@ RAILS_ENV = (Class.new(ActiveSupport::Deprecation::DeprecationProxy) do end def warn(callstack, called, args) - msg = "RAILS_ENV is deprecated! Use Rails.env instead" - ActiveSupport::Deprecation.warn(msg, callstack) + unless warned + ActiveSupport::Deprecation.warn("RAILS_ENV is deprecated! Use Rails.env instead", callstack) + self.warned = true + end end end).new RAILS_DEFAULT_LOGGER = (Class.new(ActiveSupport::Deprecation::DeprecationProxy) do + cattr_accessor :warned + self.warned = false + def target Rails.logger end @@ -41,7 +54,9 @@ RAILS_DEFAULT_LOGGER = (Class.new(ActiveSupport::Deprecation::DeprecationProxy) end def warn(callstack, called, args) - msg = "RAILS_DEFAULT_LOGGER is deprecated! Use Rails.logger instead" - ActiveSupport::Deprecation.warn(msg, callstack) + unless warned + ActiveSupport::Deprecation.warn("RAILS_DEFAULT_LOGGER is deprecated! Use Rails.logger instead", callstack) + self.warned = true + end end end).new diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 2b9c4add4f..226d1aaa88 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -99,7 +99,7 @@ module Rails unless abstract_railtie?(base) base.called_from = begin call_stack = caller.map { |p| p.split(':').first } - File.dirname(call_stack.detect { |p| p !~ %r[railties/lib/rails|rack/lib/rack] }) + File.dirname(call_stack.detect { |p| p !~ %r[railties[\w\-]*/lib/rails|rack[\w\-]*/lib/rack] }) end end @@ -185,6 +185,10 @@ module Rails app.metal_loader.paths.unshift(*paths.app.metals.to_a) end + initializer :add_generator_templates do |app| + config.generators.templates.unshift(*paths.lib.templates.to_a) + end + initializer :load_application_initializers do paths.config.initializers.to_a.sort.each do |initializer| load(initializer) diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 7d6de91430..93b882f874 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -21,6 +21,7 @@ module Rails paths.app.views "app/views" paths.lib "lib", :load_path => true paths.lib.tasks "lib/tasks", :glob => "**/*.rake" + paths.lib.templates "lib/templates" paths.config "config" paths.config.initializers "config/initializers", :glob => "**/*.rb" paths.config.locales "config/locales", :glob => "*.{rb,yml}" diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 5d95f2fa52..c01018aab2 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -66,6 +66,11 @@ module Rails aliases.deep_merge! config.aliases options.deep_merge! config.options fallbacks.merge! config.fallbacks + templates_path.concat config.templates + end + + def self.templates_path + @templates_path ||= [] end def self.aliases #:nodoc: @@ -161,7 +166,7 @@ module Rails end # Show help message with available generators. - def self.help + def self.help(command = 'generate') lookup! namespaces = subclasses.map{ |k| k.namespace } @@ -173,7 +178,7 @@ module Rails groups[base] << namespace end - puts "Usage: rails generate GENERATOR [args] [options]" + puts "Usage: rails #{command} GENERATOR [args] [options]" puts puts "General options:" puts " -h, [--help] # Print generators options and usage" diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index f95b15acce..d41da773c6 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -32,7 +32,7 @@ module Rails options[:git] = "-b #{options[:branch]} #{options[:git]}" if options[:branch] options[:svn] = "-r #{options[:revision]} #{options[:svn]}" if options[:revision] in_root do - run_ruby_script "script/plugin install #{options[:svn] || options[:git]}", :verbose => false + run_ruby_script "script/rails plugin install #{options[:svn] || options[:git]}", :verbose => false end else log "! no git or svn provided for #{name}. Skipping..." @@ -69,7 +69,7 @@ module Rails # otherwise use name (version). parts, message = [ name.inspect ], name if version ||= options.delete(:version) - parts << version + parts << version.inspect message << " (#{version})" end message = options[:git] if options[:git] @@ -81,7 +81,7 @@ module Rails end in_root do - append_file "Gemfile", "gem #{parts.join(", ")}", :verbose => false + append_file "Gemfile", "gem #{parts.join(", ")}\n", :verbose => false end end @@ -226,7 +226,7 @@ module Rails log :generate, what argument = args.map {|arg| arg.to_s }.flatten.join(" ") - in_root { run_ruby_script("script/generate #{what} #{argument}", :verbose => false) } + in_root { run_ruby_script("script/rails generate #{what} #{argument}", :verbose => false) } end # Runs the supplied rake task diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index dfa0d573c9..9624c35c0b 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -62,7 +62,7 @@ module Rails # # For example, if the user invoke the controller generator as: # - # ruby script/generate controller Account --test-framework=test_unit + # rails generate controller Account --test-framework=test_unit # # The controller generator will then try to invoke the following generators: # @@ -117,11 +117,11 @@ module Rails # All hooks come with switches for user interface. If the user don't want # to use any test framework, he can do: # - # ruby script/generate controller Account --skip-test-framework + # rails generate controller Account --skip-test-framework # # Or similarly: # - # ruby script/generate controller Account --no-test-framework + # rails generate controller Account --no-test-framework # # ==== Boolean hooks # @@ -133,7 +133,7 @@ module Rails # # Then, if you want, webrat to be invoked, just supply: # - # ruby script/generate controller Account --webrat + # rails generate controller Account --webrat # # The hooks lookup is similar as above: # @@ -213,8 +213,7 @@ module Rails if base.name && base.name !~ /Base$/ Rails::Generators.subclasses << base - if defined?(Rails.root) && Rails.root - path = File.expand_path(File.join(Rails.root, 'lib', 'templates')) + Rails::Generators.templates_path.each do |path| if base.name.include?('::') base.source_paths << File.join(path, base.base_name, base.generator_name) else diff --git a/railties/lib/rails/rack/debugger.rb b/railties/lib/rails/rack/debugger.rb index 0a7b405553..06e23db5f1 100644 --- a/railties/lib/rails/rack/debugger.rb +++ b/railties/lib/rails/rack/debugger.rb @@ -1,10 +1,12 @@ +require 'active_support/core_ext/kernel/requires' + module Rails module Rack class Debugger def initialize(app) @app = app - ARGV.clear # clear ARGV so that script/server options aren't passed to IRB + ARGV.clear # clear ARGV so that rails server options aren't passed to IRB require_library_or_gem 'ruby-debug' ::Debugger.start diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake index 7095046f21..f7cc6ff4be 100644 --- a/railties/lib/rails/tasks/documentation.rake +++ b/railties/lib/rails/tasks/documentation.rake @@ -14,7 +14,7 @@ namespace :doc do desc 'Generate documentation for the Rails framework. Specify path with PATH="/path/to/rails"' Rake::RDocTask.new("rails") { |rdoc| path = ENV['RAILS_PATH'] || 'vendor/gems/gems' - version = '-3.0.0.beta' unless ENV['RAILS_PATH'] + version = '-3.0.0.beta1' unless ENV['RAILS_PATH'] rdoc.rdoc_dir = 'doc/api' rdoc.template = "#{ENV['template']}.rb" if ENV['template'] rdoc.title = "Rails Framework Documentation" diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 350d0b3961..f9aa018cab 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -5,6 +5,12 @@ exit("Abort testing: Your Rails environment is not running in test mode!") unles require 'test/unit' require 'active_support/core_ext/kernel/requires' +# TODO: Figure out how to get the Rails::BacktraceFilter into minitest/unit +if defined?(Test::Unit::Util::BacktraceFilter) && ENV['BACKTRACE'].nil? + require 'rails/backtrace_cleaner' + Test::Unit::Util::BacktraceFilter.module_eval { include Rails::BacktraceFilterForTestUnit } +end + if defined?(ActiveRecord) class ActiveSupport::TestCase include ActiveRecord::TestFixtures diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb index 21b9dbc074..d99325a6d8 100644 --- a/railties/lib/rails/test_unit/railtie.rb +++ b/railties/lib/rails/test_unit/railtie.rb @@ -14,4 +14,4 @@ module Rails load "rails/test_unit/testing.rake" end end -end
\ No newline at end of file +end diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb index c327d4545a..d0c7cb45db 100644 --- a/railties/lib/rails/version.rb +++ b/railties/lib/rails/version.rb @@ -2,7 +2,7 @@ module Rails module VERSION #:nodoc: MAJOR = 3 MINOR = 0 - TINY = "0.beta" + TINY = "0.beta1" STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/railties/railties.gemspec b/railties/railties.gemspec index e91bd0e1b5..65d2dde89f 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -1,9 +1,10 @@ Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'railties' - s.version = '3.0.0.beta' + s.version = '3.0.0.beta1' s.summary = 'Controls boot-up, rake tasks and generators for the Rails framework.' s.description = 'Controls boot-up, rake tasks and generators for the Rails framework.' + s.required_ruby_version = '>= 1.8.7' s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' @@ -21,6 +22,6 @@ Gem::Specification.new do |s| s.add_dependency('rake', '>= 0.8.3') s.add_dependency('thor', '~> 0.13') - s.add_dependency('activesupport', '= 3.0.0.beta') - s.add_dependency('actionpack', '= 3.0.0.beta') + s.add_dependency('activesupport', '= 3.0.0.beta1') + s.add_dependency('actionpack', '= 3.0.0.beta1') end diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index 25fa100275..712c8bef36 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -52,30 +52,23 @@ module ApplicationTests end end - test "generators set rails fallbacks" do - with_config do |c| - c.generators.fallbacks[:shoulda] = :test_unit - expected = { :shoulda => :test_unit } - assert_equal expected, c.generators.fallbacks - end - end - - test "generators aliases, options and fallbacks on initialization" do + test "generators aliases, options, templates and fallbacks on initialization" do add_to_config <<-RUBY config.generators.rails :aliases => { :test_framework => "-w" } config.generators.orm :datamapper config.generators.test_framework :rspec config.generators.fallbacks[:shoulda] = :test_unit + config.generators.templates << "some/where" RUBY # Initialize the application require "#{app_path}/config/environment" require "rails/generators" - Rails::Generators.configure! assert_equal :rspec, Rails::Generators.options[:rails][:test_framework] assert_equal "-w", Rails::Generators.aliases[:rails][:test_framework] - assert_equal :test_unit, Rails::Generators.fallbacks[:shoulda] + assert_equal Hash[:shoulda => :test_unit], Rails::Generators.fallbacks + assert_equal ["#{app_path}/lib/templates", "some/where"], Rails::Generators.templates_path end test "generators no color on initialization" do diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb index 37175783d8..de316a6fd0 100644 --- a/railties/test/application/test_test.rb +++ b/railties/test/application/test_test.rb @@ -23,6 +23,23 @@ module ApplicationTests run_test 'unit/foo_test.rb' end + # Run just in Ruby < 1.9 + if defined?(Test::Unit::Util::BacktraceFilter) + test "adds backtrace cleaner" do + app_file 'test/unit/backtrace_test.rb', <<-RUBY + require 'test_helper' + + class FooTest < ActiveSupport::TestCase + def test_truth + assert Test::Unit::Util::BacktraceFilter.ancestors.include?(Rails::BacktraceFilterForTestUnit) + end + end + RUBY + + run_test 'unit/backtrace_test.rb' + end + end + test "integration test" do controller 'posts', <<-RUBY class PostsController < ActionController::Base diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index cb1fa96e0d..5929db6318 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -36,22 +36,22 @@ class ActionsTest < Rails::Generators::TestCase end def test_plugin_with_git_option_should_run_plugin_install - generator.expects(:run_ruby_script).once.with("script/plugin install #{@git_plugin_uri}", :verbose => false) + generator.expects(:run_ruby_script).once.with("script/rails plugin install #{@git_plugin_uri}", :verbose => false) action :plugin, 'restful-authentication', :git => @git_plugin_uri end def test_plugin_with_svn_option_should_run_plugin_install - generator.expects(:run_ruby_script).once.with("script/plugin install #{@svn_plugin_uri}", :verbose => false) + generator.expects(:run_ruby_script).once.with("script/rails plugin install #{@svn_plugin_uri}", :verbose => false) action :plugin, 'restful-authentication', :svn => @svn_plugin_uri end def test_plugin_with_git_option_and_branch_should_run_plugin_install - generator.expects(:run_ruby_script).once.with("script/plugin install -b stable #{@git_plugin_uri}", :verbose => false) + generator.expects(:run_ruby_script).once.with("script/rails plugin install -b stable #{@git_plugin_uri}", :verbose => false) action :plugin, 'restful-authentication', :git => @git_plugin_uri, :branch => 'stable' end def test_plugin_with_svn_option_and_revision_should_run_plugin_install - generator.expects(:run_ruby_script).once.with("script/plugin install -r 1234 #{@svn_plugin_uri}", :verbose => false) + generator.expects(:run_ruby_script).once.with("script/rails plugin install -r 1234 #{@svn_plugin_uri}", :verbose => false) action :plugin, 'restful-authentication', :svn => @svn_plugin_uri, :revision => 1234 end @@ -103,6 +103,24 @@ class ActionsTest < Rails::Generators::TestCase assert_file 'Gemfile', /gem "rspec", :only => \["development", "test"\]/ end + def test_gem_with_version_should_include_version_in_gemfile + run_generator + + action :gem, 'rspec', '>=2.0.0.a5' + + assert_file 'Gemfile', /gem "rspec", ">=2.0.0.a5"/ + end + + def test_gem_should_insert_on_separate_lines + run_generator + + action :gem, 'rspec' + action :gem, 'rspec-rails' + + assert_file 'Gemfile', /gem "rspec"$/ + assert_file 'Gemfile', /gem "rspec-rails"$/ + end + def test_environment_should_include_data_in_environment_initializer_block run_generator load_paths = 'config.load_paths += %w["#{Rails.root}/app/extras"]' @@ -155,7 +173,7 @@ class ActionsTest < Rails::Generators::TestCase end def test_generate_should_run_script_generate_with_argument_and_options - generator.expects(:run_ruby_script).once.with('script/generate model MyModel', :verbose => false) + generator.expects(:run_ruby_script).once.with('script/rails generate model MyModel', :verbose => false) action :generate, 'model', 'MyModel' end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 76579adb26..0a746b200f 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -74,13 +74,13 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_config_database_is_added_by_default run_generator assert_file "config/database.yml", /sqlite3/ - assert_file "Gemfile", /^gem "sqlite3-ruby", :require => "sqlite3"$/ + assert_file "Gemfile", /^gem\s+["']sqlite3-ruby["'],\s+:require\s+=>\s+["']sqlite3["']$/ end def test_config_another_database run_generator([destination_root, "-d", "mysql"]) assert_file "config/database.yml", /mysql/ - assert_file "Gemfile", /^gem "mysql"$/ + assert_file "Gemfile", /^gem\s+["']mysql["']$/ end def test_config_database_is_not_added_if_skip_activerecord_is_given @@ -90,7 +90,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_activerecord_is_removed_from_frameworks_if_skip_activerecord_is_given run_generator [destination_root, "--skip-activerecord"] - assert_file "config/boot.rb", /# require "active_record\/railtie"/ + assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ end def test_prototype_and_test_unit_are_added_by_default @@ -158,15 +158,13 @@ class AppGeneratorTest < Rails::Generators::TestCase generator([destination_root], :dev => true).expects(:run).with("bundle install") silence(:stdout){ generator.invoke } rails_path = File.expand_path('../../..', Rails.root) - dev_gem = %(path #{rails_path.inspect}, :glob => "{*/,}*.gemspec") - assert_file 'Gemfile', /^#{Regexp.escape(dev_gem)}$/ + assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/ end def test_edge_option generator([destination_root], :edge => true).expects(:run).with("bundle install") silence(:stdout){ generator.invoke } - edge_gem = %(gem "rails", :git => "git://github.com/rails/rails.git") - assert_file 'Gemfile', /^#{Regexp.escape(edge_gem)}$/ + assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("git://github.com/rails/rails.git")}["']$/ end protected diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index 3cd16a69f9..32be99b144 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -6,6 +6,7 @@ module Rails end end Rails.application.config.root = Rails.root +Rails.application.config.generators.templates = [File.join(Rails.root, "lib", "templates")] require 'rails/generators' require 'rails/generators/test_case' diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index dcadf3f485..364dbd8e55 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -154,7 +154,7 @@ module TestHelpers def script(script) Dir.chdir(app_path) do - `#{Gem.ruby} #{app_path}/script/#{script}` + `#{Gem.ruby} #{app_path}/script/rails #{script}` end end |