Merge remote branch 'mainstream/master'

Conflicts:
	activemodel/lib/active_model/state_machine.rb
This commit is contained in:
Pratik Naik 2010-01-31 14:32:26 +00:00
commit ed60021f39
93 changed files with 963 additions and 3932 deletions

View File

@ -24,9 +24,17 @@
actionpack_path = File.expand_path('../../../actionpack/lib', __FILE__)
$:.unshift(actionpack_path) if File.directory?(actionpack_path) && !$:.include?(actionpack_path)
require 'action_controller'
require 'abstract_controller'
require 'action_view'
# Common ActiveSupport usage in ActionMailer
require 'active_support/core_ext/class'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/array/uniq_by'
require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/string/inflections'
module ActionMailer
extend ::ActiveSupport::Autoload

View File

@ -1,8 +1,3 @@
require 'active_support/core_ext/class'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/array/uniq_by'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/string/inflections'
require 'mail'
require 'action_mailer/tmail_compat'
require 'action_mailer/collector'
@ -263,8 +258,8 @@ module ActionMailer #:nodoc:
include AbstractController::LocalizedCache
include AbstractController::Layouts
include AbstractController::Helpers
include AbstractController::UrlFor
include AbstractController::Translation
include AbstractController::Compatibility
helper ActionMailer::MailHelper

View File

@ -17,5 +17,9 @@ module ActionMailer
ActionMailer::Base.send "#{k}=", v
end
end
initializer "action_mailer.url_for" do |app|
ActionMailer::Base.send(:include, ActionController::UrlFor) if defined?(ActionController)
end
end
end

View File

@ -17,7 +17,13 @@ module Mail
old_transfer_encoding
end
end
def transfer_encoding=(value)
ActiveSupport::Deprecation.warn('Message#transfer_encoding= is deprecated, please call ' <<
'Message#content_transfer_encoding= with the same arguments', caller[0,2])
self.content_transfer_encoding = value
end
def original_filename
ActiveSupport::Deprecation.warn('Message#original_filename is deprecated, ' <<
'please call Message#filename', caller[0,2])

View File

@ -78,6 +78,23 @@ class BaseTest < ActiveSupport::TestCase
format.html{ render "welcome" } if include_html
end
end
def different_template(template_name='')
mail do |format|
format.text { render :template => template_name }
format.html { render :template => template_name }
end
end
def different_layout(layout_name='')
mail do |format|
format.text {
render :layout => layout_name
}
format.html { render :layout => layout_name }
end
end
end
test "method call to mail does not raise error" do
@ -398,6 +415,21 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("7bit", email.parts[1].content_transfer_encoding)
end
test "explicit multipart should be multipart" do
mail = BaseMailer.explicit_multipart
assert_not_nil(mail.content_type_parameters[:boundary])
end
test "should set a content type if only has an html part" do
mail = BaseMailer.html_only
assert_equal('text/html', mail.mime_type)
end
test "should set a content type if only has an plain text part" do
mail = BaseMailer.plain_text_only
assert_equal('text/plain', mail.mime_type)
end
test "explicit multipart with one part is rendered as body" do
email = BaseMailer.custom_block
assert_equal(0, email.parts.size)
@ -439,20 +471,18 @@ class BaseTest < ActiveSupport::TestCase
BaseMailer.expects(:welcome).returns(mail)
BaseMailer.welcome.deliver
end
test "explicit multipart should be multipart" do
mail = BaseMailer.explicit_multipart
assert_not_nil(mail.content_type_parameters[:boundary])
# Rendering
test "that you can specify a different template" do
mail = BaseMailer.different_template('explicit_multipart_templates')
assert_equal("HTML Explicit Multipart Templates", mail.html_part.body.decoded)
assert_equal("TEXT Explicit Multipart Templates", mail.text_part.body.decoded)
end
test "should set a content type if only has an html part" do
mail = BaseMailer.html_only
assert_equal('text/html', mail.mime_type)
end
test "should set a content type if only has an plain text part" do
mail = BaseMailer.plain_text_only
assert_equal('text/plain', mail.mime_type)
test "that you can specify a different layout" do
mail = BaseMailer.different_layout('different_layout')
assert_equal("HTML -- HTML", mail.html_part.body.decoded)
assert_equal("PLAIN -- PLAIN", mail.text_part.body.decoded)
end
protected

View File

@ -0,0 +1 @@
HTML

View File

@ -0,0 +1 @@
PLAIN

View File

@ -0,0 +1 @@
body_text

View File

@ -0,0 +1 @@
HTML -- <%= yield %>

View File

@ -0,0 +1 @@
PLAIN -- <%= yield %>

View File

@ -21,5 +21,15 @@ class TmailCompatTest < ActiveSupport::TestCase
end
assert_equal mail.content_transfer_encoding, "base64"
end
def test_transfer_encoding_setter_raises_deprecation_warning
mail = Mail.new
assert_deprecated do
assert_nothing_raised do
mail.transfer_encoding = "base64"
end
end
assert_equal mail.content_transfer_encoding, "base64"
end
end

View File

@ -1,8 +1,13 @@
require 'abstract_unit'
require 'action_controller'
class WelcomeController < ActionController::Base
end
class ActionMailer::Base
include ActionController::UrlFor
end
class TestMailer < ActionMailer::Base
default_url_options[:host] = 'www.basecamphq.com'

View File

@ -37,15 +37,15 @@ A short rundown of the major features:
def show
@customer = find_customer
end
def update
@customer = find_customer
@customer.attributes = params[:customer]
@customer.save ?
@customer.save ?
redirect_to(:action => "show") :
render(:action => "edit")
end
private
def find_customer() Customer.find(params[:id]) end
end
@ -64,7 +64,7 @@ A short rundown of the major features:
<% unless @person.is_client? %>
Not for clients to see...
<% end %>
{Learn more}[link:classes/ActionView.html]
@ -99,24 +99,24 @@ A short rundown of the major features:
before_filter :authenticate, :cache, :audit
after_filter { |c| c.response.body = Gzip::compress(c.response.body) }
after_filter LocalizeFilter
def index
# Before this action is run, the user will be authenticated, the cache
# will be examined to see if a valid copy of the results already
# exists, and the action will be logged for auditing.
# After this action has run, the output will first be localized then
# After this action has run, the output will first be localized then
# compressed to minimize bandwidth usage
end
private
def authenticate
# Implement the filter with full access to both request and response
end
end
{Learn more}[link:classes/ActionController/Filters/ClassMethods.html]
* Helpers for forms, dates, action links, and text
@ -124,26 +124,26 @@ A short rundown of the major features:
<%= html_date_select(Date.today) %>
<%= link_to "New post", :controller => "post", :action => "new" %>
<%= truncate(post.title, :length => 25) %>
{Learn more}[link:classes/ActionView/Helpers.html]
* Layout sharing for template reuse (think simple version of Struts
* Layout sharing for template reuse (think simple version of Struts
Tiles[http://jakarta.apache.org/struts/userGuide/dev_tiles.html])
class WeblogController < ActionController::Base
layout "weblog_layout"
def hello_world
end
end
Layout file (called weblog_layout):
<html><body><%= yield %></body></html>
Template for hello_world action:
<h1>Hello world</h1>
Result of running hello_world action:
<html><body><h1>Hello world</h1></body></html>
@ -156,9 +156,9 @@ A short rundown of the major features:
Accessing /clients/37signals/basecamp/project/dash calls ProjectController#dash with
{ "client_name" => "37signals", "project_name" => "basecamp" } in params[:params]
From that URL, you can rewrite the redirect in a number of ways:
redirect_to(:action => "edit") =>
/clients/37signals/basecamp/project/dash
@ -168,15 +168,6 @@ A short rundown of the major features:
{Learn more}[link:classes/ActionController/Base.html]
* Javascript and Ajax integration
link_to_function "Greeting", "alert('Hello world!')"
link_to_remote "Delete this post", :update => "posts",
:url => { :action => "destroy", :id => post.id }
{Learn more}[link:classes/ActionView/Helpers/JavaScriptHelper.html]
* Easy testing of both controller and rendered template through ActionController::TestCase
class LoginControllerTest < ActionController::TestCase
@ -218,18 +209,18 @@ A short rundown of the major features:
class WeblogController < ActionController::Base
caches_page :show
caches_action :account
def show
# the output of the method will be cached as
# the output of the method will be cached as
# ActionController::Base.page_cache_directory + "/weblog/show/n.html"
# and the web server will pick it up without even hitting Rails
end
def account
# the output of the method will be cached in the fragment store
# but Rails is hit to retrieve it, so filters are run
end
def update
List.update(params[:list][:id], params[:list])
expire_page :action => "show", :id => params[:list][:id]
@ -256,26 +247,26 @@ A short rundown of the major features:
class AccountController < ActionController::Base
scaffold :account
end
The AccountController now has the full CRUD range of actions and default
templates: list, show, destroy, new, create, edit, update
{Learn more}[link:classes/ActionController/Scaffolding/ClassMethods.html]
* Form building for Active Record model objects
The post object has a title (varchar), content (text), and
The post object has a title (varchar), content (text), and
written_on (date)
<%= form "post" %>
...will generate something like (the selects will have more options, of
course):
<form action="create" method="POST">
<p>
<b>Title:</b><br/>
<b>Title:</b><br/>
<input type="text" name="post[title]" value="<%= @post.title %>" />
</p>
<p>
@ -293,7 +284,7 @@ A short rundown of the major features:
</form>
This form generates a params[:post] array that can be used directly in a save action:
class WeblogController < ActionController::Base
def create
post = Post.create(params[:post])
@ -318,19 +309,19 @@ methods:
class WeblogController < ActionController::Base
layout "weblog/layout"
def index
@posts = Post.find(:all)
end
def show
@post = Post.find(params[:id])
end
def new
@post = Post.new
end
def create
@post = Post.create(params[:post])
redirect_to :action => "show", :id => @post.id
@ -364,7 +355,7 @@ And the templates look like this:
weblog/new.html.erb:
<%= form "post" %>
This simple setup will list all the posts in the system on the index page,
which is called by accessing /weblog/. It uses the form builder for the Active
Record model to make the new screen, which in turn hands everything over to
@ -379,7 +370,7 @@ The latest version of Action Pack can be found at
* http://rubyforge.org/project/showfiles.php?group_id=249
Documentation can be found at
Documentation can be found at
* http://api.rubyonrails.com

View File

@ -2,6 +2,7 @@ activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
require 'active_support/ruby/shim'
require 'active_support/dependencies/autoload'
require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/module/delegation'
@ -11,11 +12,11 @@ module AbstractController
autoload :Base
autoload :Callbacks
autoload :Collector
autoload :Compatibility
autoload :Helpers
autoload :Layouts
autoload :LocalizedCache
autoload :Logger
autoload :Rendering
autoload :Translation
autoload :UrlFor
end

View File

@ -0,0 +1,18 @@
module AbstractController
module Compatibility
extend ActiveSupport::Concern
def _find_layout(name, details)
details[:prefix] = nil if name =~ /\blayouts/
super
end
# Move this into a "don't run in production" module
def _default_layout(details, require_layout = false)
super
rescue ActionView::MissingTemplate
_find_layout(_layout({}), {})
nil
end
end
end

View File

@ -25,7 +25,7 @@ module AbstractController
def inherited(klass)
helpers = _helpers
klass._helpers = Module.new { include helpers }
klass.class_eval { default_helper_module! unless name.blank? }
super
end
@ -146,6 +146,16 @@ module AbstractController
end
end
end
def default_helper_module!
module_name = name.sub(/Controller$/, '')
module_path = module_name.underscore
helper module_path
rescue MissingSourceFile => e
raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
raise e unless e.missing_name? "#{module_name}Helper"
end
end
end
end

View File

@ -157,13 +157,23 @@ module AbstractController
options[:_template_name] = options[:file]
end
name = (options[:_template_name] || action_name).to_s
name = (options[:_template_name] || options[:action] || action_name).to_s
options[:_prefix] ||= _prefix if (options.keys & [:partial, :file, :template]).empty?
details = _normalize_details(options)
options[:_template] ||= with_template_cache(name) do
find_template(name, { :formats => formats }, options)
find_template(name, details, options)
end
end
def _normalize_details(options)
details = { :formats => formats }
details[:formats] = Array(options[:format]) if options[:format]
details[:locale] = Array(options[:locale]) if options[:locale]
details
end
def find_template(name, details, options)
view_paths.find(name, details, options[:_prefix], options[:_partial])
end

View File

@ -1,156 +0,0 @@
module AbstractController
# In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse
# is also possible: an URL can be generated from one of your routing definitions.
# URL generation functionality is centralized in this module.
#
# See AbstractController::Routing and AbstractController::Resources for general
# information about routing and routes.rb.
#
# <b>Tip:</b> If you need to generate URLs from your models or some other place,
# then AbstractController::UrlFor is what you're looking for. Read on for
# an introduction.
#
# == URL generation from parameters
#
# As you may know, some functions - such as AbstractController::Base#url_for
# and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
# of parameters. For example, you've probably had the chance to write code
# like this in one of your views:
#
# <%= link_to('Click here', :controller => 'users',
# :action => 'new', :message => 'Welcome!') %>
#
# #=> Generates a link to: /users/new?message=Welcome%21
#
# link_to, and all other functions that require URL generation functionality,
# actually use AbstractController::UrlFor under the hood. And in particular,
# they use the AbstractController::UrlFor#url_for method. One can generate
# the same path as the above example by using the following code:
#
# include UrlFor
# url_for(:controller => 'users',
# :action => 'new',
# :message => 'Welcome!',
# :only_path => true)
# # => "/users/new?message=Welcome%21"
#
# Notice the <tt>:only_path => true</tt> part. This is because UrlFor has no
# information about the website hostname that your Rails app is serving. So if you
# want to include the hostname as well, then you must also pass the <tt>:host</tt>
# argument:
#
# include UrlFor
# url_for(:controller => 'users',
# :action => 'new',
# :message => 'Welcome!',
# :host => 'www.example.com') # Changed this.
# # => "http://www.example.com/users/new?message=Welcome%21"
#
# By default, all controllers and views have access to a special version of url_for,
# that already knows what the current hostname is. So if you use url_for in your
# controllers or your views, then you don't need to explicitly pass the <tt>:host</tt>
# argument.
#
# For convenience reasons, mailers provide a shortcut for AbstractController::UrlFor#url_for.
# So within mailers, you only have to type 'url_for' instead of 'AbstractController::UrlFor#url_for'
# in full. However, mailers don't have hostname information, and what's why you'll still
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
#
#
# == URL generation for named routes
#
# UrlFor also allows one to access methods that have been auto-generated from
# named routes. For example, suppose that you have a 'users' resource in your
# <b>routes.rb</b>:
#
# map.resources :users
#
# This generates, among other things, the method <tt>users_path</tt>. By default,
# this method is accessible from your controllers, views and mailers. If you need
# to access this auto-generated method from other places (such as a model), then
# you can do that by including AbstractController::UrlFor in your class:
#
# class User < ActiveRecord::Base
# include AbstractController::UrlFor
#
# def base_uri
# user_path(self)
# end
# end
#
# User.find(1).base_uri # => "/users/1"
#
module UrlFor
extend ActiveSupport::Concern
included do
ActionController::Routing::Routes.install_helpers(self)
extlib_inheritable_accessor :default_url_options,
:instance_writer => false, :instance_reader => false
self.default_url_options ||= {}
end
# Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
# the form of a hash, just like the one you would use for url_for directly. Example:
#
# def default_url_options(options)
# { :project => @project.active? ? @project.url_name : "unknown" }
# end
#
# As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
# urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
# by this method.
def default_url_options(options = nil)
self.class.default_url_options
end
def rewrite_options(options) #:nodoc:
if options.delete(:use_defaults) != false && (defaults = default_url_options(options))
defaults.merge(options)
else
options
end
end
# Generate a url based on the options provided, default_url_options and the
# routes defined in routes.rb. The following options are supported:
#
# * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
# * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
# * <tt>:host</tt> - Specifies the host the link should be targeted at.
# If <tt>:only_path</tt> is false, this option must be
# provided either explicitly, or via +default_url_options+.
# * <tt>:port</tt> - Optionally specify the port to connect to.
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the
# +relative_url_root+ set in AbstractController::Base.relative_url_root.
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
#
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
# +url_for+ is forwarded to the Routes module.
#
# Examples:
#
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
# url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/'
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
def url_for(options = {})
options ||= {}
case options
when String
options
when Hash
_url_rewriter.rewrite(rewrite_options(options))
else
polymorphic_url(options)
end
end
protected
def _url_rewriter
ActionController::UrlRewriter
end
end
end

View File

@ -1,6 +1,5 @@
activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
require 'active_support/ruby/shim'
require 'abstract_controller'
require 'action_dispatch'
module ActionController
extend ActiveSupport::Autoload
@ -41,6 +40,7 @@ module ActionController
autoload :Integration, 'action_controller/deprecated/integration_test'
autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
autoload :UrlWriter, 'action_controller/deprecated'
autoload :Routing, 'action_controller/deprecated'
autoload :TestCase, 'action_controller/test_case'
@ -66,13 +66,11 @@ module ActionController
end
# All of these simply register additional autoloads
require 'abstract_controller'
require 'action_dispatch'
require 'action_view'
require 'action_controller/vendor/html-scanner'
# Common ActiveSupport usage in ActionController
require "active_support/concern"
require 'active_support/concern'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/load_error'
require 'active_support/core_ext/module/attr_internal'

View File

@ -81,5 +81,13 @@ module ActionController
filter << block if block
filter
end
protected
# Overwrite url rewriter to use request.
def _url_rewriter
return ActionController::UrlRewriter unless request
@_url_rewriter ||= ActionController::UrlRewriter.new(request, params)
end
end
end

View File

@ -2,4 +2,4 @@ ActionController::AbstractRequest = ActionController::Request = ActionDispatch::
ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response
ActionController::Routing = ActionDispatch::Routing
ActionController::Routing::Routes = ActionDispatch::Routing::RouteSet.new
ActionController::UrlWriter = AbstractController::UrlFor
ActionController::UrlWriter = ActionController::UrlFor

View File

@ -2,6 +2,8 @@ module ActionController
module Compatibility
extend ActiveSupport::Concern
include AbstractController::Compatibility
class ::ActionController::ActionControllerError < StandardError #:nodoc:
end
@ -103,19 +105,6 @@ module ActionController
super || (respond_to?(:method_missing) && "_handle_method_missing")
end
def _find_layout(name, details)
details[:prefix] = nil if name =~ /\blayouts/
super
end
# Move this into a "don't run in production" module
def _default_layout(details, require_layout = false)
super
rescue ActionView::MissingTemplate
_find_layout(_layout({}), {})
nil
end
def performed?
response_body
end

View File

@ -56,18 +56,15 @@ module ActionController
module ClassMethods
def helpers_dir
ActiveSupport::Deprecation.warn "helpers_dir is deprecated, use helpers_path instead"
self.helpers_path
end
def helpers_dir=(value)
ActiveSupport::Deprecation.warn "helpers_dir= is deprecated, use helpers_path= instead"
self.helpers_path = Array(value)
end
def inherited(klass)
klass.class_eval { default_helper_module! unless name.blank? }
super
end
# Declares helper accessors for controller attributes. For example, the
# following adds new +name+ and <tt>name=</tt> instance methods to a
# controller and makes them available to the view:
@ -101,16 +98,6 @@ module ActionController
super(args)
end
def default_helper_module!
module_name = name.sub(/Controller$/, '')
module_path = module_name.underscore
helper module_path
rescue MissingSourceFile => e
raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
raise e unless e.missing_name? "#{module_name}Helper"
end
# Extract helper names from files in app/helpers/**/*_helper.rb
def all_application_helpers
helpers = []

View File

@ -58,11 +58,6 @@ module ActionController
options.merge! :partial => action
end
if (options.keys & [:partial, :file, :template, :text, :inline]).empty?
options[:_template_name] ||= options[:action]
options[:_prefix] = _prefix
end
if options[:status]
options[:status] = Rack::Utils.status_code(options[:status])
end

View File

@ -1,15 +1,156 @@
module ActionController
# In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse
# is also possible: an URL can be generated from one of your routing definitions.
# URL generation functionality is centralized in this module.
#
# See ActionController::Routing and ActionController::Resources for general
# information about routing and routes.rb.
#
# <b>Tip:</b> If you need to generate URLs from your models or some other place,
# then ActionController::UrlFor is what you're looking for. Read on for
# an introduction.
#
# == URL generation from parameters
#
# As you may know, some functions - such as ActionController::Base#url_for
# and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
# of parameters. For example, you've probably had the chance to write code
# like this in one of your views:
#
# <%= link_to('Click here', :controller => 'users',
# :action => 'new', :message => 'Welcome!') %>
#
# #=> Generates a link to: /users/new?message=Welcome%21
#
# link_to, and all other functions that require URL generation functionality,
# actually use ActionController::UrlFor under the hood. And in particular,
# they use the ActionController::UrlFor#url_for method. One can generate
# the same path as the above example by using the following code:
#
# include UrlFor
# url_for(:controller => 'users',
# :action => 'new',
# :message => 'Welcome!',
# :only_path => true)
# # => "/users/new?message=Welcome%21"
#
# Notice the <tt>:only_path => true</tt> part. This is because UrlFor has no
# information about the website hostname that your Rails app is serving. So if you
# want to include the hostname as well, then you must also pass the <tt>:host</tt>
# argument:
#
# include UrlFor
# url_for(:controller => 'users',
# :action => 'new',
# :message => 'Welcome!',
# :host => 'www.example.com') # Changed this.
# # => "http://www.example.com/users/new?message=Welcome%21"
#
# By default, all controllers and views have access to a special version of url_for,
# that already knows what the current hostname is. So if you use url_for in your
# controllers or your views, then you don't need to explicitly pass the <tt>:host</tt>
# argument.
#
# For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for.
# So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlFor#url_for'
# in full. However, mailers don't have hostname information, and what's why you'll still
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
#
#
# == URL generation for named routes
#
# UrlFor also allows one to access methods that have been auto-generated from
# named routes. For example, suppose that you have a 'users' resource in your
# <b>routes.rb</b>:
#
# map.resources :users
#
# This generates, among other things, the method <tt>users_path</tt>. By default,
# this method is accessible from your controllers, views and mailers. If you need
# to access this auto-generated method from other places (such as a model), then
# you can do that by including ActionController::UrlFor in your class:
#
# class User < ActiveRecord::Base
# include ActionController::UrlFor
#
# def base_uri
# user_path(self)
# end
# end
#
# User.find(1).base_uri # => "/users/1"
#
module UrlFor
extend ActiveSupport::Concern
include AbstractController::UrlFor
include ActionController::RackDelegation
included do
ActionController::Routing::Routes.install_helpers(self)
extlib_inheritable_accessor :default_url_options,
:instance_writer => false, :instance_reader => false
self.default_url_options ||= {}
end
# Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
# the form of a hash, just like the one you would use for url_for directly. Example:
#
# def default_url_options(options)
# { :project => @project.active? ? @project.url_name : "unknown" }
# end
#
# As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
# urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
# by this method.
def default_url_options(options = nil)
self.class.default_url_options
end
def rewrite_options(options) #:nodoc:
if options.delete(:use_defaults) != false && (defaults = default_url_options(options))
defaults.merge(options)
else
options
end
end
# Generate a url based on the options provided, default_url_options and the
# routes defined in routes.rb. The following options are supported:
#
# * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
# * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
# * <tt>:host</tt> - Specifies the host the link should be targeted at.
# If <tt>:only_path</tt> is false, this option must be
# provided either explicitly, or via +default_url_options+.
# * <tt>:port</tt> - Optionally specify the port to connect to.
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the
# +relative_url_root+ set in ActionController::Base.relative_url_root.
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
#
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
# +url_for+ is forwarded to the Routes module.
#
# Examples:
#
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
# url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/'
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
def url_for(options = {})
options ||= {}
case options
when String
options
when Hash
_url_rewriter.rewrite(rewrite_options(options))
else
polymorphic_url(options)
end
end
protected
def _url_rewriter
return ActionController::UrlRewriter unless request
@_url_rewriter ||= ActionController::UrlRewriter.new(request, params)
ActionController::UrlRewriter
end
end
end

View File

@ -23,6 +23,7 @@
activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
require 'active_support'
require 'active_support/dependencies/autoload'

View File

@ -36,6 +36,11 @@ module ActionDispatch
@template.layout
end
def redirected_to
::ActiveSupport::Deprecation.warn("response.redirected_to is deprecated. Use response.redirect_url instead", caller)
redirect_url
end
def redirect_url_match?(pattern)
::ActiveSupport::Deprecation.warn("response.redirect_url_match? is deprecated. Use assert_match(/foo/, response.redirect_url) instead", caller)
return false if redirect_url.nil?

View File

@ -6,15 +6,20 @@ module ActionView #:nodoc:
end
class MissingTemplate < ActionViewError #:nodoc:
attr_reader :path, :action_name
attr_reader :path
def initialize(paths, path, template_format = nil)
def initialize(paths, path, details, partial)
@path = path
@action_name = path.split("/").last.split(".")[0...-1].join(".")
full_template_path = path.include?('.') ? path : "#{path}.erb"
display_paths = paths.compact.join(":")
template_type = (path =~ /layouts/i) ? 'layout' : 'template'
super("Missing #{template_type} #{full_template_path} in view path #{display_paths}")
template_type = if partial
"partial"
elsif path =~ /layouts/i
'layout'
else
'template'
end
super("Missing #{template_type} #{path} with #{details.inspect} in view path #{display_paths}")
end
end

View File

@ -3,7 +3,6 @@ require 'active_support/benchmarkable'
module ActionView #:nodoc:
module Helpers #:nodoc:
autoload :ActiveModelHelper, 'action_view/helpers/active_model_helper'
autoload :AjaxHelperCompat, 'action_view/helpers/ajax_helper'
autoload :AssetTagHelper, 'action_view/helpers/asset_tag_helper'
autoload :AtomFeedHelper, 'action_view/helpers/atom_feed_helper'
autoload :CacheHelper, 'action_view/helpers/cache_helper'
@ -15,11 +14,12 @@ module ActionView #:nodoc:
autoload :FormTagHelper, 'action_view/helpers/form_tag_helper'
autoload :JavaScriptHelper, 'action_view/helpers/javascript_helper'
autoload :NumberHelper, 'action_view/helpers/number_helper'
autoload :AjaxHelper, 'action_view/helpers/ajax_helper'
autoload :PrototypeHelper, 'action_view/helpers/prototype_helper'
autoload :RawOutputHelper, 'action_view/helpers/raw_output_helper'
autoload :RecordIdentificationHelper, 'action_view/helpers/record_identification_helper'
autoload :RecordTagHelper, 'action_view/helpers/record_tag_helper'
autoload :SanitizeHelper, 'action_view/helpers/sanitize_helper'
autoload :ScriptaculousHelper, 'action_view/helpers/scriptaculous_helper'
autoload :TagHelper, 'action_view/helpers/tag_helper'
autoload :TextHelper, 'action_view/helpers/text_helper'
autoload :TranslationHelper, 'action_view/helpers/translation_helper'
@ -47,11 +47,12 @@ module ActionView #:nodoc:
include FormTagHelper
include JavaScriptHelper
include NumberHelper
include AjaxHelperCompat
include PrototypeHelper
include RawOutputHelper
include RecordIdentificationHelper
include RecordTagHelper
include SanitizeHelper
include ScriptaculousHelper
include TagHelper
include TextHelper
include TranslationHelper

View File

@ -1,713 +0,0 @@
module ActionView
module Helpers
module AjaxHelper
# Included for backwards compatibility / RJS functionality
# Rails classes should not be aware of individual JS frameworks
include PrototypeHelper
# Returns a form that will allow the unobtrusive JavaScript drivers to submit the
# form dynamically. The default driver behaviour is an XMLHttpRequest in the background
# instead of the regular POST arrangement. Even though it's using JavaScript to serialize
# the form elements, the form submission will work just like a regular submission as
# viewed by the receiving side (all elements available in <tt>params</tt>). The options
# for specifying the target with <tt>:url</tt> anddefining callbacks is the same as +link_to_remote+.
#
# === Resource
#
# Example:
#
# # Generates:
# # <form action='/authors'
# # data-remote='true'
# # class='new_author'
# # id='create-author'
# # method='post'> ... </form>
# #
# <% remote_form_for(@record, {:html => { :id => 'create-author' }}) do |f| %>
# ...
# <% end %>
#
# This will expand to be the same as:
#
# <% remote_form_for :post, @post, :url => post_path(@post),
# :html => { :method => :put,
# :class => "edit_post",
# :id => "edit_post_45" } do |f| %>
# ...
# <% end %>
#
# === Nested Resource
#
# Example:
# # Generates:
# # <form action='/authors/1/articles'
# # data-remote="true"
# # class='new_article'
# # method='post'
# # id='new_article'></form>
# #
# <% remote_form_for([@author, @article]) do |f| %>
# ...
# <% end %>
#
# This will expand to be the same as:
#
# <% remote_form_for :article, @article, :url => author_article_path(@author, @article),
# :html => { :method => :put,
# :class => "new_article",
# :id => "new_comment" } do |f| %>
# ...
# <% end %>
#
# If you don't need to attach a form to a resource, then check out form_remote_tag.
#
# See FormHelper#form_for for additional semantics.
def remote_form_for(record_or_name_or_array, *args, &proc)
options = args.extract_options!
if confirm = options.delete(:confirm)
add_confirm_to_attributes!(options, confirm)
end
object_name = extract_object_name_for_form!(args, options, record_or_name_or_array)
concat(form_remote_tag(options))
fields_for(object_name, *(args << options), &proc)
concat('</form>'.html_safe!)
end
alias_method :form_remote_for, :remote_form_for
# Returns a form tag that will allow the unobtrusive JavaScript drivers to submit the
# form dynamically. The default JavaScript driver behaviour is an XMLHttpRequest
# in the background instead of the regular POST arrangement. Even though it's using
# JavaScript to serialize the form elements, the form submission will work just like
# a regular submission as viewed by the receiving side (all elements available in
# <tt>params</tt>). The options for specifying the target with <tt>:url</tt> and
# defining callbacks is the same as +link_to_remote+.
#
# A "fall-through" target for browsers that doesn't do JavaScript can be
# specified with the <tt>:action</tt>/<tt>:method</tt> options on <tt>:html</tt>.
#
# Example:
#
# # Generates:
# # <form action="http://www.example.com/fast"
# # method="post"
# # data-remote="true"
# # data-update-success="glass_of_beer"></form>
# #
# form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }) {}
#
# The Hash passed to the <tt>:html</tt> key is equivalent to the options (2nd)
# argument in the FormTagHelper.form_tag method.
#
# By default the fall-through action is the same as the one specified in
# the <tt>:url</tt> (and the default method is <tt>:post</tt>).
#
# form_remote_tag also takes a block, like form_tag:
# # Generates:
# # <form action='/posts'
# # method='post'
# # data-remote='true'>
# # <input name="commit" type="submit" value="Save" />
# # </form>
# #
# <% form_remote_tag :url => '/posts' do -%>
# <%= submit_tag 'Save' %>
# <% end -%>
#
# # Generates:
# # <form action="http://www.example.com/fast"
# # method="post"
# # data-remote="true"
# # data-update-success="glass_of_beer">Hello world!</form>
# #
# <% form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }) do -%>
# "Hello world!"
# <% end -%>
#
def form_remote_tag(options = {}, &block)
html_options = options.delete(:callbacks)
attributes = {}
attributes.merge!(extract_remote_attributes!(options))
attributes.merge!(html_options) if html_options
attributes.merge!(options)
attributes.delete(:builder)
form_tag(attributes.delete(:action) || attributes.delete("data-url"), attributes, &block)
end
# Returns a link that will allow unobtrusive JavaScript to dynamical adjust its
# behaviour. The default behaviour is an XMLHttpRequest in the background instead
# of the regular GET arrangement. The result of that request can then be inserted
# into a DOM object whose id can be specified with <tt>options[:update]</tt>. Usually,
# the result would be a partial prepared by the controller with render :partial.
#
# Examples:
#
# # Generates:
# # <a href="#"
# # data-remote="true"
# # data-url="http://www.example.com/whatnot"
# # data-method="delete"
# # rel="nofollow">Remove Author</a>
# #
# link_to_remote("Remove Author", { :url => { :action => "whatnot" },
# :method => "delete"})
#
#
# You can override the generated HTML options by specifying a hash in
# <tt>options[:html]</tt>.
#
# # Generates:
# # <a class="fine"
# # href="#"
# # data-remote="true"
# # data-url="http://www.example.com/whatnot"
# # data-method="delete"
# # rel="nofollow">Remove Author</a>
# #
# link_to_remote("Remove Author", { :url => { :action => "whatnot" },
# :method => "delete",
# :html => { :class => "fine" }})
#
#
# You can also specify a hash for <tt>options[:update]</tt> to allow for
# easy redirection of output to an other DOM element if a server-side
# error occurs:
#
# Example:
# # Generates:
# #
# # <a href="#"
# # data-url="http://www.example.com/destroy"
# # data-update-success="posts"
# # data-update-failure="error"
# # data-remote="true">Delete this Post</a>'
# #
# link_to_remote "Delete this post",
# :url => { :action => "destroy"},
# :update => { :success => "posts", :failure => "error" }
#
# Optionally, you can use the <tt>options[:position]</tt> parameter to
# influence how the target DOM element is updated. It must be one of
# <tt>:before</tt>, <tt>:top</tt>, <tt>:bottom</tt>, or <tt>:after</tt>.
#
# Example:
# # Generates:
# # <a href="#"
# # data-remote="true"
# # data-url="http://www.example.com/whatnot"
# # data-update-position="bottom">Remove Author</a>
# #
# link_to_remote("Remove Author", :url => { :action => "whatnot" }, :position => :bottom)
#
#
# The method used is by default POST. You can also specify GET or you
# can simulate PUT or DELETE over POST. All specified with <tt>options[:method]</tt>
#
# Example:
# # Generates:
# # <a href='#'
# # data-url='/person/4'
# # rel='nofollow'
# # data-remote='true'
# # data-method='delete'>Destroy</a>
# #
# link_to_remote "Destroy", :url => person_url(:id => person), :method => :delete
#
# By default, these remote requests are processed asynchronous during
# which various JavaScript callbacks can be triggered (for progress
# indicators and the likes). All callbacks get access to the
# <tt>request</tt> object, which holds the underlying XMLHttpRequest.
#
# To access the server response, use <tt>request.responseText</tt>, to
# find out the HTTP status, use <tt>request.status</tt>.
#
# Example:
# # Generates:
# #
# # <a href='#'
# # data-url='http://www.example.com/undo?n=5'
# # data-oncomplete='undoRequestCompleted(request)'
# # data-remote='true'>undo</a>
# #
# link_to_remote "undo",
# :url => { :controller => "words", :action => "undo", :n => word_counter },
# :complete => "undoRequestCompleted(request)"
#
# The callbacks that may be specified are (in order):
#
# <tt>:loading</tt>:: Called when the remote document is being
# loaded with data by the browser.
# <tt>:loaded</tt>:: Called when the browser has finished loading
# the remote document.
# <tt>:interactive</tt>:: Called when the user can interact with the
# remote document, even though it has not
# finished loading.
# <tt>:success</tt>:: Called when the XMLHttpRequest is completed,
# and the HTTP status code is in the 2XX range.
# <tt>:failure</tt>:: Called when the XMLHttpRequest is completed,
# and the HTTP status code is not in the 2XX
# range.
# <tt>:complete</tt>:: Called when the XMLHttpRequest is complete
# (fires after success/failure if they are
# present).
#
# You can further refine <tt>:success</tt> and <tt>:failure</tt> by
# adding additional callbacks for specific status codes.
#
# Example:
#
# # Generates:
# # <a href='/testing/action'
# # date-remote='true'
# # data-failure="function(request){alert('HTTP Error '+ request.status +'+!');return false}"
# # data-404="function(request){alert('Not found...? Wrong URL...?')}"> Hello</a>
# #
# link_to_remote word,
# :url => { :action => "action" },
# 404 => "alert('Not found...? Wrong URL...?')",
# :failure => "alert('HTTP Error ' + request.status + '!')"
#
# A status code callback overrides the success/failure handlers if
# present.
#
# If you for some reason or another need synchronous processing (that'll
# block the browser while the request is happening), you can specify
# <tt>options[:type] = :synchronous</tt>.
#
# You can customize further browser side call logic by passing in
# JavaScript code snippets via some optional parameters. In their order
# of use these are:
#
# <tt>:confirm</tt>:: Adds confirmation dialog.
# <tt>:condition</tt>:: Perform remote request conditionally
# by this expression. Use this to
# describe browser-side conditions when
# request should not be initiated.
# <tt>:before</tt>:: Called before request is initiated.
# <tt>:after</tt>:: Called immediately after request was
# initiated and before <tt>:loading</tt>.
# <tt>:submit</tt>:: Specifies the DOM element ID that's used
# as the parent of the form elements. By
# default this is the current form, but
# it could just as well be the ID of a
# table row or any other DOM element.
# <tt>:with</tt>:: A JavaScript expression specifying
# the parameters for the XMLHttpRequest.
# Any expressions should return a valid
# URL query string.
#
# Example:
#
# :with => "'name=' + $('name').value"
#
# You can generate a link that uses the UJS drivers in the general case, while
# degrading gracefully to plain link behavior in the absence of
# JavaScript by setting <tt>html_options[:href]</tt> to an alternate URL.
# Note the extra curly braces around the <tt>options</tt> hash separate
# it as the second parameter from <tt>html_options</tt>, the third.
#
# Example:
#
# # Generates:
# # <a href='/posts/1'
# # rel='nofollow'
# # data-remote='true'
# # data-method='delete'> Delete this post</a>
# #
# link_to_remote "Delete this post",
# { :update => "posts", :url => { :action => "destroy", :id => post.id } }
#
def link_to_remote(name, options, html_options = {})
attributes = {}
attributes.merge!(:rel => "nofollow") if options[:method] && options[:method].to_s.downcase == "delete"
attributes.merge!(extract_remote_attributes!(options))
if confirm = options.delete(:confirm)
add_confirm_to_attributes!(attributes, confirm)
end
attributes.merge!(html_options)
href = html_options[:href].nil? ? "#" : html_options[:href]
attributes.merge!(:href => href)
content_tag(:a, name, attributes)
end
# Returns an input of type button, which allows the unobtrusive JavaScript driver
# to dynamically adjust its behaviour. The default driver behaviour is to call a
# remote action via XMLHttpRequest in the background.
# The options for specifying the target with :url and defining callbacks is the same
# as link_to_remote.
#
# Example:
#
# # Generates:
# # <input class="fine"
# # type="button"
# # value="Remote outpost"
# # data-remote="true"
# # data-url="http://www.example.com/whatnot" />
# #
# button_to_remote("Remote outpost", { :url => { :action => "whatnot" }}, { :class => "fine" })
#
def button_to_remote(name, options = {}, html_options = {})
attributes = html_options.merge!(:type => "button", :value => name)
if confirm = options.delete(:confirm)
add_confirm_to_attributes!(attributes, confirm)
end
if disable_with = options.delete(:disable_with)
add_disable_with_to_attributes!(attributes, disable_with)
end
attributes.merge!(extract_remote_attributes!(options))
tag(:input, attributes)
end
# Returns an input tag of type button, with the element name of +name+ and a value (i.e., display text)
# of +value+ which will allow the unobtrusive JavaScript driver to dynamically adjust its behaviour
# The default behaviour is to call a remote action via XMLHttpRequest in the background.
#
# request that reloads the page.
#
# # Create a button that submits to the create action
# #
# # Generates:
# # <input name='create_btn'
# # type='button'
# # value='Create'
# # data-remote='true'
# # data-url='/create' />
# #
# <%= submit_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %>
#
# # Submit to the remote action update and update the DIV succeed or fail based
# # on the success or failure of the request
# #
# # Generates:
# # <input name='update_btn'
# # type='button'
# # value='Update'
# # date-remote-submit='true'
# # data-url='/testing/update'
# # data-success='succeed'
# # data-failure='fail' />
# #
# <%= submit_to_remote 'update_btn', 'Update', :url => { :action => 'update' },
# :update => { :success => "succeed", :failure => "fail" }
#
# <tt>options</tt> argument is the same as in form_remote_tag.
def submit_to_remote(name, value, options = {})
html_options = options.delete(:html) || {}
html_options.merge!(:name => name, :value => value, :type => "button")
attributes = extract_remote_attributes!(options)
attributes.merge!(html_options)
attributes["data-remote-submit"] = true
attributes.delete("data-remote")
tag(:input, attributes)
end
# Periodically provides the UJS driver with the information to call the specified
# url (<tt>options[:url]</tt>) every <tt>options[:frequency]</tt> seconds (default is 10). Usually used to
# update a specified div (<tt>options[:update]</tt>) with the results
# of the remote call. The options for specifying the target with <tt>:url</tt>
# and defining callbacks is the same as link_to_remote.
# Examples:
# # Call get_averages and put its results in 'avg' every 10 seconds
# # Generates:
# # <script data-periodical='true'
# # data-url='/get_averages'
# # type='application/json'
# # data-update-success='avg'
# # data-frequency='10'></script>
# #
# periodically_call_remote(:url => { :action => 'get_averages' }, :update => 'avg')
#
# # Call invoice every 10 seconds with the id of the customer
# # If it succeeds, update the invoice DIV; if it fails, update the error DIV
# # Generates:
# # <script data-periodical='true'
# # data-url='/invoice/1'
# # type='application/json'
# # data-update-success='invoice'
# # data-update-failure='error'
# # data-frequency='10'></script>"
# #
# periodically_call_remote(:url => { :action => 'invoice', :id => 1 },
# :update => { :success => "invoice", :failure => "error" }
#
# # Call update every 20 seconds and update the new_block DIV
# # Generates:
# # <script data-periodical='true'
# # data-url='update'
# # type='application/json'
# # data-update-success='news_block'
# # data-frequency='20'></script>
# #
# periodically_call_remote(:url => 'update', :frequency => '20', :update => 'news_block')
#
def periodically_call_remote(options = {})
attributes = extract_observer_attributes!(options)
attributes["data-periodical"] = true
attributes["data-frequency"] ||= 10
# periodically_call_remote does not need data-observe=true
attributes.delete('data-observe')
script_decorator(attributes).html_safe!
end
# Observes the field with the DOM ID specified by +field_id+ and calls a
# callback when its contents have changed. The default callback is an
# Ajax call. By default the value of the observed field is sent as a
# parameter with the Ajax call.
#
# Example:
# # Generates:
# # "<script type='text/javascript'
# # data-observe='true'
# # data-observed='suggest'
# # data-frequency='0.25'
# # type='application/json'
# # data-url='/find_suggestion'
# # data-update-success='suggest'
# # data-with='q'></script>"
# #
# <%= observe_field :suggest, :url => { :action => :find_suggestion },
# :frequency => 0.25,
# :update => :suggest,
# :with => 'q'
# %>
#
# Required +options+ are either of:
# <tt>:url</tt>:: +url_for+-style options for the action to call
# when the field has changed.
# <tt>:function</tt>:: Instead of making a remote call to a URL, you
# can specify javascript code to be called instead.
# Note that the value of this option is used as the
# *body* of the javascript function, a function definition
# with parameters named element and value will be generated for you
# for example:
# observe_field("glass", :frequency => 1, :function => "alert('Element changed')")
# will generate:
# new Form.Element.Observer('glass', 1, function(element, value) {alert('Element changed')})
# The element parameter is the DOM element being observed, and the value is its value at the
# time the observer is triggered.
#
# Additional options are:
# <tt>:frequency</tt>:: The frequency (in seconds) at which changes to
# this field will be detected. Not setting this
# option at all or to a value equal to or less than
# zero will use event based observation instead of
# time based observation.
# <tt>:update</tt>:: Specifies the DOM ID of the element whose
# innerHTML should be updated with the
# XMLHttpRequest response text.
# <tt>:with</tt>:: A JavaScript expression specifying the parameters
# for the XMLHttpRequest. The default is to send the
# key and value of the observed field. Any custom
# expressions should return a valid URL query string.
# The value of the field is stored in the JavaScript
# variable +value+.
#
# Examples
#
# :with => "'my_custom_key=' + value"
# :with => "'person[name]=' + prompt('New name')"
# :with => "Form.Element.serialize('other-field')"
#
# Finally
# :with => 'name'
# is shorthand for
# :with => "'name=' + value"
# This essentially just changes the key of the parameter.
#
# Additionally, you may specify any of the options documented in the
# <em>Common options</em> section at the top of this document.
#
# Example:
#
# # Sends params: {:title => 'Title of the book'} when the book_title input
# # field is changed.
# observe_field 'book_title',
# :url => 'http://example.com/books/edit/1',
# :with => 'title'
#
#
def observe_field(name, options = {})
html_options = options.delete(:callbacks)
options[:observed] = name
attributes = extract_observer_attributes!(options)
attributes.merge!(html_options) if html_options
script_decorator(attributes).html_safe!
end
# Observes the form with the DOM ID specified by +form_id+ and calls a
# callback when its contents have changed. The default callback is an
# Ajax call. By default all fields of the observed field are sent as
# parameters with the Ajax call.
#
# The +options+ for +observe_form+ are the same as the options for
# +observe_field+. The JavaScript variable +value+ available to the
# <tt>:with</tt> option is set to the serialized form by default.
def observe_form(name, options = {})
html_options = options.delete(:callbacks)
options[:observed] = name
attributes = extract_observer_attributes!(options)
attributes.merge!(html_options) if html_options
script_decorator(attributes).html_safe!
end
def script_decorator(options)
attributes = %w(type="application/json")
attributes += options.map{|k, v| k + '="' + v.to_s + '"'}
"<script " + attributes.join(" ") + "></script>"
end
private
def extract_remote_attributes!(options)
attributes = options.delete(:html) || {}
attributes.merge!(extract_update_attributes!(options))
attributes.merge!(extract_request_attributes!(options))
attributes["data-remote"] = true
if submit = options.delete(:submit)
attributes["data-submit"] = submit
end
attributes
end
def extract_request_attributes!(options)
attributes = {}
if method = options.delete(:method)
attributes["data-method"] = method.to_s
end
if type = options.delete(:type)
attributes["data-remote-type"] = type.to_s
end
url_options = options.delete(:url)
url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash)
attributes["data-url"] = escape_javascript(url_for(url_options)) if url_options
purge_unused_attributes!(attributes)
end
def extract_update_attributes!(options)
attributes = {}
update = options.delete(:update)
if update.is_a?(Hash)
attributes["data-update-success"] = update[:success]
attributes["data-update-failure"] = update[:failure]
else
attributes["data-update-success"] = update
end
if position = options.delete(:position)
attributes["data-update-position"] = position.to_s
end
purge_unused_attributes!(attributes)
end
def extract_observer_attributes!(options)
callback = options.delete(:function)
frequency = options.delete(:frequency) || 10
attributes = extract_remote_attributes!(options)
attributes["data-observe"] = true
attributes["data-observed"] = options.delete(:observed)
attributes["data-onobserve"] = callback if callback
attributes["data-frequency"] = frequency if frequency && frequency.to_f != 0
attributes.delete("data-remote")
purge_unused_attributes!(attributes)
end
def purge_unused_attributes!(attributes)
attributes.delete_if {|key, value| value.nil? }
attributes
end
end
# TODO: All evaled goes here per wycat
module AjaxHelperCompat
include AjaxHelper
def link_to_remote(name, options, html_options = {})
set_callbacks(options, html_options)
set_with_and_condition_attributes(options, html_options)
super
end
def button_to_remote(name, options = {}, html_options = {})
set_callbacks(options, html_options)
set_with_and_condition_attributes(options, html_options)
super
end
def form_remote_tag(options, &block)
html = {}
set_callbacks(options, html)
set_with_and_condition_attributes(options, html)
options.merge!(:callbacks => html)
super
end
def observe_field(name, options = {})
html = {}
set_with_and_condition_attributes(options, html)
options.merge!(:callbacks => html)
super
end
def observe_form(name, options = {})
html = {}
set_with_and_condition_attributes(options, html)
options.merge!(:callbacks => html)
super
end
private
def set_callbacks(options, html)
[:before, :after, :uninitialized, :complete, :failure, :success, :interactive, :loaded, :loading].each do |type|
html["data-on#{type}"] = options.delete(type.to_sym)
end
options.each do |option, value|
if option.is_a?(Integer)
html["data-on#{option}"] = options.delete(option)
end
end
end
def set_with_and_condition_attributes(options, html)
if with = options.delete(:with)
html["data-with"] = with
end
if condition = options.delete(:condition)
html["data-condition"] = condition
end
end
end
end
end

View File

@ -140,7 +140,7 @@ module ActionView
:stylesheets_dir => "#{assets_dir}/stylesheets",
}
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'].freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls', 'rails'].freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
# Returns a link tag that browsers and news readers can use to auto-detect
# an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or

View File

@ -262,8 +262,23 @@ module ActionView
# FormTagHelper#form_tag.
def form_for(record_or_name_or_array, *args, &proc)
raise ArgumentError, "Missing block" unless block_given?
options = args.extract_options!
object_name = extract_object_name_for_form!(args, options, record_or_name_or_array)
case record_or_name_or_array
when String, Symbol
object_name = record_or_name_or_array
when Array
object = record_or_name_or_array.last
object_name = ActionController::RecordIdentifier.singular_class_name(object)
apply_form_for_options!(record_or_name_or_array, options)
args.unshift object
else
object = record_or_name_or_array
object_name = ActionController::RecordIdentifier.singular_class_name(object)
apply_form_for_options!([object], options)
args.unshift object
end
concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}))
fields_for(object_name, *(args << options), &proc)
@ -727,25 +742,6 @@ module ActionView
def radio_button(object_name, method, tag_value, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
end
private
def extract_object_name_for_form!(args, options, record_or_name_or_array)
case record_or_name_or_array
when String, Symbol
object_name = record_or_name_or_array
when Array
object = record_or_name_or_array.last
object_name = ActionController::RecordIdentifier.singular_class_name(object)
apply_form_for_options!(record_or_name_or_array, options)
args.unshift object
else
object = record_or_name_or_array
object_name = ActionController::RecordIdentifier.singular_class_name(object)
apply_form_for_options!([object], options)
args.unshift object
end
object_name
end
end
module InstanceTagMethods #:nodoc:

View File

@ -57,7 +57,7 @@ module ActionView
# ==== Examples
# select_tag "people", options_from_collection_for_select(@people, "name", "id")
# # <select id="people" name="people"><option value="1">David</option></select>
#
#
# select_tag "people", "<option>David</option>"
# # => <select id="people" name="people"><option>David</option></select>
#
@ -128,7 +128,7 @@ module ActionView
# Creates a label field
#
# ==== Options
# ==== Options
# * Creates standard HTML attributes for the tag.
#
# ==== Examples
@ -352,20 +352,20 @@ module ActionView
# # => <input disabled="disabled" name="commit" type="submit" value="Save edits" />
#
# submit_tag "Complete sale", :disable_with => "Please wait..."
# # => <input name="commit" data-disable-with="Please wait..."
# # => <input name="commit" onclick="this.disabled=true;this.value='Please wait...';this.form.submit();"
# # type="submit" value="Complete sale" />
#
# submit_tag nil, :class => "form_submit"
# # => <input class="form_submit" name="commit" type="submit" />
#
# submit_tag "Edit", :disable_with => "Editing...", :class => "edit-button"
# # => <input class="edit-button" data-disable-with="Editing..."
# # => <input class="edit-button" onclick="this.disabled=true;this.value='Editing...';this.form.submit();"
# # name="commit" type="submit" value="Edit" />
def submit_tag(value = "Save changes", options = {})
options.stringify_keys!
if disable_with = options.delete("disable_with")
add_disable_with_to_attributes!(options, disable_with)
options["data-disable-with"] = disable_with if disable_with
end
if confirm = options.delete("confirm")
@ -482,7 +482,6 @@ module ActionView
def sanitize_to_id(name)
name.to_s.gsub(']','').gsub(/[^-a-zA-Z0-9:.]/, "_")
end
end
end
end

View File

@ -35,98 +35,7 @@ module ActionView
# For documentation on +javascript_include_tag+ see
# ActionView::Helpers::AssetTagHelper.
module JavaScriptHelper
unless const_defined? :JAVASCRIPT_PATH
JAVASCRIPT_PATH = File.join(File.dirname(__FILE__), 'javascripts')
end
include AjaxHelperCompat
# Returns a link of the given +name+ that will trigger a JavaScript +function+ using the
# onclick handler and return false after the fact.
#
# The first argument +name+ is used as the link text.
#
# The next arguments are optional and may include the javascript function definition and a hash of html_options.
#
# The +function+ argument can be omitted in favor of an +update_page+
# block, which evaluates to a string when the template is rendered
# (instead of making an Ajax request first).
#
# The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
#
# Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
#
#
# Examples:
# link_to_function "Greeting", "alert('Hello world!')"
# Produces:
# <a onclick="alert('Hello world!'); return false;" href="#">Greeting</a>
#
# link_to_function(image_tag("delete"), "if (confirm('Really?')) do_delete()")
# Produces:
# <a onclick="if (confirm('Really?')) do_delete(); return false;" href="#">
# <img src="/images/delete.png?" alt="Delete"/>
# </a>
#
# link_to_function("Show me more", nil, :id => "more_link") do |page|
# page[:details].visual_effect :toggle_blind
# page[:more_link].replace_html "Show me less"
# end
# Produces:
# <a href="#" id="more_link" onclick="try {
# $(&quot;details&quot;).visualEffect(&quot;toggle_blind&quot;);
# $(&quot;more_link&quot;).update(&quot;Show me less&quot;);
# }
# catch (e) {
# alert('RJS error:\n\n' + e.toString());
# alert('$(\&quot;details\&quot;).visualEffect(\&quot;toggle_blind\&quot;);
# \n$(\&quot;more_link\&quot;).update(\&quot;Show me less\&quot;);');
# throw e
# };
# return false;">Show me more</a>
#
def link_to_function(name, *args, &block)
html_options = args.extract_options!.symbolize_keys
function = block_given? ? update_page(&block) : args[0] || ''
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
href = html_options[:href] || '#'
content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
end
# Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the
# onclick handler.
#
# The first argument +name+ is used as the button's value or display text.
#
# The next arguments are optional and may include the javascript function definition and a hash of html_options.
#
# The +function+ argument can be omitted in favor of an +update_page+
# block, which evaluates to a string when the template is rendered
# (instead of making an Ajax request first).
#
# The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
#
# Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
#
# Examples:
# button_to_function "Greeting", "alert('Hello world!')"
# button_to_function "Delete", "if (confirm('Really?')) do_delete()"
# button_to_function "Details" do |page|
# page[:details].visual_effect :toggle_slide
# end
# button_to_function "Details", :class => "details_button" do |page|
# page[:details].visual_effect :toggle_slide
# end
def button_to_function(name, *args, &block)
html_options = args.extract_options!.symbolize_keys
function = block_given? ? update_page(&block) : args[0] || ''
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
end
include PrototypeHelper
JS_ESCAPE_MAP = {
'\\' => '\\\\',
@ -186,65 +95,6 @@ module ActionView
def javascript_cdata_section(content) #:nodoc:
"\n//#{cdata_section("\n#{content}\n//")}\n"
end
protected
def convert_options_to_javascript!(html_options, url = '')
confirm, popup = html_options.delete("confirm"), html_options.delete("popup")
method, href = html_options.delete("method"), html_options['href']
if popup && method
raise ActionView::ActionViewError, "You can't use :popup and :method in the same link"
elsif confirm && popup
add_confirm_to_attributes!(html_options, confirm)
html_options["data-popup"] = popup_attributes(popup)
elsif confirm && method
add_confirm_to_attributes!(html_options, confirm)
add_method_to_attributes!(html_options, method, url)
elsif confirm
add_confirm_to_attributes!(html_options, confirm)
elsif method
add_method_to_attributes!(html_options, method, url)
elsif popup
html_options["data-popup"] = popup_attributes(popup)
end
end
def add_confirm_to_attributes!(html_options, confirm)
html_options["data-confirm"] = confirm if confirm
end
def add_method_to_attributes!(html_options, method, url = nil)
html_options["rel"] = "nofollow" if method.to_s.downcase == "delete"
html_options["data-method"] = method
if url.size > 0
html_options["data-url"] = url
end
end
def add_disable_with_to_attributes!(html_options, disable_with)
html_options["data-disable-with"] = disable_with if disable_with
end
def popup_attributes(popup)
popup.is_a?(Array) ? "{title: '#{popup.first}', options: '#{popup.last}'}" : "true"
end
def options_for_javascript(options)
if options.empty?
'{}'
else
"{#{options.keys.map { |k| "#{k}:#{options[k]}" }.sort.join(', ')}}"
end
end
def array_or_string_for_javascript(option)
if option.kind_of?(Array)
"['#{option.join('\',\'')}']"
elsif !option.nil?
"'#{option}'"
end
end
end
end
end

View File

@ -1,7 +1,6 @@
require 'set'
require 'active_support/json'
require 'active_support/core_ext/object/returning'
require 'action_view/helpers/scriptaculous_helper'
module ActionView
module Helpers
@ -28,6 +27,26 @@ module ActionView
# ActionView::Helpers::JavaScriptHelper for more information on including
# this and other JavaScript files in your Rails templates.)
#
# Now you're ready to call a remote action either through a link...
#
# link_to_remote "Add to cart",
# :url => { :action => "add", :id => product.id },
# :update => { :success => "cart", :failure => "error" }
#
# ...through a form...
#
# <% form_remote_tag :url => '/shipping' do -%>
# <div><%= submit_tag 'Recalculate Shipping' %></div>
# <% end -%>
#
# As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than
# are listed here); check out the documentation for each method to find out more about its usage and options.
#
# === Common Options
# See link_to_remote for documentation of options common to all Ajax
# helpers; any of the options specified by link_to_remote can be used
# by the other helpers.
#
# == Designing your Rails actions for Ajax
# When building your action handlers (that is, the Rails actions that receive your background requests), it's
# important to remember a few things. First, whatever your action would normally return to the browser, it will
@ -74,8 +93,6 @@ module ActionView
# See JavaScriptGenerator for information on updating multiple elements
# on the page in an Ajax response.
module PrototypeHelper
include ScriptaculousHelper
unless const_defined? :CALLBACKS
CALLBACKS = Set.new([ :create, :uninitialized, :loading, :loaded,
:interactive, :complete, :failure, :success ] +
@ -85,6 +102,39 @@ module ActionView
:form, :with, :update, :script, :type ]).merge(CALLBACKS)
end
# Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the
# onclick handler.
#
# The first argument +name+ is used as the button's value or display text.
#
# The next arguments are optional and may include the javascript function definition and a hash of html_options.
#
# The +function+ argument can be omitted in favor of an +update_page+
# block, which evaluates to a string when the template is rendered
# (instead of making an Ajax request first).
#
# The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
#
# Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
#
# Examples:
# button_to_function "Greeting", "alert('Hello world!')"
# button_to_function "Delete", "if (confirm('Really?')) do_delete()"
# button_to_function "Details" do |page|
# page[:details].visual_effect :toggle_slide
# end
# button_to_function "Details", :class => "details_button" do |page|
# page[:details].visual_effect :toggle_slide
# end
def button_to_function(name, *args, &block)
html_options = args.extract_options!.symbolize_keys
function = block_given? ? update_page(&block) : args[0] || ''
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
end
# Returns the JavaScript needed for a remote function.
# Takes the same arguments as link_to_remote.
#
@ -502,32 +552,6 @@ module ActionView
record "}, #{(seconds * 1000).to_i})"
end
# Starts a script.aculo.us visual effect. See
# ActionView::Helpers::ScriptaculousHelper for more information.
def visual_effect(name, id = nil, options = {})
record @context.send(:visual_effect, name, id, options)
end
# Creates a script.aculo.us sortable element. Useful
# to recreate sortable elements after items get added
# or deleted.
# See ActionView::Helpers::ScriptaculousHelper for more information.
def sortable(id, options = {})
record @context.send(:sortable_element_js, id, options)
end
# Creates a script.aculo.us draggable element.
# See ActionView::Helpers::ScriptaculousHelper for more information.
def draggable(id, options = {})
record @context.send(:draggable_element_js, id, options)
end
# Creates a script.aculo.us drop receiving element.
# See ActionView::Helpers::ScriptaculousHelper for more information.
def drop_receiving(id, options = {})
record @context.send(:drop_receiving_element_js, id, options)
end
private
def loop_on_multiple_args(method, ids)
record(ids.size>1 ?
@ -599,65 +623,57 @@ module ActionView
javascript_tag update_page(&block), html_options
end
protected
def options_for_ajax(options)
js_options = build_callbacks(options)
js_options['asynchronous'] = options[:type] != :synchronous
js_options['method'] = method_option_to_s(options[:method]) if options[:method]
js_options['insertion'] = "'#{options[:position].to_s.downcase}'" if options[:position]
js_options['evalScripts'] = options[:script].nil? || options[:script]
if options[:form]
js_options['parameters'] = 'Form.serialize(this)'
elsif options[:submit]
js_options['parameters'] = "Form.serialize('#{options[:submit]}')"
elsif options[:with]
js_options['parameters'] = options[:with]
end
if protect_against_forgery? && !options[:form]
if js_options['parameters']
js_options['parameters'] << " + '&"
protected
def options_for_javascript(options)
if options.empty?
'{}'
else
js_options['parameters'] = "'"
end
js_options['parameters'] << "#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}')"
end
options_for_javascript(js_options)
end
def method_option_to_s(method)
(method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'"
end
def build_observer(klass, name, options = {})
if options[:with] && (options[:with] !~ /[\{=(.]/)
options[:with] = "'#{options[:with]}=' + encodeURIComponent(value)"
else
options[:with] ||= 'value' unless options[:function]
end
callback = options[:function] || remote_function(options)
javascript = "new #{klass}('#{name}', "
javascript << "#{options[:frequency]}, " if options[:frequency]
javascript << "function(element, value) {"
javascript << "#{callback}}"
javascript << ")"
javascript_tag(javascript)
end
def build_callbacks(options)
callbacks = {}
options.each do |callback, code|
if CALLBACKS.include?(callback)
name = 'on' + callback.to_s.capitalize
callbacks[name] = "function(request){#{code}}"
"{#{options.keys.map { |k| "#{k}:#{options[k]}" }.sort.join(', ')}}"
end
end
callbacks
end
def options_for_ajax(options)
js_options = build_callbacks(options)
js_options['asynchronous'] = options[:type] != :synchronous
js_options['method'] = method_option_to_s(options[:method]) if options[:method]
js_options['insertion'] = "'#{options[:position].to_s.downcase}'" if options[:position]
js_options['evalScripts'] = options[:script].nil? || options[:script]
if options[:form]
js_options['parameters'] = 'Form.serialize(this)'
elsif options[:submit]
js_options['parameters'] = "Form.serialize('#{options[:submit]}')"
elsif options[:with]
js_options['parameters'] = options[:with]
end
if protect_against_forgery? && !options[:form]
if js_options['parameters']
js_options['parameters'] << " + '&"
else
js_options['parameters'] = "'"
end
js_options['parameters'] << "#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}')"
end
options_for_javascript(js_options)
end
def method_option_to_s(method)
(method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'"
end
def build_callbacks(options)
callbacks = {}
options.each do |callback, code|
if CALLBACKS.include?(callback)
name = 'on' + callback.to_s.capitalize
callbacks[name] = "function(request){#{code}}"
end
end
callbacks
end
end
# Converts chained method calls on DOM proxy elements into JavaScript chains

View File

@ -3,11 +3,11 @@ require 'active_support/json'
module ActionView
module Helpers
# Provides a set of helpers for calling Scriptaculous JavaScript
# Provides a set of helpers for calling Scriptaculous JavaScript
# functions, including those which create Ajax controls and visual effects.
#
# To be able to use these helpers, you must include the Prototype
# JavaScript framework and the Scriptaculous JavaScript library in your
# To be able to use these helpers, you must include the Prototype
# JavaScript framework and the Scriptaculous JavaScript library in your
# pages. See the documentation for ActionView::Helpers::JavaScriptHelper
# for more information on including the necessary JavaScript.
#
@ -18,22 +18,17 @@ module ActionView
unless const_defined? :TOGGLE_EFFECTS
TOGGLE_EFFECTS = [:toggle_appear, :toggle_slide, :toggle_blind]
end
# Returns a JavaScript snippet to be used on the Ajax callbacks for
# starting visual effects.
#
# Example:
# <%= link_to_remote "Reload", :update => "posts",
# :url => { :action => "reload" },
# :complete => visual_effect(:highlight, "posts", :duration => 0.5)
#
# If no +element_id+ is given, it assumes "element" which should be a local
# variable in the generated JavaScript execution context. This can be
# variable in the generated JavaScript execution context. This can be
# used for example with +drop_receiving_element+:
#
# <%= drop_receiving_element (...), :loading => visual_effect(:fade) %>
#
# This would fade the element that was dropped on the drop receiving
# This would fade the element that was dropped on the drop receiving
# element.
#
# For toggling visual effects, you can use <tt>:toggle_appear</tt>, <tt>:toggle_slide</tt>, and
@ -44,13 +39,13 @@ module ActionView
# http://script.aculo.us for more documentation.
def visual_effect(name, element_id = false, js_options = {})
element = element_id ? ActiveSupport::JSON.encode(element_id) : "element"
js_options[:queue] = if js_options[:queue].is_a?(Hash)
'{' + js_options[:queue].map {|k, v| k == :limit ? "#{k}:#{v}" : "#{k}:'#{v}'" }.join(',') + '}'
elsif js_options[:queue]
"'#{js_options[:queue]}'"
end if js_options[:queue]
[:endcolor, :direction, :startcolor, :scaleMode, :restorecolor].each do |option|
js_options[option] = "'#{js_options[option]}'" if js_options[option]
end
@ -61,7 +56,7 @@ module ActionView
"new Effect.#{name.to_s.camelize}(#{element},#{options_for_javascript(js_options)});"
end
end
# Makes the element with the DOM ID specified by +element_id+ sortable
# by drag-and-drop and make an Ajax call whenever the sort order has
# changed. By default, the action called gets the serialized sortable
@ -71,84 +66,84 @@ module ActionView
#
# <%= sortable_element("my_list", :url => { :action => "order" }) %>
#
# In the example, the action gets a "my_list" array parameter
# containing the values of the ids of elements the sortable consists
# In the example, the action gets a "my_list" array parameter
# containing the values of the ids of elements the sortable consists
# of, in the current order.
#
# Important: For this to work, the sortable elements must have id
# attributes in the form "string_identifier". For example, "item_1". Only
# the identifier part of the id attribute will be serialized.
#
#
# Additional +options+ are:
#
# * <tt>:format</tt> - A regular expression to determine what to send as the
# serialized id to the server (the default is <tt>/^[^_]*_(.*)$/</tt>).
#
#
# * <tt>:constraint</tt> - Whether to constrain the dragging to either
# <tt>:horizontal</tt> or <tt>:vertical</tt> (or false to make it unconstrained).
#
#
# * <tt>:overlap</tt> - Calculate the item overlap in the <tt>:horizontal</tt>
# or <tt>:vertical</tt> direction.
#
#
# * <tt>:tag</tt> - Which children of the container element to treat as
# sortable (default is <tt>li</tt>).
#
#
# * <tt>:containment</tt> - Takes an element or array of elements to treat as
# potential drop targets (defaults to the original target element).
#
#
# * <tt>:only</tt> - A CSS class name or array of class names used to filter
# out child elements as candidates.
#
#
# * <tt>:scroll</tt> - Determines whether to scroll the list during drag
# operations if the list runs past the visual border.
#
#
# * <tt>:tree</tt> - Determines whether to treat nested lists as part of the
# main sortable list. This means that you can create multi-layer lists,
# and not only sort items at the same level, but drag and sort items
# between levels.
#
#
# * <tt>:hoverclass</tt> - If set, the Droppable will have this additional CSS class
# when an accepted Draggable is hovered over it.
#
# when an accepted Draggable is hovered over it.
#
# * <tt>:handle</tt> - Sets whether the element should only be draggable by an
# embedded handle. The value may be a string referencing a CSS class value
# (as of script.aculo.us V1.5). The first child/grandchild/etc. element
# found within the element that has this CSS class value will be used as
# the handle.
#
#
# * <tt>:ghosting</tt> - Clones the element and drags the clone, leaving
# the original in place until the clone is dropped (default is <tt>false</tt>).
#
#
# * <tt>:dropOnEmpty</tt> - If true the Sortable container will be made into
# a Droppable, that can receive a Draggable (as according to the containment
# rules) as a child element when there are no more elements inside (default
# is <tt>false</tt>).
#
#
# * <tt>:onChange</tt> - Called whenever the sort order changes while dragging. When
# dragging from one Sortable to another, the callback is called once on each
# Sortable. Gets the affected element as its parameter.
#
#
# * <tt>:onUpdate</tt> - Called when the drag ends and the Sortable's order is
# changed in any way. When dragging from one Sortable to another, the callback
# is called once on each Sortable. Gets the container as its parameter.
#
#
# See http://script.aculo.us for more documentation.
def sortable_element(element_id, options = {})
javascript_tag(sortable_element_js(element_id, options).chop!)
end
def sortable_element_js(element_id, options = {}) #:nodoc:
options[:with] ||= "Sortable.serialize(#{ActiveSupport::JSON.encode(element_id)})"
options[:onUpdate] ||= "function(){" + remote_function(options) + "}"
options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
[:tag, :overlap, :constraint, :handle].each do |option|
options[option] = "'#{options[option]}'" if options[option]
end
options[:containment] = array_or_string_for_javascript(options[:containment]) if options[:containment]
options[:only] = array_or_string_for_javascript(options[:only]) if options[:only]
%(Sortable.create(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
end
@ -156,24 +151,24 @@ module ActionView
#
# Example:
# <%= draggable_element("my_image", :revert => true)
#
#
# You can change the behaviour with various options, see
# http://script.aculo.us for more documentation.
def draggable_element(element_id, options = {})
javascript_tag(draggable_element_js(element_id, options).chop!)
end
def draggable_element_js(element_id, options = {}) #:nodoc:
%(new Draggable(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
end
# Makes the element with the DOM ID specified by +element_id+ receive
# dropped draggable elements (created by +draggable_element+).
# and make an AJAX call. By default, the action called gets the DOM ID
# and make an AJAX call. By default, the action called gets the DOM ID
# of the element as parameter.
#
# Example:
# <%= drop_receiving_element("my_cart", :url =>
# <%= drop_receiving_element("my_cart", :url =>
# { :controller => "cart", :action => "add" }) %>
#
# You can change the behaviour with various options, see
@ -181,46 +176,87 @@ module ActionView
#
# Some of these +options+ include:
# * <tt>:accept</tt> - Set this to a string or an array of strings describing the
# allowable CSS classes that the +draggable_element+ must have in order
# allowable CSS classes that the +draggable_element+ must have in order
# to be accepted by this +drop_receiving_element+.
#
#
# * <tt>:confirm</tt> - Adds a confirmation dialog. Example:
#
#
# :confirm => "Are you sure you want to do this?"
#
#
# * <tt>:hoverclass</tt> - If set, the +drop_receiving_element+ will have
# this additional CSS class when an accepted +draggable_element+ is
# hovered over it.
#
# hovered over it.
#
# * <tt>:onDrop</tt> - Called when a +draggable_element+ is dropped onto
# this element. Override this callback with a JavaScript expression to
# this element. Override this callback with a JavaScript expression to
# change the default drop behaviour. Example:
#
#
# :onDrop => "function(draggable_element, droppable_element, event) { alert('I like bananas') }"
#
#
# This callback gets three parameters: The Draggable element, the Droppable
# element and the Event object. You can extract additional information about
# the drop - like if the Ctrl or Shift keys were pressed - from the Event object.
#
#
# * <tt>:with</tt> - A JavaScript expression specifying the parameters for
# the XMLHttpRequest. Any expressions should return a valid URL query string.
def drop_receiving_element(element_id, options = {})
javascript_tag(drop_receiving_element_js(element_id, options).chop!)
end
def drop_receiving_element_js(element_id, options = {}) #:nodoc:
options[:with] ||= "'id=' + encodeURIComponent(element.id)"
options[:onDrop] ||= "function(element){" + remote_function(options) + "}"
options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
options[:accept] = array_or_string_for_javascript(options[:accept]) if options[:accept]
options[:accept] = array_or_string_for_javascript(options[:accept]) if options[:accept]
options[:hoverclass] = "'#{options[:hoverclass]}'" if options[:hoverclass]
# Confirmation happens during the onDrop callback, so it can be removed from the options
options.delete(:confirm) if options[:confirm]
%(Droppables.add(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
end
protected
def array_or_string_for_javascript(option)
if option.kind_of?(Array)
"['#{option.join('\',\'')}']"
elsif !option.nil?
"'#{option}'"
end
end
end
module PrototypeHelper
class JavaScriptGenerator
module GeneratorMethods
# Starts a script.aculo.us visual effect. See
# ActionView::Helpers::ScriptaculousHelper for more information.
def visual_effect(name, id = nil, options = {})
record @context.send(:visual_effect, name, id, options)
end
# Creates a script.aculo.us sortable element. Useful
# to recreate sortable elements after items get added
# or deleted.
# See ActionView::Helpers::ScriptaculousHelper for more information.
def sortable(id, options = {})
record @context.send(:sortable_element_js, id, options)
end
# Creates a script.aculo.us draggable element.
# See ActionView::Helpers::ScriptaculousHelper for more information.
def draggable(id, options = {})
record @context.send(:draggable_element_js, id, options)
end
# Creates a script.aculo.us drop receiving element.
# See ActionView::Helpers::ScriptaculousHelper for more information.
def drop_receiving(id, options = {})
record @context.send(:drop_receiving_element_js, id, options)
end
end
end
end
end
end

View File

@ -120,10 +120,6 @@ module ActionView
# * <tt>:confirm => 'question?'</tt> - This will add a JavaScript confirm
# prompt with the question specified. If the user accepts, the link is
# processed normally, otherwise no action is taken.
# * <tt>:popup => true || array of window options</tt> - This will force the
# link to open in a popup window. By passing true, a default browser window
# will be opened with the URL. You can also specify an array of options
# that are passed to the <tt>window.open</tt> JavaScript call.
# * <tt>:method => symbol of HTTP verb</tt> - This modifier will dynamically
# create an HTML form and immediately submit the form for processing using
# the HTTP verb specified. Useful for having links perform a POST operation
@ -136,10 +132,6 @@ module ActionView
# the request object's methods for <tt>post?</tt>, <tt>delete?</tt> or <tt>put?</tt>.
# * The +html_options+ will accept a hash of html attributes for the link tag.
#
# You can mix and match the +html_options+ with the exception of
# <tt>:popup</tt> and <tt>:method</tt> which will raise an
# <tt>ActionView::ActionViewError</tt> exception.
#
# ==== Examples
# Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments
# and newer RESTful routes. Current Rails style favors RESTful routes whenever possible, so base
@ -203,17 +195,11 @@ module ActionView
# link_to "Nonsense search", searches_path(:foo => "bar", :baz => "quux")
# # => <a href="/searches?foo=bar&amp;baz=quux">Nonsense search</a>
#
# The three options specific to +link_to+ (<tt>:confirm</tt>, <tt>:popup</tt>, and <tt>:method</tt>) are used as follows:
# The three options specific to +link_to+ (<tt>:confirm</tt> and <tt>:method</tt>) are used as follows:
#
# link_to "Visit Other Site", "http://www.rubyonrails.org/", :confirm => "Are you sure?"
# # => <a href="http://www.rubyonrails.org/" onclick="return confirm('Are you sure?');">Visit Other Site</a>
#
# link_to "Help", { :action => "help" }, :popup => true
# # => <a href="/testing/help/" onclick="window.open(this.href);return false;">Help</a>
#
# link_to "View Image", @image, :popup => ['new_window_name', 'height=300,width=600']
# # => <a href="/images/9" onclick="window.open(this.href,'new_window_name','height=300,width=600');return false;">View Image</a>
#
# link_to "Delete Image", @image, :confirm => "Are you sure?", :method => :delete
# # => <a href="/images/9" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form');
# f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;
@ -232,11 +218,11 @@ module ActionView
html_options = args[2]
url = url_for(options)
html_options = convert_options_to_data_attributes(options, html_options)
if html_options
html_options = html_options.stringify_keys
href = html_options['href']
convert_options_to_javascript!(html_options, url)
tag_options = tag_options(html_options)
else
tag_options = nil
@ -305,11 +291,10 @@ module ActionView
request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
end
url = options.is_a?(String) ? options : self.url_for(options)
name ||= url
convert_options_to_javascript!(html_options, url)
html_options = convert_options_to_data_attributes(options, html_options)
html_options.merge!("type" => "submit", "value" => name)
@ -562,6 +547,63 @@ module ActionView
end
private
def convert_options_to_data_attributes(options, html_options)
html_options = {} if html_options.nil?
html_options = html_options.stringify_keys
if (options.is_a?(Hash) && options.key?('remote')) || (html_options.is_a?(Hash) && html_options.key?('remote'))
html_options['data-remote'] = 'true'
options.delete('remote') if options.is_a?(Hash)
html_options.delete('remote') if html_options.is_a?(Hash)
end
confirm = html_options.delete("confirm")
if html_options.key?("popup")
ActiveSupport::Deprecation.warn(":popup has been deprecated", caller)
end
method, href = html_options.delete("method"), html_options['href']
if confirm && method
add_confirm_to_attributes!(html_options, confirm)
add_method_to_attributes!(html_options, method)
elsif confirm
add_confirm_to_attributes!(html_options, confirm)
elsif method
add_method_to_attributes!(html_options, method)
end
html_options["data-url"] = options[:url] if options.is_a?(Hash) && options[:url]
html_options
end
def add_confirm_to_attributes!(html_options, confirm)
html_options["data-confirm"] = confirm if confirm
end
def add_method_to_attributes!(html_options, method)
html_options["rel"] = "nofollow" if method && method.to_s.downcase != "get"
html_options["data-method"] = method if method
end
def options_for_javascript(options)
if options.empty?
'{}'
else
"{#{options.keys.map { |k| "#{k}:#{options[k]}" }.sort.join(', ')}}"
end
end
def array_or_string_for_javascript(option)
if option.kind_of?(Array)
"['#{option.join('\',\'')}']"
elsif !option.nil?
"'#{option}'"
end
end
# Processes the +html_options+ hash, converting the boolean
# attributes from true/false form into the form required by
# HTML/XHTML. (An attribute is considered to be boolean if

View File

@ -45,7 +45,7 @@ module ActionView #:nodoc:
end
end
raise ActionView::MissingTemplate.new(self, "#{prefix}/#{path} - #{details.inspect} - partial: #{!!partial}")
raise ActionView::MissingTemplate.new(self, "#{prefix}/#{path}", details, partial)
end
def exists?(path, extension = nil, prefix = nil, partial = false)

View File

@ -11,13 +11,17 @@ module AbstractController
end
self.view_paths = [ActionView::FixtureResolver.new(
"default.erb" => "With Default",
"template.erb" => "With Template",
"renderer/default.erb" => "With Default",
"renderer/string.erb" => "With String",
"renderer/symbol.erb" => "With Symbol",
"renderer/template_name.erb" => "With Template Name",
"string/with_path.erb" => "With String With Path",
"some/file.erb" => "With File",
"template_name.erb" => "With Template Name"
"with_format.html.erb" => "With html format",
"with_format.xml.erb" => "With xml format",
"with_locale.en.erb" => "With en locale",
"with_locale.pl.erb" => "With pl locale"
)]
def template
@ -59,6 +63,22 @@ module AbstractController
def object
render :_template => ActionView::Template::Text.new("With Object")
end
def with_html_format
render :template => "with_format", :format => :html
end
def with_xml_format
render :template => "with_format", :format => :xml
end
def with_en_locale
render :template => "with_locale"
end
def with_pl_locale
render :template => "with_locale", :locale => :pl
end
end
class TestRenderer < ActiveSupport::TestCase
@ -117,6 +137,25 @@ module AbstractController
assert_equal "With Object", @controller.response_body
end
def test_render_with_html_format
@controller.process(:with_html_format)
assert_equal "With html format", @controller.response_body
end
def test_render_with_xml_format
@controller.process(:with_xml_format)
assert_equal "With xml format", @controller.response_body
end
def test_render_with_en_locale
@controller.process(:with_en_locale)
assert_equal "With en locale", @controller.response_body
end
def test_render_with_pl_locale
@controller.process(:with_pl_locale)
assert_equal "With pl locale", @controller.response_body
end
end
end
end

View File

@ -18,7 +18,7 @@ unless defined?(ActionMailer)
end
end
ActionMailer::Base.template_root = FIXTURE_LOAD_PATH
ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH
class AssertSelectTest < ActionController::TestCase
Assertion = ActiveSupport::TestCase::Assertion
@ -716,7 +716,7 @@ EOF
def test_assert_select_email
assert_raise(Assertion) { assert_select_email {} }
AssertSelectMailer.deliver_test "<div><p>foo</p><p>bar</p></div>"
AssertSelectMailer.test("<div><p>foo</p><p>bar</p></div>").deliver
assert_select_email do
assert_select "div:root" do
assert_select "p:first-child", "foo"

View File

@ -135,17 +135,16 @@ class HelperTest < ActiveSupport::TestCase
assert methods.include?('foobar')
end
# TODO Add this deprecation back before Rails 3.0 final release
# def test_deprecation
# assert_deprecated do
# ActionController::Base.helpers_dir = "some/foo/bar"
# end
# assert_deprecated do
# assert_equal ["some/foo/bar"], ActionController::Base.helpers_dir
# end
# ensure
# ActionController::Base.helpers_path = [File.dirname(__FILE__) + '/../fixtures/helpers']
# end
def test_deprecation
assert_deprecated do
ActionController::Base.helpers_dir = "some/foo/bar"
end
assert_deprecated do
assert_equal ["some/foo/bar"], ActionController::Base.helpers_dir
end
ensure
ActionController::Base.helpers_path = [File.dirname(__FILE__) + '/../fixtures/helpers']
end
private
def expected_helper_methods

View File

@ -499,7 +499,7 @@ class RespondWithController < ActionController::Base
def using_resource_with_action
respond_with(resource, :action => :foo) do |format|
format.html { raise ActionView::MissingTemplate.new([], "method") }
format.html { raise ActionView::MissingTemplate.new([], "foo/bar", {}, false) }
end
end

View File

@ -6,14 +6,10 @@ module RequestForgeryProtectionActions
def index
render :inline => "<%= form_tag('/') {} %>"
end
def show_button
render :inline => "<%= button_to('New', '/') {} %>"
end
def remote_form
render :inline => "<% form_remote_tag(:url => '/') {} %>"
end
def unsafe
render :text => 'pwn'
@ -30,11 +26,11 @@ end
class FreeCookieController < RequestForgeryProtectionController
self.allow_forgery_protection = false
def index
render :inline => "<%= form_tag('/') {} %>"
end
def show_button
render :inline => "<%= button_to('New', '/') {} %>"
end
@ -65,11 +61,6 @@ module RequestForgeryProtectionTests
assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
end
def test_should_render_remote_form_with_only_one_token_parameter
get :remote_form
assert_equal 1, @response.body.scan(@token).size
end
def test_should_allow_get
get :index
assert_response :success
@ -84,12 +75,12 @@ module RequestForgeryProtectionTests
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
assert_raise(ActionController::InvalidAuthenticityToken) { post :index, :format => :html }
end
def test_should_not_allow_html_put_without_token
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
assert_raise(ActionController::InvalidAuthenticityToken) { put :index, :format => :html }
end
def test_should_not_allow_html_delete_without_token
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
assert_raise(ActionController::InvalidAuthenticityToken) { delete :index, :format => :html }
@ -154,51 +145,51 @@ module RequestForgeryProtectionTests
delete :index, :format => 'xml'
end
end
def test_should_allow_xhr_post_without_token
assert_nothing_raised { xhr :post, :index }
end
def test_should_allow_xhr_put_without_token
assert_nothing_raised { xhr :put, :index }
end
def test_should_allow_xhr_delete_without_token
assert_nothing_raised { xhr :delete, :index }
end
def test_should_allow_xhr_post_with_encoded_form_content_type_without_token
@request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
assert_nothing_raised { xhr :post, :index }
end
def test_should_allow_post_with_token
post :index, :authenticity_token => @token
assert_response :success
end
def test_should_allow_put_with_token
put :index, :authenticity_token => @token
assert_response :success
end
def test_should_allow_delete_with_token
delete :index, :authenticity_token => @token
assert_response :success
end
def test_should_allow_post_with_xml
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
post :index, :format => 'xml'
assert_response :success
end
def test_should_allow_put_with_xml
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
put :index, :format => 'xml'
assert_response :success
end
def test_should_allow_delete_with_xml
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
delete :index, :format => 'xml'
@ -231,17 +222,17 @@ class FreeCookieControllerTest < ActionController::TestCase
ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
end
def test_should_not_render_form_with_token_tag
get :index
assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false
end
def test_should_not_render_button_to_with_token_tag
get :show_button
assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false
end
def test_should_allow_all_methods_without_token
[:post, :put, :delete].each do |method|
assert_nothing_raised { send(method, :index)}

View File

@ -5,7 +5,7 @@ module AbstractController
class UrlForTests < ActionController::TestCase
class W
include AbstractController::UrlFor
include ActionController::UrlFor
end
def teardown
@ -132,7 +132,7 @@ module AbstractController
end
# We need to create a new class in order to install the new named route.
kls = Class.new { include AbstractController::UrlFor }
kls = Class.new { include ActionController::UrlFor }
controller = kls.new
assert controller.respond_to?(:home_url)
assert_equal 'http://www.basecamphq.com/home/sweet/home/again',
@ -153,7 +153,7 @@ module AbstractController
match '/home/sweet/home/:user', :to => 'home#index', :as => :home
end
kls = Class.new { include AbstractController::UrlFor }
kls = Class.new { include ActionController::UrlFor }
controller = kls.new
assert_equal 'http://www.basecamphq.com/subdir/home/sweet/home/again',
@ -171,7 +171,7 @@ module AbstractController
end
# We need to create a new class in order to install the new named route.
kls = Class.new { include AbstractController::UrlFor }
kls = Class.new { include ActionController::UrlFor }
controller = kls.new
assert controller.respond_to?(:home_url)
assert_equal '/brave/new/world',
@ -239,7 +239,7 @@ module AbstractController
end
# We need to create a new class in order to install the new named route.
kls = Class.new { include AbstractController::UrlFor }
kls = Class.new { include ActionController::UrlFor }
kls.default_url_options[:host] = 'www.basecamphq.com'
controller = kls.new
@ -251,8 +251,8 @@ module AbstractController
end
def test_multiple_includes_maintain_distinct_options
first_class = Class.new { include AbstractController::UrlFor }
second_class = Class.new { include AbstractController::UrlFor }
first_class = Class.new { include ActionController::UrlFor }
second_class = Class.new { include ActionController::UrlFor }
first_host, second_host = 'firsthost.com', 'secondhost.com'

View File

@ -1,452 +0,0 @@
require 'abstract_unit'
require 'active_model'
class Author
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
def save
@id = 1
end
def new_record?
@id.nil?
end
def name
@id.nil? ? 'new author' : "author ##{@id}"
end
end
class Article
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
attr_reader :author_id
def save
@id = 1
@author_id = 1
end
def new_record?
@id.nil?
end
def name
@id.nil? ? 'new article' : "article ##{@id}"
end
end
class AjaxHelperBaseTest < ActionView::TestCase
attr_accessor :formats, :output_buffer
def reset_formats(format)
@format = format
end
def setup
super
@template = self
@controller = Class.new do
def url_for(options)
return optons unless options.is_a?(Hash)
url = options.delete(:only_path) ? '/' : 'http://www.example.com'
if controller = options.delete(:controller)
url << '/' << controller.to_s
end
if action = options.delete(:action)
url << '/' << action.to_s
end
if id = options.delete(:id)
url << '/' << id.to_s
end
url << hash_to_param(options) if options.any?
url.gsub!(/\/\/+/,'/')
url
end
private
def hash_to_param(hash)
hash.map { |k,v| "#{k}=#{v}" }.join('&').insert(0,'?')
end
end.new
end
protected
def request_forgery_protection_token
nil
end
def protect_against_forgery?
false
end
end
class AjaxHelperTest < AjaxHelperBaseTest
def _evaluate_assigns_and_ivars() end
def setup
@record = @author = Author.new
@article = Article.new
super
end
test "link_to_remote" do
assert_dom_equal %(<a class=\"fine\" href=\"#\" data-remote=\"true\" data-url=\"/whatnot\">Remove Author</a>),
link_to_remote("Remove Author", { :url => { :action => "whatnot" }}, { :class => "fine" })
assert_dom_equal %(<a href=\"#\" data-remote=\"true\" data-url=\"/whatnot\" data-oncomplete=\"alert(request.responseText)\">Remove Author</a>),
link_to_remote("Remove Author", :complete => "alert(request.responseText)", :url => { :action => "whatnot" })
assert_dom_equal %(<a href=\"#\" data-remote=\"true\" data-url=\"/whatnot\" data-onsuccess=\"alert(request.responseText)\">Remove Author</a>),
link_to_remote("Remove Author", :success => "alert(request.responseText)", :url => { :action => "whatnot" })
assert_dom_equal %(<a href=\"#\" data-remote=\"true\" data-url=\"/whatnot\" data-onfailure=\"alert(request.responseText)\">Remove Author</a>),
link_to_remote("Remove Author", :failure => "alert(request.responseText)", :url => { :action => "whatnot" })
assert_dom_equal %(<a href=\"#\" data-remote=\"true\" data-url=\"/whatnot?a=10&amp;b=20\" data-onfailure=\"alert(request.responseText)\">Remove Author</a>),
link_to_remote("Remove Author", :failure => "alert(request.responseText)", :url => { :action => "whatnot", :a => '10', :b => '20' })
assert_dom_equal %(<a href=\"#\" data-remote=\"true\" data-url=\"/whatnot\" data-remote-type=\"synchronous\">Remove Author</a>),
link_to_remote("Remove Author", :url => { :action => "whatnot" }, :type => :synchronous)
assert_dom_equal %(<a href=\"#\" data-remote=\"true\" data-url=\"/whatnot\" data-update-position=\"bottom\">Remove Author</a>),
link_to_remote("Remove Author", :url => { :action => "whatnot" }, :position => :bottom)
end
test "link_to_remote with url and oncomplete" do
actual = link_to_remote "undo", :url => { :controller => "words", :action => "undo", :n => 5 }, :complete => "undoRequestCompleted(request)"
expected = '<a href="#" data-url="/words/undo?n=5" data-remote="true" data-oncomplete="undoRequestCompleted(request)">undo</a>'
assert_dom_equal expected, actual
end
test "link_to_remote with delete" do
actual = link_to_remote("Remove Author", { :url => { :action => "whatnot" }, :method => 'delete'}, { :class => "fine" })
expected = '<a class="fine" rel="nofollow" href="#" data-remote="true" data-method="delete" data-url="/whatnot">Remove Author</a>'
assert_dom_equal expected, actual
end
test "link_to_remote using both url and href" do
expected = '<a href="/destroy" data-url="/destroy" data-update-success="posts" data-remote="true">Delete this Post</a>'
assert_dom_equal expected, link_to_remote( "Delete this Post",
{ :update => "posts",
:url => { :action => "destroy" } },
:href => url_for(:action => "destroy"))
end
test "link_to_remote with update-success and url" do
expected = '<a href="#" data-url="/destroy" data-update-success="posts" data-update-failure="error" data-remote="true">Delete this Post</a>'
assert_dom_equal expected, link_to_remote( "Delete this Post", :url => { :action => "destroy"},
:update => { :success => "posts", :failure => "error" })
end
test "link_to_remote with before/after callbacks" do
assert_dom_equal %(<a href=\"#\" data-remote=\"true\" data-url=\"/whatnot\" data-onbefore=\"before();\" data-onafter=\"after();\">Remote outauthor</a>),
link_to_remote("Remote outauthor", :url => { :action => "whatnot" }, :before => "before();", :after => "after();")
end
test "link_to_remote using :with expression" do
expected = %(<a href=\"#\" data-remote=\"true\" data-url=\"/whatnot\" data-with=\"id=123\">Remote outauthor</a>)
assert_dom_equal expected, link_to_remote("Remote outauthor", :url => { :action => "whatnot" }, :with => "id=123")
end
test "link_to_remote using :condition expression" do
expected = %(<a href=\"#\" data-remote=\"true\" data-url=\"/whatnot\" data-condition=\"$('foo').val() == true\">Remote outauthor</a>)
assert_dom_equal expected, link_to_remote("Remote outauthor", :url => { :action => "whatnot" }, :condition => '$(\'foo\').val() == true')
end
test "link_to_remote using explicit :href" do
expected = %(<a href=\"http://www.example.com/testhref\" data-remote=\"true\" data-url=\"/whatnot\" data-condition=\"$('foo').val() == true\">Remote outauthor</a>)
assert_dom_equal expected, link_to_remote("Remote outauthor", {:url => { :action => "whatnot" }, :condition => '$(\'foo\').val() == true'}, :href => 'http://www.example.com/testhref')
end
test "link_to_remote using :submit" do
expected = %(<a href=\"#\" data-remote=\"true\" data-url=\"/whatnot\" data-submit=\"myForm\">Remote outauthor</a>)
assert_dom_equal expected, link_to_remote("Remote outauthor", :url => { :action => "whatnot" }, :submit => 'myForm')
end
test "link_to_remote with method delete" do
assert_dom_equal %(<a class=\"fine\" href=\"#\" data-remote=\"true\" data-url=\"/whatnot\" data-method=\"delete\" rel=\"nofollow\">Remote outauthor</a>),
link_to_remote("Remote outauthor", { :url => { :action => "whatnot" }, :method => "delete"}, { :class => "fine" })
end
test "link_to_remote with method delete as symbol" do
assert_dom_equal %(<a class=\"fine\" href=\"#\" data-remote=\"true\" data-url=\"/whatnot\" data-method=\"delete\" rel=\"nofollow\">Remote outauthor</a>),
link_to_remote("Remote outauthor", { :url => { :action => "whatnot" }, :method => :delete}, { :class => "fine" })
end
test "link_to_remote html options" do
assert_dom_equal %(<a class=\"fine\" href=\"#\" data-remote=\"true\" data-url=\"/whatnot\">Remote outauthor</a>),
link_to_remote("Remote outauthor", { :url => { :action => "whatnot" }, :html => { :class => "fine" } })
end
test "link_to_remote url quote escaping" do
assert_dom_equal %(<a href="#" data-remote=\"true\" data-url=\"/whatnot\\\'s\">Remote</a>),
link_to_remote("Remote", { :url => { :action => "whatnot's" } })
end
test "link_to_remote with confirm" do
assert_dom_equal %(<a class=\"fine\" href=\"#\" data-remote=\"true\" data-url=\"/whatnot\" data-method=\"delete\" rel=\"nofollow\" data-confirm="Are you sure?">Remote confirm</a>),
link_to_remote("Remote confirm", { :url => { :action => "whatnot" }, :method => "delete", :confirm => "Are you sure?"}, { :class => "fine" })
end
test "button_to_remote" do
assert_dom_equal %(<input class=\"fine\" type=\"button\" value=\"Remote outpost\" data-remote=\"true\" data-url=\"/whatnot\" />),
button_to_remote("Remote outpost", { :url => { :action => "whatnot" }}, { :class => "fine" })
assert_dom_equal %(<input type=\"button\" value=\"Remote outpost\" data-remote=\"true\" data-url=\"/whatnot\" data-oncomplete=\"alert(request.reponseText)\" />),
button_to_remote("Remote outpost", :complete => "alert(request.reponseText)", :url => { :action => "whatnot" })
assert_dom_equal %(<input type=\"button\" value=\"Remote outpost\" data-remote=\"true\" data-url=\"/whatnot\" data-onsuccess=\"alert(request.reponseText)\" />),
button_to_remote("Remote outpost", :success => "alert(request.reponseText)", :url => { :action => "whatnot" })
assert_dom_equal %(<input type=\"button\" value=\"Remote outpost\" data-remote=\"true\" data-url=\"/whatnot\" data-onfailure=\"alert(request.reponseText)\" />),
button_to_remote("Remote outpost", :failure => "alert(request.reponseText)", :url => { :action => "whatnot" })
assert_dom_equal %(<input type=\"button\" value=\"Remote outpost\" data-remote=\"true\" data-url=\"/whatnot?a=10&amp;b=20\" data-onfailure=\"alert(request.reponseText)\" />),
button_to_remote("Remote outpost", :failure => "alert(request.reponseText)", :url => { :action => "whatnot", :a => '10', :b => '20' })
end
test "button_to_remote with confirm" do
assert_dom_equal %(<input class=\"fine\" type=\"button\" value=\"Remote outpost\" data-remote=\"true\" data-url=\"/whatnot\" data-confirm="Are you sure?" />),
button_to_remote("Remote outpost", { :url => { :action => "whatnot" }, :confirm => "Are you sure?"}, { :class => "fine" })
end
test "button_to_remote with :submit" do
assert_dom_equal %(<input class=\"fine\" type=\"button\" value=\"Remote outpost\" data-remote=\"true\" data-url=\"/whatnot\" data-submit="myForm" />),
button_to_remote("Remote outpost", { :url => { :action => "whatnot" }, :submit => "myForm"}, { :class => "fine" })
end
test "periodically_call_remote" do
expected = "<script data-url='/mehr_bier' data-update-success='schremser_bier' type='application/json' data-frequency='10' data-periodical='true'></script>"
actual = periodically_call_remote(:update => "schremser_bier", :url => { :action => "mehr_bier" })
assert_dom_equal expected, actual
end
test "periodically_call_remote_with_frequency" do
expected = "<script data-periodical='true' type='application/json' data-frequency='2'></script>"
actual = periodically_call_remote(:frequency => 2)
assert_dom_equal expected, actual
end
test "periodically_call_remote_with_function" do
expected = "<script data-periodical=\"true\" type=\"application/json\" data-onobserve=\"alert('test')\" data-frequency='2'></script>"
actual = periodically_call_remote(:frequency => 2, :function => "alert('test')")
assert_dom_equal expected, actual
end
test "periodically_call_remote_with_update" do
actual = periodically_call_remote(:url => { :action => 'get_averages' }, :update => 'avg')
expected = "<script data-periodical='true' data-url='/get_averages' type='application/json' data-update-success='avg' data-frequency='10'></script>"
assert_dom_equal expected, actual
end
test "periodically_call_remote with update success and failure" do
actual = periodically_call_remote(:url => { :action => 'invoice', :id => 1 },:update => { :success => "invoice", :failure => "error" })
expected = "<script data-periodical='true' data-url='/invoice/1' type='application/json' data-update-success='invoice' data-frequency='10' data-update-failure='error'></script>"
assert_dom_equal expected, actual
end
test "periodically_call_remote with frequency and update" do
actual = periodically_call_remote(:url => 'update', :frequency => '20', :update => 'news_block')
expected = "<script data-periodical='true' data-url='update' type='application/json' data-update-success='news_block' data-frequency='20'></script>"
assert_dom_equal expected, actual
end
test "form_remote_tag" do
assert_dom_equal %(<form action=\"/fast\" method=\"post\" data-remote=\"true\" data-update-success=\"glass_of_beer\">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast } )
assert_dom_equal %(<form action=\"/fast\" method=\"post\" data-remote=\"true\" data-update-success=\"glass_of_beer\">),
form_remote_tag(:update => { :success => "glass_of_beer" }, :url => { :action => :fast })
assert_dom_equal %(<form action=\"/fast\" method=\"post\" data-remote=\"true\" data-update-failure=\"glass_of_water\">),
form_remote_tag(:update => { :failure => "glass_of_water" }, :url => { :action => :fast })
assert_dom_equal %(<form action=\"/fast\" method=\"post\" data-remote=\"true\" data-update-success=\"glass_of_beer\" data-update-failure=\"glass_of_water\">),
form_remote_tag(:update => { :success => 'glass_of_beer', :failure => "glass_of_water" }, :url => { :action => :fast })
end
test "form_remote_tag with method" do
assert_dom_equal %(<form action=\"/fast\" method=\"post\" data-remote=\"true\" data-update-success=\"glass_of_beer\"><div style='margin:0;padding:0;display:inline'><input name='_method' type='hidden' value='put' /></div>),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, :html => { :method => :put })
end
test "form_remote_tag with url" do
form_remote_tag(:url => '/posts' ){}
expected = "<form action='/posts' method='post' data-remote='true'></form>"
assert_dom_equal expected, output_buffer
end
test "form_remote_tag with block in erb" do
__in_erb_template = ''
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }) { concat "Hello world!" }
assert_dom_equal %(<form action=\"/fast\" method=\"post\" data-remote=\"true\" data-update-success=\"glass_of_beer\">Hello world!</form>), output_buffer
end
test "remote_form_for with record identification with new record" do
remote_form_for(@record, {:html => { :id => 'create-author' }}) {}
expected = %(<form action='#{authors_path}' data-remote=\"true\" class='new_author' id='create-author' method='post'></form>)
assert_dom_equal expected, output_buffer
end
test "remote_form_for with url" do
remote_form_for(@record, {:html => { :id => 'create-author' }}) {}
expected = "<form action='/authors' data-remote='true' class='new_author' id='create-author' method='post'></form>"
assert_dom_equal expected, output_buffer
end
test "remote_form_for with record identification without html options" do
remote_form_for(@record) {}
expected = %(<form action='#{authors_path}' data-remote=\"true\" class='new_author' method='post' id='new_author'></form>)
assert_dom_equal expected, output_buffer
end
test "remote_form_for with record identification with existing record" do
@record.save
remote_form_for(@record) {}
expected = %(<form action='#{author_path(@record)}' id='edit_author_1' method='post' data-remote=\"true\" class='edit_author'><div style='margin:0;padding:0;display:inline'><input name='_method' type='hidden' value='put' /></div></form>)
assert_dom_equal expected, output_buffer
end
test "remote_form_for with new nested object and an excisting parent" do
@author.save
remote_form_for([@author, @article]) {}
expected = %(<form action='#{author_articles_path(@author)}' data-remote=\"true\" class='new_article' method='post' id='new_article'></form>)
assert_dom_equal expected, output_buffer
end
test "remote_form_for with existing object in list" do
@author.save
@article.save
remote_form_for([@author, @article]) {}
expected = %(<form action='#{author_article_path(@author, @article)}' id='edit_article_#{@article.id}' method='post' data-remote=\"true\" class='edit_article'><div style='margin:0;padding:0;display:inline'><input name='_method' type='hidden' value='put' /></div></form>)
assert_dom_equal expected, output_buffer
end
test "on callbacks" do
callbacks = [:uninitialized, :loading, :loaded, :interactive, :complete, :success, :failure]
callbacks.each do |callback|
assert_dom_equal %(<form action=\"/fast\" method=\"post\" data-remote=\"true\" data-update-success=\"glass_of_beer\" data-on#{callback}=\"monkeys();\">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
assert_dom_equal %(<form action=\"/fast\" method=\"post\" data-remote=\"true\" data-update-success=\"glass_of_beer\" data-on#{callback}=\"monkeys();\">),
form_remote_tag(:update => { :success => "glass_of_beer" }, :url => { :action => :fast }, callback=>"monkeys();")
assert_dom_equal %(<form action=\"/fast\" method=\"post\" data-remote=\"true\" data-update-failure=\"glass_of_beer\" data-on#{callback}=\"monkeys();\">),
form_remote_tag(:update => { :failure => "glass_of_beer" }, :url => { :action => :fast }, callback=>"monkeys();")
assert_dom_equal %(<form action=\"/fast\" method=\"post\" data-remote=\"true\" data-update-failure=\"glass_of_water\" data-update-success=\"glass_of_beer\" data-on#{callback}=\"monkeys();\">),
form_remote_tag(:update => { :success => "glass_of_beer", :failure => "glass_of_water" }, :url => { :action => :fast }, callback=>"monkeys();")
end
#HTTP status codes 200 up to 599 have callbacks
#these should work
100.upto(599) do |callback|
assert_dom_equal %(<form action=\"/fast\" method=\"post\" data-remote=\"true\" data-update-success=\"glass_of_beer\" data-on#{callback}=\"monkeys();\">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
end
#test 200 and 404
assert_dom_equal %(<form action=\"/fast\" method=\"post\" data-remote=\"true\" data-update-success=\"glass_of_beer\" data-on200=\"monkeys();\" data-on404=\"bananas();\">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, 200=>"monkeys();", 404=>"bananas();")
#these shouldn't
1.upto(99) do |callback|
assert_dom_equal %(<form action=\"/fast\" method=\"post\" data-remote=\"true\" data-update-success=\"glass_of_beer\" data-on#{callback}=\"monkeys();\">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
end
600.upto(999) do |callback|
assert_dom_equal %(<form action=\"/fast\" method=\"post\" data-remote=\"true\" data-update-success=\"glass_of_beer\" data-on#{callback}=\"monkeys();\">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
end
#test ultimate combo
assert_dom_equal %(<form data-on404=\"bananas();\" method=\"post\" data-onsuccess=\"s()\" action=\"/fast\" data-oncomplete=\"c();\" data-update-success=\"glass_of_beer\" data-on200=\"monkeys();\" data-onloading=\"c1()\" data-remote=\"true\" data-onfailure=\"f();\">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, :loading => "c1()", :success => "s()", :failure => "f();", :complete => "c();", 200=>"monkeys();", 404=>"bananas();")
end
test "submit_to_remote" do
assert_dom_equal %(<input name=\"More beer!\" type=\"button\" value=\"1000000\" data-url=\"/empty_bottle\" data-remote-submit=\"true\" data-update-success=\"empty_bottle\" />),
submit_to_remote("More beer!", 1_000_000, :url => { :action => 'empty_bottle' }, :update => "empty_bottle")
end
test "submit_to_remote simple" do
expected = "<input name='create_btn' type='button' value='Create' data-remote-submit='true' data-url='/create' />"
actual = submit_to_remote 'create_btn', 'Create', :url => { :action => 'create' }
assert_dom_equal expected, actual
end
test "submit_to_remote with success and failure" do
expected = "<input name='update_btn' data-url='/update' data-remote-submit='true' data-update-failure='fail' data-update-success='succeed' value='Update' type='button' />"
actual = submit_to_remote 'update_btn', 'Update', :url => { :action => 'update' }, :update => { :success => "succeed", :failure => "fail" }
assert_dom_equal expected, actual
end
test "observe_field" do
assert_dom_equal %(<script type=\"text/javascript\" data-observe=\"true\" data-observed=\"glass\" data-frequency=\"300\" type=\"application/json\" data-url=\"/reorder_if_empty\"></script>),
observe_field("glass", :frequency => 5.minutes, :url => { :action => "reorder_if_empty" })
end
test "observe_field with url, frequency, update and with" do
actual = observe_field :suggest, :url => { :action => :find_suggestion }, :frequency => 0.25, :update => :suggest, :with => 'q'
expected = "<script type='text/javascript' data-observe='true' data-observed='suggest' data-frequency='0.25' type='application/json' data-url='/find_suggestion' data-update-success='suggest' data-with='q'></script>"
assert_dom_equal actual, expected
end
test "observe_field default frequency" do
actual = observe_field :suggest
expected = "<script type='text/javascript' data-observe='true' data-observed='suggest' data-frequency='10' type='application/json'></script>"
assert_dom_equal actual, expected
end
test "observe_field using with option" do
expected = %(<script type=\"text/javascript\" data-observe=\"true\" data-observed=\"glass\" data-frequency=\"300\" type=\"application/json\" data-url=\"/check_value\" data-with=\"id=123\"></script>)
assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => 'id=123')
end
test "observe_field using condition option" do
expected = %(<script type=\"text/javascript\" data-observe=\"true\" data-observed=\"glass\" data-frequency=\"300\" type=\"application/json\" data-url=\"/check_value\" data-condition=\"$('foo').val() == true\"></script>)
assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :condition => '$(\'foo\').val() == true')
end
test "observe_field using json in with option" do
expected = %(<script data-with=\"{'id':value}\" data-observed=\"glass\" data-url=\"/check_value\" data-observe=\"true\" type=\"application/json\" data-frequency=\"300\"></script>)
assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => "{'id':value}")
end
test "observe_field using function for callback" do
assert_dom_equal %(<script data-observed=\"glass\" data-observe=\"true\" type=\"application/json\" data-onobserve=\"alert('Element changed')\" data-frequency=\"300\"></script>),
observe_field("glass", :frequency => 5.minutes, :function => "alert('Element changed')")
end
test "observe_form" do
assert_dom_equal %(<script data-observed=\"cart\" data-url=\"/cart_changed\" data-observe=\"true\" type=\"application/json\" data-frequency=\"2\"></script>),
observe_form("cart", :frequency => 2, :url => { :action => "cart_changed" })
end
test "observe_form using function for callback" do
assert_dom_equal %(<script data-observed=\"cart\" data-observe=\"true\" type=\"application/json\" data-onobserve=\"alert('Form changed')\" data-frequency=\"2\"></script>),
observe_form("cart", :frequency => 2, :function => "alert('Form changed')")
end
test "observe_field without frequency" do
assert_dom_equal %(<script data-observed=\"glass\" data-observe=\"true\" type=\"application/json\" data-frequency='10'></script>),
observe_field("glass")
end
protected
def author_path(record)
"/authors/#{record.id}"
end
def authors_path
"/authors"
end
def author_articles_path(author)
"/authors/#{author.id}/articles"
end
def author_article_path(author, article)
"/authors/#{author.id}/articles/#{article.id}"
end
end

View File

@ -86,11 +86,11 @@ class AssetTagHelperTest < ActionView::TestCase
%(javascript_include_tag("bank.js")) => %(<script src="/javascripts/bank.js" type="text/javascript"></script>),
%(javascript_include_tag("bank", :lang => "vbscript")) => %(<script lang="vbscript" src="/javascripts/bank.js" type="text/javascript"></script>),
%(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(<script src="/javascripts/common.javascript" type="text/javascript"></script>\n<script src="/elsewhere/cools.js" type="text/javascript"></script>),
%(javascript_include_tag(:defaults)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
%(javascript_include_tag(:defaults)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
%(javascript_include_tag(:all)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>),
%(javascript_include_tag(:all, :recursive => true)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/subdir/subdir.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>),
%(javascript_include_tag(:defaults, "bank")) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
%(javascript_include_tag("bank", :defaults)) => %(<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
%(javascript_include_tag(:defaults, "bank")) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
%(javascript_include_tag("bank", :defaults)) => %(<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
%(javascript_include_tag("http://example.com/all")) => %(<script src="http://example.com/all" type="text/javascript"></script>),
%(javascript_include_tag("http://example.com/all.js")) => %(<script src="http://example.com/all.js" type="text/javascript"></script>),
@ -235,7 +235,7 @@ class AssetTagHelperTest < ActionView::TestCase
def test_javascript_include_tag_with_given_asset_id
ENV["RAILS_ASSET_ID"] = "1"
assert_dom_equal(%(<script src="/javascripts/prototype.js?1" type="text/javascript"></script>\n<script src="/javascripts/effects.js?1" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js?1" type="text/javascript"></script>\n<script src="/javascripts/controls.js?1" type="text/javascript"></script>\n<script src="/javascripts/application.js?1" type="text/javascript"></script>), javascript_include_tag(:defaults))
assert_dom_equal(%(<script src="/javascripts/prototype.js?1" type="text/javascript"></script>\n<script src="/javascripts/effects.js?1" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js?1" type="text/javascript"></script>\n<script src="/javascripts/controls.js?1" type="text/javascript"></script>\n<script src="/javascripts/rails.js?1" type="text/javascript"></script>\n<script src="/javascripts/application.js?1" type="text/javascript"></script>), javascript_include_tag(:defaults))
end
def test_javascript_include_tag_is_html_safe
@ -246,14 +246,14 @@ class AssetTagHelperTest < ActionView::TestCase
def test_register_javascript_include_default
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'bank'
assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
end
def test_register_javascript_include_default_mixed_defaults
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'bank'
ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'robber', '/elsewhere/cools.js'
assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/elsewhere/cools.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/elsewhere/cools.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
end
def test_custom_javascript_expansions
@ -265,7 +265,7 @@ class AssetTagHelperTest < ActionView::TestCase
def test_custom_javascript_expansions_and_defaults_puts_application_js_at_the_end
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :robbery => ["bank", "robber"]
assert_dom_equal %(<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag('controls',:defaults, :robbery, 'effects')
assert_dom_equal %(<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag('controls',:defaults, :robbery, 'effects')
end
def test_custom_javascript_expansions_with_undefined_symbol
@ -965,6 +965,5 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
def test_assert_css_and_js_of_the_same_name_return_correct_extension
assert_dom_equal(%(/collaboration/hieraki/javascripts/foo.js), javascript_path("foo"))
assert_dom_equal(%(/collaboration/hieraki/stylesheets/foo.css), stylesheet_path("foo"))
end
end

View File

@ -1230,26 +1230,6 @@ class FormHelperTest < ActionView::TestCase
end
# Perhaps this test should be moved to prototype helper tests.
def test_remote_form_for_with_labelled_builder
self.extend ActionView::Helpers::AjaxHelper
remote_form_for(:post, @post, :builder => LabelledFormBuilder) do |f|
concat f.text_field(:title)
concat f.text_area(:body)
concat f.check_box(:secret)
end
expected =
%(<form action="http://www.example.com" data-remote="true" method="post">) +
"<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
"<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
"<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" +
"</form>"
assert_dom_equal expected, output_buffer
end
def test_fields_for_with_labelled_builder
fields_for(:post, @post, :builder => LabelledFormBuilder) do |f|
concat f.text_field(:title)
@ -1396,15 +1376,6 @@ class FormHelperTest < ActionView::TestCase
assert_equal expected, output_buffer
end
def test_remote_form_for_with_html_options_adds_options_to_form_tag
self.extend ActionView::Helpers::AjaxHelper
remote_form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end
expected = "<form action=\"http://www.example.com\" class=\"some_class\" id=\"some_form\" method=\"post\" data-remote=\"true\"></form>"
assert_dom_equal expected, output_buffer
end
protected
def comments_path(post)
"/posts/#{post.id}/comments"

View File

@ -285,8 +285,8 @@ class FormTagHelperTest < ActionView::TestCase
def test_submit_tag
assert_dom_equal(
%(<input name='commit' data-disable-with="Saving..." onclick="alert('hello!');" type="submit" value="Save" />),
submit_tag("Save", :disable_with => "Saving...", :onclick => "alert('hello!');")
%(<input name='commit' data-disable-with="Saving..." onclick="alert('hello!')" type="submit" value="Save" />),
submit_tag("Save", :disable_with => "Saving...", :onclick => "alert('hello!')")
)
end
@ -299,21 +299,21 @@ class FormTagHelperTest < ActionView::TestCase
def test_submit_tag_with_confirmation
assert_dom_equal(
%(<input name='commit' type='submit' value='Save' data-confirm="Are you sure?"/>),
%(<input name='commit' type='submit' value='Save' data-confirm="Are you sure?" />),
submit_tag("Save", :confirm => "Are you sure?")
)
end
def test_submit_tag_with_confirmation_and_with_disable_with
assert_dom_equal(
%(<input name="commit" data-confirm="Are you sure?" data-disable-with="Saving..." type="submit" value="Save" />),
%(<input name="commit" data-disable-with="Saving..." data-confirm="Are you sure?" type="submit" value="Save" />),
submit_tag("Save", :disable_with => "Saving...", :confirm => "Are you sure?")
)
end
def test_image_submit_tag_with_confirmation
assert_dom_equal(
%(<input type="image" src="/images/save.gif" data-confirm="Are you sure?"/>),
%(<input type="image" src="/images/save.gif" data-confirm="Are you sure?" />),
image_submit_tag("save.gif", :confirm => "Are you sure?")
)
end

View File

@ -30,35 +30,6 @@ class JavaScriptHelperTest < ActionView::TestCase
assert_equal %(dont <\\/close> tags), escape_javascript(%(dont </close> tags))
end
def test_link_to_function
assert_dom_equal %(<a href="#" onclick="alert('Hello world!'); return false;">Greeting</a>),
link_to_function("Greeting", "alert('Hello world!')")
end
def test_link_to_function_with_existing_onclick
assert_dom_equal %(<a href="#" onclick="confirm('Sanity!'); alert('Hello world!'); return false;">Greeting</a>),
link_to_function("Greeting", "alert('Hello world!')", :onclick => "confirm('Sanity!')")
end
def test_link_to_function_with_rjs_block
html = link_to_function( "Greet me!" ) do |page|
page.replace_html 'header', "<h1>Greetings</h1>"
end
assert_dom_equal %(<a href="#" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E&quot;);; return false;">Greet me!</a>), html
end
def test_link_to_function_with_rjs_block_and_options
html = link_to_function( "Greet me!", :class => "updater" ) do |page|
page.replace_html 'header', "<h1>Greetings</h1>"
end
assert_dom_equal %(<a href="#" class="updater" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E&quot;);; return false;">Greet me!</a>), html
end
def test_link_to_function_with_href
assert_dom_equal %(<a href="http://example.com/" onclick="alert('Hello world!'); return false;">Greeting</a>),
link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/')
end
def test_button_to_function
assert_dom_equal %(<input type="button" onclick="alert('Hello world!');" value="Greeting" />),
button_to_function("Greeting", "alert('Hello world!')")

View File

@ -101,7 +101,6 @@ class PrototypeHelperTest < PrototypeHelperBaseTest
assert_equal javascript_tag(create_generator(&block).to_s, {:defer => 'true'}), update_page_tag({:defer => 'true'}, &block)
end
protected
def author_path(record)
"/authors/#{record.id}"
@ -466,4 +465,3 @@ return value.reverse();
assert_equal "MyObject.myMethod(function() { $(\"one\").show();\n$(\"two\").hide(); });", @generator.to_s
end
end

View File

@ -174,81 +174,58 @@ class UrlHelperTest < ActionView::TestCase
link_to("Hello", "http://www.example.com", :confirm => "Are you sure?")
)
assert_dom_equal(
"<a href=\"http://www.example.com\" data-confirm=\"You can\'t possibly be sure, can you?\">Hello</a>",
"<a href=\"http://www.example.com\" data-confirm=\"You can't possibly be sure, can you?\">Hello</a>",
link_to("Hello", "http://www.example.com", :confirm => "You can't possibly be sure, can you?")
)
assert_dom_equal(
"<a href=\"http://www.example.com\" data-confirm=\"You can\'t possibly be sure,\n can you?\">Hello</a>",
"<a href=\"http://www.example.com\" data-confirm=\"You can't possibly be sure,\n can you?\">Hello</a>",
link_to("Hello", "http://www.example.com", :confirm => "You can't possibly be sure,\n can you?")
)
end
def test_link_tag_with_popup
def test_link_to_with_remote
assert_dom_equal(
"<a href=\"http://www.example.com\" data-popup=\"true\">Hello</a>",
link_to("Hello", "http://www.example.com", :popup => true)
)
assert_dom_equal(
"<a href=\"http://www.example.com\" data-popup=\"true\">Hello</a>",
link_to("Hello", "http://www.example.com", :popup => 'true')
)
assert_dom_equal(
"<a href=\"http://www.example.com\" data-popup=\"{title: 'window_name', options: 'width=300,height=300'}\">Hello</a>",
link_to("Hello", "http://www.example.com", :popup => ['window_name', 'width=300,height=300'])
)
end
def test_link_tag_with_popup_and_javascript_confirm
assert_dom_equal(
"<a href=\"http://www.example.com\" data-confirm=\"Fo\' sho\'?\" data-popup=\"true\">Hello</a>",
link_to("Hello", "http://www.example.com", { :popup => true, :confirm => "Fo' sho'?" })
)
assert_dom_equal(
"<a href=\"http://www.example.com\" data-confirm=\"Are you serious?\" data-popup=\"{title: 'window_name', options: 'width=300,height=300'}\">Hello</a>",
link_to("Hello", "http://www.example.com", { :popup => ['window_name', 'width=300,height=300'], :confirm => "Are you serious?" })
"<a href=\"http://www.example.com\" data-remote=\"true\">Hello</a>",
link_to("Hello", "http://www.example.com", :remote => true)
)
end
def test_link_tag_using_post_javascript
assert_dom_equal(
"<a href='http://www.example.com' data-url='http://www.example.com' data-method=\"post\">Hello</a>",
"<a href='http://www.example.com' data-method=\"post\" rel=\"nofollow\">Hello</a>",
link_to("Hello", "http://www.example.com", :method => :post)
)
end
def test_link_tag_using_delete_javascript
assert_dom_equal(
"<a href='http://www.example.com' data-url='http://www.example.com' rel=\"nofollow\" data-method=\"delete\">Destroy</a>",
"<a href='http://www.example.com' rel=\"nofollow\" data-method=\"delete\">Destroy</a>",
link_to("Destroy", "http://www.example.com", :method => :delete)
)
end
def test_link_tag_using_delete_javascript_and_href
assert_dom_equal(
"<a href='\#' data-url='http://www.example.com' rel=\"nofollow\" data-method=\"delete\">Destroy</a>",
"<a href='\#' rel=\"nofollow\" data-method=\"delete\">Destroy</a>",
link_to("Destroy", "http://www.example.com", :method => :delete, :href => '#')
)
end
def test_link_tag_using_post_javascript_and_confirm
assert_dom_equal(
"<a href=\"http://www.example.com\" data-url='http://www.example.com' data-method=\"post\" data-confirm=\"Are you serious?\">Hello</a>",
"<a href=\"http://www.example.com\" data-method=\"post\" rel=\"nofollow\" data-confirm=\"Are you serious?\">Hello</a>",
link_to("Hello", "http://www.example.com", :method => :post, :confirm => "Are you serious?")
)
end
def test_link_tag_using_delete_javascript_and_href_and_confirm
assert_dom_equal(
"<a href=\"#\" data-url='http://www.example.com' rel=\"nofollow\" data-method=\"delete\" data-confirm=\"Are you serious?\">Destroy</a>",
"<a href='\#' rel=\"nofollow\" data-confirm=\"Are you serious?\" data-method=\"delete\">Destroy</a>",
link_to("Destroy", "http://www.example.com", :method => :delete, :href => '#', :confirm => "Are you serious?"),
"When specifying url, form should be generated with it, but not this.href"
)
end
def test_link_tag_using_post_javascript_and_popup
assert_raise(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :method => :post, :confirm => "Are you serious?") }
end
def test_link_tag_using_block_in_erb
__in_erb_template = ''

View File

@ -43,7 +43,6 @@ module ActiveModel
autoload :Observer, 'active_model/observing'
autoload :Observing
autoload :Serialization
autoload :StateMachine
autoload :TestCase
autoload :Translation
autoload :VERSION

View File

@ -242,28 +242,35 @@ module ActiveModel
# <li><tt>activemodel.errors.models.admin.blank</tt></li>
# <li><tt>activemodel.errors.models.user.attributes.title.blank</tt></li>
# <li><tt>activemodel.errors.models.user.blank</tt></li>
# <li><tt>activemodel.errors.messages.blank</tt></li>
# <li>any default you provided through the +options+ hash (in the activemodel.errors scope)</li>
# <li><tt>activemodel.errors.messages.blank</tt></li>
# <li><tt>errors.attributes.title.blank</tt></li>
# <li><tt>errors.messages.blank</tt></li>
# </ol>
def generate_message(attribute, message = :invalid, options = {})
message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
defaults = @base.class.lookup_ancestors.map do |klass|
[ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
:"models.#{klass.name.underscore}.#{message}" ]
[ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.attributes.#{attribute}.#{message}",
:"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.#{message}" ]
end
defaults << options.delete(:default)
defaults = defaults.compact.flatten << :"messages.#{message}"
defaults << :"#{@base.class.i18n_scope}.errors.messages.#{message}"
defaults << :"errors.attributes.#{attribute}.#{message}"
defaults << :"errors.messages.#{message}"
defaults.compact!
defaults.flatten!
key = defaults.shift
value = @base.send(:read_attribute_for_validation, attribute)
options = { :default => defaults,
options = {
:default => defaults,
:model => @base.class.model_name.human,
:attribute => @base.class.human_attribute_name(attribute),
:value => value,
:scope => [:errors]
:value => value
}.merge(options)
I18n.translate(key, options)

View File

@ -1,219 +0,0 @@
module ActiveModel
# ActiveModel::StateMachine provides methods that turn your object into a
# finite state machine, able to move from one state to another.
#
# A minimal implementation could be:
#
# class EmailMessage
# include ActiveModel::StateMachine
#
# state_machine do
# state :unread
# state :read
# end
#
# event :open_email do
# transitions :to => :read, :from => :unread
# end
# end
#
# === Examples
#
# class TrafficLight
# include ActiveModel::StateMachine
#
# attr_reader :runners_caught
#
# def initialize
# @runners_caught = 0
# end
#
# state_machine do
# state :red
# state :green
# state :yellow
# state :blink
#
# event :change_color do
# transitions :to => :red, :from => [:yellow],
# :on_transition => :catch_runners
# transitions :to => :green, :from => [:red]
# transitions :to => :yellow, :from => [:green]
# end
#
# event :defect do
# transitions :to => :blink, :from => [:yellow, :red, :green]
# end
#
# event :repair do
# transitions :to => :red, :from => [:blink]
# end
# end
#
# def catch_runners
# @runners_caught += 1
# end
# end
#
# light = TrafficLight.new
# light.current_state # => :red
# light.change_color! # => true
# light.current_state # => :green
# light.green? # => true
# light.change_color! # => true
# light.current_state # => :yellow
# light.red? # => false
# light.change_color! # => true
# light.runners_caught # => 1
#
# * The initial state for TrafficLight is red which is the first state defined.
#
# TrafficLight.state_machine.initial_state # => :red
#
# * Call an event to transition a state machine, e.g. <tt>change_color!</tt>.
# You can call the event with or without the exclamation mark, however, the common Ruby
# idiom is to name methods that directly change the state of the receivier with
# an exclamation mark, so <tt>change_color!</tt> is preferred over <tt>change_color</tt>.
#
# light.current_state #=> :green
# light.change_color! #=> true
# light.current_state #=> :yellow
#
# * On a succesful transition to red (from yellow), the local +catch_runners+
# method is executed
#
# light.current_state #=> :red
# light.change_color! #=> true
# light.runners_caught #=> 1
#
# * The object acts differently depending on its current state, for instance,
# the change_color! method has a different action depending on the current
# color of the light
#
# light.change_color! #=> true
# light.current_state #=> :red
# light.change_color! #=> true
# light.current_state #=> :green
#
# * Get the possible events for a state
#
# TrafficLight.state_machine.events_for(:red) # => [:change_color, :defect]
# TrafficLight.state_machine.events_for(:blink) # => [:repair]
#
# The StateMachine also supports the following features :
#
# * Success callbacks on event transition
#
# event :sample, :success => :we_win do
# ...
# end
#
# * Enter and exit callbacks par state
#
# state :open, :enter => [:alert_twitter, :send_emails], :exit => :alert_twitter
#
# * Guards on transition
#
# event :close do
# # You may only close the store if the safe is locked!!
# transitions :to => :closed, :from => :open, :guard => :safe_locked?
# end
#
# * Setting the initial state
#
# state_machine :initial => :yellow do
# ...
# end
#
# * Named the state machine, to have more than one
#
# class Stated
# include ActiveModel::StateMachine
#
# strate_machine :name => :ontest do
# end
#
# state_machine do
# end
# end
#
# # Get the state of the <tt>:ontest</tt> state machine
# stat.current_state(:ontest)
# # Get the initial state
# Stated.state_machine(:ontest).initial_state
#
# * Changing the state
#
# stat.current_state(:default, :astate) # => :astate
# # But you must give the name of the state machine, here <tt>:default</tt>
#
module StateMachine
autoload :Event, 'active_model/state_machine/event'
autoload :Machine, 'active_model/state_machine/machine'
autoload :State, 'active_model/state_machine/state'
autoload :StateTransition, 'active_model/state_machine/state_transition'
extend ActiveSupport::Concern
class InvalidTransition < Exception
end
module ClassMethods
def inherited(klass)
super
klass.state_machines = state_machines
end
def state_machines
@state_machines ||= {}
end
def state_machines=(value)
@state_machines = value ? value.dup : nil
end
def state_machine(name = nil, options = {}, &block)
if name.is_a?(Hash)
options = name
name = nil
end
name ||= :default
state_machines[name] ||= Machine.new(self, name)
block ? state_machines[name].update(options, &block) : state_machines[name]
end
def define_state_query_method(state_name)
name = "#{state_name}?"
undef_method(name) if method_defined?(name)
class_eval "def #{name}; current_state.to_s == %(#{state_name}) end"
end
end
def current_state(name = nil, new_state = nil, persist = false)
sm = self.class.state_machine(name)
ivar = sm.current_state_variable
if name && new_state
if persist && respond_to?(:write_state)
write_state(sm, new_state)
end
if respond_to?(:write_state_without_persistence)
write_state_without_persistence(sm, new_state)
end
instance_variable_set(ivar, new_state)
else
instance_variable_set(ivar, nil) unless instance_variable_defined?(ivar)
value = instance_variable_get(ivar)
return value if value
if respond_to?(:read_state)
value = instance_variable_set(ivar, read_state(sm))
end
value || sm.initial_state
end
end
end
end

View File

@ -1,62 +0,0 @@
module ActiveModel
module StateMachine
class Event
attr_reader :name, :success
def initialize(machine, name, options = {}, &block)
@machine, @name, @transitions = machine, name, []
if machine
machine.klass.send(:define_method, "#{name}!") do |*args|
machine.fire_event(name, self, true, *args)
end
machine.klass.send(:define_method, name.to_s) do |*args|
machine.fire_event(name, self, false, *args)
end
end
update(options, &block)
end
def fire(obj, to_state = nil, *args)
transitions = @transitions.select { |t| t.from == obj.current_state(@machine ? @machine.name : nil) }
raise InvalidTransition if transitions.size == 0
next_state = nil
transitions.each do |transition|
next if to_state && !Array(transition.to).include?(to_state)
if transition.perform(obj)
next_state = to_state || Array(transition.to).first
transition.execute(obj, *args)
break
end
end
next_state
end
def transitions_from_state?(state)
@transitions.any? { |t| t.from? state }
end
def ==(event)
if event.is_a? Symbol
name == event
else
name == event.name
end
end
def update(options = {}, &block)
if options.key?(:success) then @success = options[:success] end
if block then instance_eval(&block) end
self
end
private
def transitions(trans_opts)
Array(trans_opts[:from]).each do |s|
@transitions << StateTransition.new(trans_opts.merge({:from => s.to_sym}))
end
end
end
end
end

View File

@ -1,75 +0,0 @@
module ActiveModel
module StateMachine
class Machine
attr_writer :initial_state
attr_accessor :states, :events, :state_index
attr_reader :klass, :name
def initialize(klass, name, options = {}, &block)
@klass, @name, @states, @state_index, @events = klass, name, [], {}, {}
update(options, &block)
end
def initial_state
@initial_state ||= (states.first ? states.first.name : nil)
end
def update(options = {}, &block)
if options.key?(:initial) then @initial_state = options[:initial] end
if block then instance_eval(&block) end
self
end
def fire_event(event, record, persist, *args)
state_index[record.current_state(@name)].call_action(:exit, record)
if new_state = @events[event].fire(record, *args)
state_index[new_state].call_action(:enter, record)
if record.respond_to?(event_fired_callback)
record.send(event_fired_callback, record.current_state, new_state)
end
record.current_state(@name, new_state, persist)
record.send(@events[event].success) if @events[event].success
true
else
if record.respond_to?(event_failed_callback)
record.send(event_failed_callback, event)
end
false
end
end
def states_for_select
states.map { |st| [st.display_name, st.name.to_s] }
end
def events_for(state)
events = @events.values.select { |event| event.transitions_from_state?(state) }
events.map! { |event| event.name }
end
def current_state_variable
"@#{@name}_current_state"
end
private
def state(name, options = {})
@states << (state_index[name] ||= State.new(name, :machine => self)).update(options)
end
def event(name, options = {}, &block)
(@events[name] ||= Event.new(self, name)).update(options, &block)
end
def event_fired_callback
@event_fired_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_fired'
end
def event_failed_callback
@event_failed_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_failed'
end
end
end
end

View File

@ -1,47 +0,0 @@
module ActiveModel
module StateMachine
class State
attr_reader :name, :options
def initialize(name, options = {})
@name = name
if machine = options.delete(:machine)
machine.klass.define_state_query_method(name)
end
update(options)
end
def ==(state)
if state.is_a? Symbol
name == state
else
name == state.name
end
end
def call_action(action, record)
action = @options[action]
case action
when Symbol, String
record.send(action)
when Proc
action.call(record)
end
end
def display_name
@display_name ||= name.to_s.gsub(/_/, ' ').capitalize
end
def for_select
[display_name, name.to_s]
end
def update(options = {})
if options.key?(:display) then @display_name = options.delete(:display) end
@options = options
self
end
end
end
end

View File

@ -1,40 +0,0 @@
module ActiveModel
module StateMachine
class StateTransition
attr_reader :from, :to, :options
def initialize(opts)
@from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
@options = opts
end
def perform(obj)
case @guard
when Symbol, String
obj.send(@guard)
when Proc
@guard.call(obj)
else
true
end
end
def execute(obj, *args)
case @on_transition
when Symbol, String
obj.send(@on_transition, *args)
when Proc
@on_transition.call(obj, *args)
end
end
def ==(obj)
@from == obj.from && @to == obj.to
end
def from?(value)
@from == value
end
end
end
end

View File

@ -1,49 +0,0 @@
require 'cases/helper'
class EventTest < ActiveModel::TestCase
def setup
@state_name = :close_order
@success = :success_callback
end
def new_event
@event = ActiveModel::StateMachine::Event.new(nil, @state_name, {:success => @success}) do
transitions :to => :closed, :from => [:open, :received]
end
end
test 'should set the name' do
assert_equal @state_name, new_event.name
end
test 'should set the success option' do
assert_equal @success, new_event.success
end
test 'should create StateTransitions' do
ActiveModel::StateMachine::StateTransition.expects(:new).with(:to => :closed, :from => :open)
ActiveModel::StateMachine::StateTransition.expects(:new).with(:to => :closed, :from => :received)
new_event
end
end
class EventBeingFiredTest < ActiveModel::TestCase
test 'should raise an AASM::InvalidTransition error if the transitions are empty' do
event = ActiveModel::StateMachine::Event.new(nil, :event)
assert_raise ActiveModel::StateMachine::InvalidTransition do
event.fire(nil)
end
end
test 'should return the state of the first matching transition it finds' do
event = ActiveModel::StateMachine::Event.new(nil, :event) do
transitions :to => :closed, :from => [:open, :received]
end
obj = stub
obj.stubs(:current_state).returns(:open)
assert_equal :closed, event.fire(obj)
end
end

View File

@ -1,43 +0,0 @@
require 'cases/helper'
class MachineTestSubject
include ActiveModel::StateMachine
state_machine do
state :open
state :closed
end
state_machine :initial => :foo do
event :shutdown do
transitions :from => :open, :to => :closed
end
event :timeout do
transitions :from => :open, :to => :closed
end
end
state_machine :extra, :initial => :bar do
end
end
class StateMachineMachineTest < ActiveModel::TestCase
test "allows reuse of existing machines" do
assert_equal 2, MachineTestSubject.state_machines.size
end
test "sets #initial_state from :initial option" do
assert_equal :bar, MachineTestSubject.state_machine(:extra).initial_state
end
test "accesses non-default state machine" do
assert_kind_of ActiveModel::StateMachine::Machine, MachineTestSubject.state_machine(:extra)
end
test "finds events for given state" do
events = MachineTestSubject.state_machine.events_for(:open)
assert events.include?(:shutdown)
assert events.include?(:timeout)
end
end

View File

@ -1,72 +0,0 @@
require 'cases/helper'
class StateTestSubject
include ActiveModel::StateMachine
state_machine do
end
end
class StateTest < ActiveModel::TestCase
def setup
@state_name = :astate
@machine = StateTestSubject.state_machine
@options = { :crazy_custom_key => 'key', :machine => @machine }
end
def new_state(options={})
ActiveModel::StateMachine::State.new(@state_name, @options.merge(options))
end
test 'sets the name' do
assert_equal :astate, new_state.name
end
test 'sets the display_name from name' do
assert_equal "Astate", new_state.display_name
end
test 'sets the display_name from options' do
assert_equal "A State", new_state(:display => "A State").display_name
end
test 'sets the options and expose them as options' do
@options.delete(:machine)
assert_equal @options, new_state.options
end
test 'equals a symbol of the same name' do
assert_equal new_state, :astate
end
test 'equals a State of the same name' do
assert_equal new_state, new_state
end
test 'should send a message to the record for an action if the action is present as a symbol' do
state = new_state(:entering => :foo)
record = stub
record.expects(:foo)
state.call_action(:entering, record)
end
test 'should send a message to the record for an action if the action is present as a string' do
state = new_state(:entering => 'foo')
record = stub
record.expects(:foo)
state.call_action(:entering, record)
end
test 'should call a proc, passing in the record for an action if the action is present' do
state = new_state(:entering => Proc.new {|r| r.foobar})
record = stub
record.expects(:foobar)
state.call_action(:entering, record)
end
end

View File

@ -1,84 +0,0 @@
require 'cases/helper'
class StateTransitionTest < ActiveModel::TestCase
test 'should set from, to, and opts attr readers' do
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
st = ActiveModel::StateMachine::StateTransition.new(opts)
assert_equal opts[:from], st.from
assert_equal opts[:to], st.to
assert_equal opts, st.options
end
test 'should pass equality check if from and to are the same' do
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
st = ActiveModel::StateMachine::StateTransition.new(opts)
obj = stub
obj.stubs(:from).returns(opts[:from])
obj.stubs(:to).returns(opts[:to])
assert_equal st, obj
end
test 'should fail equality check if from are not the same' do
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
st = ActiveModel::StateMachine::StateTransition.new(opts)
obj = stub
obj.stubs(:from).returns('blah')
obj.stubs(:to).returns(opts[:to])
assert_not_equal st, obj
end
test 'should fail equality check if to are not the same' do
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
st = ActiveModel::StateMachine::StateTransition.new(opts)
obj = stub
obj.stubs(:from).returns(opts[:from])
obj.stubs(:to).returns('blah')
assert_not_equal st, obj
end
end
class StateTransitionGuardCheckTest < ActiveModel::TestCase
test 'should return true of there is no guard' do
opts = {:from => 'foo', :to => 'bar'}
st = ActiveModel::StateMachine::StateTransition.new(opts)
assert st.perform(nil)
end
test 'should call the method on the object if guard is a symbol' do
opts = {:from => 'foo', :to => 'bar', :guard => :test_guard}
st = ActiveModel::StateMachine::StateTransition.new(opts)
obj = stub
obj.expects(:test_guard)
st.perform(obj)
end
test 'should call the method on the object if guard is a string' do
opts = {:from => 'foo', :to => 'bar', :guard => 'test_guard'}
st = ActiveModel::StateMachine::StateTransition.new(opts)
obj = stub
obj.expects(:test_guard)
st.perform(obj)
end
test 'should call the proc passing the object if the guard is a proc' do
opts = {:from => 'foo', :to => 'bar', :guard => Proc.new {|o| o.test_guard}}
st = ActiveModel::StateMachine::StateTransition.new(opts)
obj = stub
obj.expects(:test_guard)
st.perform(obj)
end
end

View File

@ -1,312 +0,0 @@
require 'cases/helper'
class StateMachineSubject
include ActiveModel::StateMachine
state_machine do
state :open, :exit => :exit
state :closed, :enter => :enter
event :close, :success => :success_callback do
transitions :to => :closed, :from => [:open]
end
event :null do
transitions :to => :closed, :from => [:open], :guard => :always_false
end
end
state_machine :bar do
state :read
state :ended
event :foo do
transitions :to => :ended, :from => [:read]
end
end
def always_false
false
end
def success_callback
end
def enter
end
def exit
end
end
class StateMachineSubjectSubclass < StateMachineSubject
end
class StateMachineClassLevelTest < ActiveModel::TestCase
test 'defines a class level #state_machine method on its including class' do
assert StateMachineSubject.respond_to?(:state_machine)
end
test 'defines a class level #state_machines method on its including class' do
assert StateMachineSubject.respond_to?(:state_machines)
end
test 'class level #state_machine returns machine instance' do
assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machine
end
test 'class level #state_machine returns machine instance with given name' do
assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machine(:default)
end
test 'class level #state_machines returns hash of machine instances' do
assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machines[:default]
end
test "should return a select friendly array of states in the form of [['Friendly name', 'state_name']]" do
assert_equal [['Open', 'open'], ['Closed', 'closed']], StateMachineSubject.state_machine.states_for_select
end
end
class StateMachineInstanceLevelTest < ActiveModel::TestCase
def setup
@foo = StateMachineSubject.new
end
test 'defines an accessor for the current state' do
assert @foo.respond_to?(:current_state)
end
test 'defines a state querying instance method on including class' do
assert @foo.respond_to?(:open?)
end
test 'defines an event! instance method' do
assert @foo.respond_to?(:close!)
end
test 'defines an event instance method' do
assert @foo.respond_to?(:close)
end
end
class StateMachineInitialStatesTest < ActiveModel::TestCase
def setup
@foo = StateMachineSubject.new
end
test 'sets the initial state' do
assert_equal :open, @foo.current_state
end
test '#open? should be initially true' do
assert @foo.open?
end
test '#closed? should be initially false' do
assert !@foo.closed?
end
test 'uses the first state defined if no initial state is given' do
assert_equal :read, @foo.current_state(:bar)
end
end
class StateMachineEventFiringWithPersistenceTest < ActiveModel::TestCase
def setup
@subj = StateMachineSubject.new
end
test 'updates the current state' do
@subj.close!
assert_equal :closed, @subj.current_state
end
test 'fires the Event' do
@subj.class.state_machine.events[:close].expects(:fire).with(@subj)
@subj.close!
end
test 'calls the success callback if one was provided' do
@subj.expects(:success_callback)
@subj.close!
end
test 'attempts to persist if write_state is defined' do
def @subj.write_state
end
@subj.expects(:write_state)
@subj.close!
end
end
class StateMachineEventFiringWithoutPersistence < ActiveModel::TestCase
test 'updates the current state' do
subj = StateMachineSubject.new
assert_equal :open, subj.current_state
subj.close
assert_equal :closed, subj.current_state
end
test 'fires the Event' do
subj = StateMachineSubject.new
StateMachineSubject.state_machine.events[:close].expects(:fire).with(subj)
subj.close
end
test 'attempts to persist if write_state is defined' do
subj = StateMachineSubject.new
def subj.write_state
end
subj.expects(:write_state_without_persistence)
subj.close
end
end
class StateMachinePersistenceTest < ActiveModel::TestCase
test 'reads the state if it has not been set and read_state is defined' do
subj = StateMachineSubject.new
def subj.read_state
end
subj.expects(:read_state).with(StateMachineSubject.state_machine)
subj.current_state
end
end
class StateMachineEventCallbacksTest < ActiveModel::TestCase
test 'should call aasm_event_fired if defined and successful for bang fire' do
subj = StateMachineSubject.new
def subj.aasm_event_fired(from, to)
end
subj.expects(:event_fired)
subj.close!
end
test 'should call aasm_event_fired if defined and successful for non-bang fire' do
subj = StateMachineSubject.new
def subj.aasm_event_fired(from, to)
end
subj.expects(:event_fired)
subj.close
end
test 'should call aasm_event_failed if defined and transition failed for bang fire' do
subj = StateMachineSubject.new
def subj.event_failed(event)
end
subj.expects(:event_failed)
subj.null!
end
test 'should call aasm_event_failed if defined and transition failed for non-bang fire' do
subj = StateMachineSubject.new
def subj.aasm_event_failed(event)
end
subj.expects(:event_failed)
subj.null
end
end
class StateMachineStateActionsTest < ActiveModel::TestCase
test "calls enter when entering state" do
subj = StateMachineSubject.new
subj.expects(:enter)
subj.close
end
test "calls exit when exiting state" do
subj = StateMachineSubject.new
subj.expects(:exit)
subj.close
end
end
class StateMachineInheritanceTest < ActiveModel::TestCase
test "has the same states as its parent" do
assert_equal StateMachineSubject.state_machine.states, StateMachineSubjectSubclass.state_machine.states
end
test "has the same events as its parent" do
assert_equal StateMachineSubject.state_machine.events, StateMachineSubjectSubclass.state_machine.events
end
end
class StateMachineSubject
state_machine :chetan_patil, :initial => :sleeping do
state :sleeping
state :showering
state :working
state :dating
event :wakeup do
transitions :from => :sleeping, :to => [:showering, :working]
end
event :dress do
transitions :from => :sleeping, :to => :working, :on_transition => :wear_clothes
transitions :from => :showering, :to => [:working, :dating], :on_transition => Proc.new { |obj, *args| obj.wear_clothes(*args) }
end
end
def wear_clothes(shirt_color, trouser_type)
end
end
class StateMachineWithComplexTransitionsTest < ActiveModel::TestCase
def setup
@subj = StateMachineSubject.new
end
test 'transitions to specified next state (sleeping to showering)' do
@subj.wakeup! :showering
assert_equal :showering, @subj.current_state(:chetan_patil)
end
test 'transitions to specified next state (sleeping to working)' do
@subj.wakeup! :working
assert_equal :working, @subj.current_state(:chetan_patil)
end
test 'transitions to default (first or showering) state' do
@subj.wakeup!
assert_equal :showering, @subj.current_state(:chetan_patil)
end
test 'transitions to default state when on_transition invoked' do
@subj.dress!(nil, 'purple', 'dressy')
assert_equal :working, @subj.current_state(:chetan_patil)
end
test 'calls on_transition method with args' do
@subj.wakeup! :showering
@subj.expects(:wear_clothes).with('blue', 'jeans')
@subj.dress! :working, 'blue', 'jeans'
end
test 'calls on_transition proc' do
@subj.wakeup! :showering
@subj.expects(:wear_clothes).with('purple', 'slacks')
@subj.dress!(:dating, 'purple', 'slacks')
end
end

View File

@ -254,7 +254,7 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_confirmation_of w/o mocha
def test_validates_confirmation_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:confirmation => 'custom message'}}}}}
I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:confirmation => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {:confirmation => 'global message'}}
Person.validates_confirmation_of :title
@ -275,7 +275,7 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_acceptance_of w/o mocha
def test_validates_acceptance_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:accepted => 'custom message'}}}}}
I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:accepted => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {:accepted => 'global message'}}
Person.validates_acceptance_of :title, :allow_nil => false
@ -294,7 +294,7 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_presence_of w/o mocha
def test_validates_presence_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:blank => 'custom message'}}}}}
I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:blank => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {:blank => 'global message'}}
Person.validates_presence_of :title
@ -313,7 +313,7 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_length_of :within w/o mocha
def test_validates_length_of_within_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:too_short => 'custom message'}}}}}
I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:too_short => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {:too_short => 'global message'}}
Person.validates_length_of :title, :within => 3..5
@ -332,7 +332,7 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_length_of :is w/o mocha
def test_validates_length_of_is_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}
I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {:wrong_length => 'global message'}}
Person.validates_length_of :title, :is => 5
@ -351,7 +351,7 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_format_of w/o mocha
def test_validates_format_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:invalid => 'custom message'}}}}}
I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:invalid => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}}
Person.validates_format_of :title, :with => /^[1-9][0-9]*$/
@ -370,7 +370,7 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_inclusion_of w/o mocha
def test_validates_inclusion_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:inclusion => 'custom message'}}}}}
I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:inclusion => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {:inclusion => 'global message'}}
Person.validates_inclusion_of :title, :in => %w(a b c)
@ -389,7 +389,7 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_exclusion_of w/o mocha
def test_validates_exclusion_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:exclusion => 'custom message'}}}}}
I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:exclusion => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {:exclusion => 'global message'}}
Person.validates_exclusion_of :title, :in => %w(a b c)
@ -410,7 +410,7 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_numericality_of without :only_integer w/o mocha
def test_validates_numericality_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}
I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {:not_a_number => 'global message'}}
Person.validates_numericality_of :title
@ -431,7 +431,7 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_numericality_of with :only_integer w/o mocha
def test_validates_numericality_of_only_integer_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}
I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {:not_a_number => 'global message'}}
Person.validates_numericality_of :title, :only_integer => true
@ -452,7 +452,7 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_numericality_of :odd w/o mocha
def test_validates_numericality_of_odd_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:odd => 'custom message'}}}}}
I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:odd => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {:odd => 'global message'}}
Person.validates_numericality_of :title, :only_integer => true, :odd => true
@ -473,7 +473,7 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_numericality_of :less_than w/o mocha
def test_validates_numericality_of_less_than_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:less_than => 'custom message'}}}}}
I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:less_than => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {:less_than => 'global message'}}
Person.validates_numericality_of :title, :only_integer => true, :less_than => 0
@ -502,7 +502,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_validates_with_message_symbol_must_translate_per_attribute
I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}
I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}}
Person.validates_presence_of :title, :message => :custom_error
@person.title = nil
@person.valid?
@ -510,7 +510,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_validates_with_message_symbol_must_translate_per_model
I18n.backend.store_translations 'en', :errors => {:models => {:person => {:custom_error => "I am a custom error"}}}
I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:custom_error => "I am a custom error"}}}}
Person.validates_presence_of :title, :message => :custom_error
@person.title = nil
@person.valid?

View File

@ -73,7 +73,6 @@ module ActiveRecord
autoload :SchemaDumper
autoload :Serialization
autoload :SessionStore
autoload :StateMachine
autoload :Timestamp
autoload :Transactions
autoload :Validations

View File

@ -1,9 +1,16 @@
en:
errors:
messages:
taken: "has already been taken"
record_invalid: "Validation failed: {{errors}}"
# Append your own errors here or at the model/attributes scope.
# Attributes names common to most models
#attributes:
#created_at: "Created at"
#updated_at: "Updated at"
# ActiveRecord models configuration
activerecord:
errors:
messages:
taken: "has already been taken"
record_invalid: "Validation failed: {{errors}}"
# Append your own errors here or at the model/attributes scope.
# You can define own errors for models or model attributes.
# The values :model, :attribute and :value are always available for interpolation.
@ -19,13 +26,6 @@ en:
# custom blank validation message for login attribute of User model.
#models:
# Attributes names common to most models
#attributes:
#created_at: "Created at"
#updated_at: "Updated at"
# ActiveRecord models configuration
#activerecord:
# Translate model names. Used in Model.human_name().
#models:
# For example,

View File

@ -50,7 +50,7 @@ module ActiveRecord
end
# Setup database middleware after initializers have run
initializer "active_record.initialize_database_middleware" do |app|
initializer "active_record.initialize_database_middleware", :after => "action_controller.set_configs" do |app|
middleware = app.config.middleware
if middleware.include?("ActiveRecord::SessionStore")
middleware.insert_before "ActiveRecord::SessionStore", ActiveRecord::ConnectionAdapters::ConnectionManagement
@ -77,17 +77,5 @@ module ActiveRecord
end
end
end
initializer "active_record.i18n_deprecation" do
require 'active_support/i18n'
begin
I18n.t(:"activerecord.errors", :raise => true)
warn "[DEPRECATION] \"activerecord.errors\" namespace is deprecated in I18n " <<
"yml files, please use just \"errors\" instead."
rescue Exception => e
# No message then.
end
end
end
end

View File

@ -25,7 +25,7 @@ module ActiveRecord
case value
when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope
values = value.to_a
values.any? ? attribute.in(values) : attribute.eq(nil)
attribute.in(values)
when Range
# TODO : Arel should handle ranges with excluded end.
if value.exclude_end?

View File

@ -1,24 +0,0 @@
module ActiveRecord
module StateMachine #:nodoc:
extend ActiveSupport::Concern
include ActiveModel::StateMachine
included do
before_validation :set_initial_state
validates_presence_of :state
end
protected
def write_state(state_machine, state)
update_attributes! :state => state.to_s
end
def read_state(state_machine)
self.state.to_sym
end
def set_initial_state
self.state ||= self.class.state_machine.initial_state.to_s
end
end
end

View File

@ -10,8 +10,8 @@ module ActiveRecord
attr_reader :record
def initialize(record)
@record = record
errors = @record.errors.full_messages.join(I18n.t('support.array.words_connector', :default => ', '))
super(I18n.t('errors.messages.record_invalid', :errors => errors))
errors = @record.errors.full_messages.join(", ")
super(I18n.t("activerecord.errors.messages.record_invalid", :errors => errors))
end
end

View File

@ -1002,9 +1002,9 @@ module AutosaveAssociationOnACollectionAssociationTests
end
def test_should_default_invalid_error_from_i18n
I18n.backend.store_translations(:en, :errors => { :models =>
I18n.backend.store_translations(:en, :activerecord => {:errors => { :models =>
{ @association_name.to_s.singularize.to_sym => { :blank => "cannot be blank" } }
})
}})
@pirate.send(@association_name).build(:name => '')

View File

@ -1,42 +0,0 @@
require 'cases/helper'
require 'models/traffic_light'
class StateMachineTest < ActiveRecord::TestCase
def setup
@light = TrafficLight.create!
end
test "states initial state" do
assert @light.off?
assert_equal :off, @light.current_state
end
test "transition to a valid state" do
@light.reset
assert @light.red?
assert_equal :red, @light.current_state
@light.green_on
assert @light.green?
assert_equal :green, @light.current_state
end
test "transition does not persist state" do
@light.reset
assert_equal :red, @light.current_state
@light.reload
assert_equal "off", @light.state
end
test "transition does persists state" do
@light.reset!
assert_equal :red, @light.current_state
@light.reload
assert_equal "red", @light.state
end
test "transition to an invalid state" do
assert_raise(ActiveModel::StateMachine::InvalidTransition) { @light.yellow_on }
assert_equal :off, @light.current_state
end
end

View File

@ -50,8 +50,8 @@ class I18nValidationTest < ActiveRecord::TestCase
# validates_uniqueness_of w/o mocha
def test_validates_associated_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :errors => {:models => {:topic => {:attributes => {:title => {:taken => 'custom message'}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {:taken => 'global message'}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:taken => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:taken => 'global message'}}}
Topic.validates_uniqueness_of :title
unique_topic.valid?
@ -59,7 +59,7 @@ class I18nValidationTest < ActiveRecord::TestCase
end
def test_validates_associated_finds_global_default_translation
I18n.backend.store_translations 'en', :errors => {:messages => {:taken => 'global message'}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:taken => 'global message'}}}
Topic.validates_uniqueness_of :title
unique_topic.valid?
@ -83,16 +83,16 @@ class I18nValidationTest < ActiveRecord::TestCase
# validates_associated w/o mocha
def test_validates_associated_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
Topic.validates_associated :replies
replied_topic.valid?
assert_equal ['custom message'], replied_topic.errors[:replies]
assert_equal ['custom message'], replied_topic.errors[:replies].uniq
end
def test_validates_associated_finds_global_default_translation
I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
Topic.validates_associated :replies
replied_topic.valid?

View File

@ -1,27 +0,0 @@
class TrafficLight < ActiveRecord::Base
include ActiveRecord::StateMachine
state_machine do
state :off
state :red
state :green
state :yellow
event :red_on do
transitions :to => :red, :from => [:yellow]
end
event :green_on do
transitions :to => :green, :from => [:red]
end
event :yellow_on do
transitions :to => :yellow, :from => [:green]
end
event :reset do
transitions :to => :red, :from => [:off]
end
end
end

View File

@ -1,67 +1,41 @@
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/object/metaclass'
class Class
def superclass_delegating_reader(*names)
class_to_stop_searching_on = superclass.name.blank? ? "Object" : superclass.name
options = names.extract_options!
def superclass_delegating_accessor(name, options = {})
# Create private _name and _name= methods that can still be used if the public
# methods are overridden. This allows
_superclass_delegating_accessor("_#{name}")
names.each do |name|
# def self.only_reader
# if defined?(@only_reader)
# @only_reader
# elsif superclass < Object && superclass.respond_to?(:only_reader)
# superclass.only_reader
# end
# end
class_eval <<-EOS, __FILE__, __LINE__ + 1
def self.#{name}
if defined?(@#{name})
@#{name}
elsif superclass < #{class_to_stop_searching_on} && superclass.respond_to?(:#{name})
superclass.#{name}
end
end
EOS
# Generate the public methods name, name=, and name?
# These methods dispatch to the private _name, and _name= methods, making them
# overridable
metaclass.send(:define_method, name) { send("_#{name}") }
metaclass.send(:define_method, "#{name}?") { !!send("_#{name}") }
metaclass.send(:define_method, "#{name}=") { |value| send("_#{name}=", value) }
unless options[:instance_reader] == false
class_eval <<-EOS, __FILE__, __LINE__ + 1
def #{name} # def only_reader
self.class.#{name} # self.class.only_reader
end # end
def self.#{name}? # def self.only_reader?
!!#{name} # !!only_reader
end # end
def #{name}? # def only_reader?
!!#{name} # !!only_reader
end # end
EOS
end
# If an instance_reader is needed, generate methods for name and name= on the
# class itself, so instances will be able to see them
define_method(name) { send("_#{name}") } if options[:instance_reader] != false
define_method("#{name}?") { !!send("#{name}") } if options[:instance_reader] != false
end
private
# Take the object being set and store it in a method. This gives us automatic
# inheritance behavior, without having to store the object in an instance
# variable and look up the superclass chain manually.
def _stash_object_in_method(object, method, instance_reader = true)
metaclass.send(:define_method, method) { object }
define_method(method) { object } if instance_reader
end
def _superclass_delegating_accessor(name, options = {})
metaclass.send(:define_method, "#{name}=") do |value|
_stash_object_in_method(value, name, options[:instance_reader] != false)
end
self.send("#{name}=", nil)
end
def superclass_delegating_writer(*names, &block)
options = names.extract_options!
names.each do |name|
class_eval <<-EOS, __FILE__, __LINE__ + 1
def self.#{name}=(value) # def self.property=(value)
@#{name} = value # @property = value
end # end
EOS
self.send(:"#{name}=", yield) if block_given?
end
end
# These class attributes behave something like the class
# inheritable accessors. But instead of copying the hash over at
# the time the subclass is first defined, the accessors simply
# delegate to their superclass unless they have been given a
# specific value. This stops the strange situation where values
# set after class definition don't get applied to subclasses.
def superclass_delegating_accessor(*names, &block)
superclass_delegating_reader(*names)
superclass_delegating_writer(*names, &block)
end
end

View File

@ -21,27 +21,6 @@ class DelegatingAttributesTest < Test::Unit::TestCase
@single_class = Class.new(Object)
end
def test_simple_reader_declaration
single_class.superclass_delegating_reader :only_reader
# The class and instance should have an accessor, but there
# should be no mutator
assert single_class.respond_to?(:only_reader)
assert single_class.respond_to?(:only_reader?)
assert single_class.public_instance_methods.map(&:to_s).include?("only_reader")
assert single_class.public_instance_methods.map(&:to_s).include?("only_reader?")
assert !single_class.respond_to?(:only_reader=)
end
def test_simple_writer_declaration
single_class.superclass_delegating_writer :only_writer
# The class should have a mutator, the instances shouldn't
# neither should have an accessor
assert single_class.respond_to?(:only_writer=)
assert !single_class.public_instance_methods.include?("only_writer=")
assert !single_class.public_instance_methods.include?("only_writer")
assert !single_class.respond_to?(:only_writer)
end
def test_simple_accessor_declaration
single_class.superclass_delegating_accessor :both
# Class should have accessor and mutator
@ -74,19 +53,6 @@ class DelegatingAttributesTest < Test::Unit::TestCase
assert_equal false, single_class.both?
end
def test_working_with_accessors
single_class.superclass_delegating_reader :only_reader
single_class.instance_variable_set("@only_reader", "reading only")
assert_equal "reading only", single_class.only_reader
assert_equal "reading only", single_class.new.only_reader
end
def test_working_with_simple_mutators
single_class.superclass_delegating_writer :only_writer
single_class.only_writer="written"
assert_equal "written", single_class.instance_variable_get("@only_writer")
end
def test_child_class_delegates_to_parent_but_can_be_overridden
parent = Class.new
parent.superclass_delegating_accessor :both

View File

@ -64,6 +64,10 @@ task :generate_guides do
ruby "guides/rails_guides.rb"
end
task :update_prototype_ujs do
system "curl http://github.com/rails/prototype-ujs/raw/master/src/rails.js > lib/generators/rails/app/templates/public/javascripts/rails.js"
end
# Generate documentation ------------------------------------------------------------------
Rake::RDocTask.new { |rdoc|

View File

@ -1,152 +0,0 @@
/*!
* jQuery JavaScript Library v1.4.1
* http://jquery.com/
*
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* Includes Sizzle.js
* http://sizzlejs.com/
* Copyright 2010, The Dojo Foundation
* Released under the MIT, BSD, and GPL Licenses.
*
* Date: Mon Jan 25 19:43:33 2010 -0500
*/
(function(z,v){function la(){if(!c.isReady){try{r.documentElement.doScroll("left")}catch(a){setTimeout(la,1);return}c.ready()}}function Ma(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,i){var j=a.length;if(typeof b==="object"){for(var n in b)X(a,n,b[n],f,e,d);return a}if(d!==v){f=!i&&f&&c.isFunction(d);for(n=0;n<j;n++)e(a[n],b,f?d.call(a[n],n,e(a[n],b)):d,i);return a}return j?
e(a[0],b):null}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function ma(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function na(a){var b,d=[],f=[],e=arguments,i,j,n,o,m,s,x=c.extend({},c.data(this,"events").live);if(!(a.button&&a.type==="click")){for(o in x){j=x[o];if(j.live===a.type||j.altLive&&c.inArray(a.type,j.altLive)>-1){i=j.data;i.beforeFilter&&i.beforeFilter[a.type]&&!i.beforeFilter[a.type](a)||f.push(j.selector)}else delete x[o]}i=c(a.target).closest(f,
a.currentTarget);m=0;for(s=i.length;m<s;m++)for(o in x){j=x[o];n=i[m].elem;f=null;if(i[m].selector===j.selector){if(j.live==="mouseenter"||j.live==="mouseleave")f=c(a.relatedTarget).closest(j.selector)[0];if(!f||f!==n)d.push({elem:n,fn:j})}}m=0;for(s=d.length;m<s;m++){i=d[m];a.currentTarget=i.elem;a.data=i.fn.data;if(i.fn.apply(i.elem,e)===false){b=false;break}}return b}}function oa(a,b){return"live."+(a?a+".":"")+b.replace(/\./g,"`").replace(/ /g,"&")}function pa(a){return!a||!a.parentNode||a.parentNode.nodeType===
11}function qa(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var i in f)for(var j in f[i])c.event.add(this,i,f[i][j],f[i][j].data)}}})}function ra(a,b,d){var f,e,i;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&a[0].indexOf("<option")<0&&(c.support.checkClone||!sa.test(a[0]))){e=true;if(i=c.fragments[a[0]])if(i!==1)f=i}if(!f){b=b&&b[0]?b[0].ownerDocument||b[0]:r;f=b.createDocumentFragment();
c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=i?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(ta.concat.apply([],ta.slice(0,b)),function(){d[this]=a});return d}function ua(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Na=z.jQuery,Oa=z.$,r=z.document,S,Pa=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Qa=/^.[^:#\[\.,]*$/,Ra=/\S/,Sa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Ta=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,O=navigator.userAgent,
va=false,P=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,Q=Array.prototype.slice,wa=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(typeof a==="string")if((d=Pa.exec(a))&&(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:r;if(a=Ta.exec(a))if(c.isPlainObject(b)){a=[r.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=ra([d[1]],
[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}}else{if(b=r.getElementById(d[2])){if(b.id!==d[2])return S.find(a);this.length=1;this[0]=b}this.context=r;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=r;a=r.getElementsByTagName(a)}else return!b||b.jquery?(b||S).find(a):c(b).find(a);else if(c.isFunction(a))return S.ready(a);if(a.selector!==v){this.selector=a.selector;this.context=a.context}return c.isArray(a)?this.setArray(a):c.makeArray(a,
this)},selector:"",jquery:"1.4.1",length:0,size:function(){return this.length},toArray:function(){return Q.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){a=c(a||null);a.prevObject=this;a.context=this.context;if(b==="find")a.selector=this.selector+(this.selector?" ":"")+d;else if(b)a.selector=this.selector+"."+b+"("+d+")";return a},setArray:function(a){this.length=0;ba.apply(this,a);return this},each:function(a,b){return c.each(this,
a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(r,c);else P&&P.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(Q.apply(this,arguments),"slice",Q.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};
c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,i,j,n;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(i in e){j=a[i];n=e[i];if(a!==n)if(f&&n&&(c.isPlainObject(n)||c.isArray(n))){j=j&&(c.isPlainObject(j)||c.isArray(j))?j:c.isArray(n)?[]:{};a[i]=c.extend(f,j,n)}else if(n!==v)a[i]=n}return a};c.extend({noConflict:function(a){z.$=
Oa;if(a)z.jQuery=Na;return c},isReady:false,ready:function(){if(!c.isReady){if(!r.body)return setTimeout(c.ready,13);c.isReady=true;if(P){for(var a,b=0;a=P[b++];)a.call(r,c);P=null}c.fn.triggerHandler&&c(r).triggerHandler("ready")}},bindReady:function(){if(!va){va=true;if(r.readyState==="complete")return c.ready();if(r.addEventListener){r.addEventListener("DOMContentLoaded",L,false);z.addEventListener("load",c.ready,false)}else if(r.attachEvent){r.attachEvent("onreadystatechange",L);z.attachEvent("onload",
c.ready);var a=false;try{a=z.frameElement==null}catch(b){}r.documentElement.doScroll&&a&&la()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,"isPrototypeOf"))return false;var b;for(b in a);return b===v||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;
return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return z.JSON&&z.JSON.parse?z.JSON.parse(a):(new Function("return "+a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Ra.test(a)){var b=r.getElementsByTagName("head")[0]||
r.documentElement,d=r.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(r.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,i=a.length,j=i===v||c.isFunction(a);if(d)if(j)for(f in a){if(b.apply(a[f],d)===false)break}else for(;e<i;){if(b.apply(a[e++],d)===false)break}else if(j)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=
a[0];e<i&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Sa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==
v;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,i=a.length;e<i;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,i=0,j=a.length;i<j;i++){e=b(a[i],i,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=v}else if(b&&!c.isFunction(b)){d=b;b=v}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},
uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});O=c.uaMatch(O);if(O.browser){c.browser[O.browser]=true;c.browser.version=O.version}if(c.browser.webkit)c.browser.safari=true;if(wa)c.inArray=function(a,b){return wa.call(b,a)};S=c(r);if(r.addEventListener)L=function(){r.removeEventListener("DOMContentLoaded",
L,false);c.ready()};else if(r.attachEvent)L=function(){if(r.readyState==="complete"){r.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=r.documentElement,b=r.createElement("script"),d=r.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";var e=d.getElementsByTagName("*"),i=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!i)){c.support=
{leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(i.getAttribute("style")),hrefNormalized:i.getAttribute("href")==="/a",opacity:/^0.55$/.test(i.style.opacity),cssFloat:!!i.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:r.createElement("select").appendChild(r.createElement("option")).selected,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};
b.type="text/javascript";try{b.appendChild(r.createTextNode("window."+f+"=1;"))}catch(j){}a.insertBefore(b,a.firstChild);if(z[f]){c.support.scriptEval=true;delete z[f]}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function n(){c.support.noCloneEvent=false;d.detachEvent("onclick",n)});d.cloneNode(true).fireEvent("onclick")}d=r.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=r.createDocumentFragment();a.appendChild(d.firstChild);
c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var n=r.createElement("div");n.style.width=n.style.paddingLeft="1px";r.body.appendChild(n);c.boxModel=c.support.boxModel=n.offsetWidth===2;r.body.removeChild(n).style.display="none"});a=function(n){var o=r.createElement("div");n="on"+n;var m=n in o;if(!m){o.setAttribute(n,"return;");m=typeof o[n]==="function"}return m};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=i=null}})();c.props=
{"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ua=0,xa={},Va={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==z?xa:a;var f=a[G],e=c.cache;if(!b&&!f)return null;f||(f=++Ua);if(typeof b==="object"){a[G]=f;e=e[f]=c.extend(true,
{},b)}else e=e[f]?e[f]:typeof d==="undefined"?Va:(e[f]={});if(d!==v){a[G]=f;e[b]=d}return typeof b==="string"?e[b]:e}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==z?xa:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{try{delete a[G]}catch(i){a.removeAttribute&&a.removeAttribute(G)}delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,
a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===v){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===v&&this.length)f=c.data(this[0],a);return f===v&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);
return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===v)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||
a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var ya=/[\n\t]/g,ca=/\s+/,Wa=/\r/g,Xa=/href|src|style/,Ya=/(button|input)/i,Za=/(button|input|object|select|textarea)/i,$a=/^(a|area)$/i,za=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(o){var m=
c(this);m.addClass(a.call(this,o,m.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className)for(var i=" "+e.className+" ",j=0,n=b.length;j<n;j++){if(i.indexOf(" "+b[j]+" ")<0)e.className+=" "+b[j]}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(o){var m=c(this);m.removeClass(a.call(this,o,m.attr("class")))});if(a&&typeof a==="string"||a===v)for(var b=(a||"").split(ca),
d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var i=(" "+e.className+" ").replace(ya," "),j=0,n=b.length;j<n;j++)i=i.replace(" "+b[j]+" "," ");e.className=i.substring(1,i.length-1)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var i=c(this);i.toggleClass(a.call(this,e,i.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,i=0,j=c(this),n=b,o=
a.split(ca);e=o[i++];){n=f?n:!j.hasClass(e);j[n?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(ya," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===v){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||
{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var i=b?d:0;for(d=b?d+1:e.length;i<d;i++){var j=e[i];if(j.selected){a=c(j).val();if(b)return a;f.push(a)}}return f}if(za.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Wa,"")}return v}var n=c.isFunction(a);return this.each(function(o){var m=c(this),s=a;if(this.nodeType===1){if(n)s=a.call(this,o,m.val());
if(typeof s==="number")s+="";if(c.isArray(s)&&za.test(this.type))this.checked=c.inArray(m.val(),s)>=0;else if(c.nodeName(this,"select")){var x=c.makeArray(s);c("option",this).each(function(){this.selected=c.inArray(c(this).val(),x)>=0});if(!x.length)this.selectedIndex=-1}else this.value=s}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return v;if(f&&b in c.attrFn)return c(a)[b](d);
f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==v;b=f&&c.props[b]||b;if(a.nodeType===1){var i=Xa.test(b);if(b in a&&f&&!i){if(e){b==="type"&&Ya.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:Za.test(a.nodeName)||$a.test(a.nodeName)&&a.href?0:v;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=
""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&i?a.getAttribute(b,2):a.getAttribute(b);return a===null?v:a}return c.style(a,b,d)}});var ab=function(a){return a.replace(/[^\w\s\.\|`]/g,function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==z&&!a.frameElement)a=z;if(!d.guid)d.guid=c.guid++;if(f!==v){d=c.proxy(d);d.data=f}var e=c.data(a,"events")||c.data(a,"events",{}),i=c.data(a,"handle"),j;if(!i){j=
function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(j.elem,arguments):v};i=c.data(a,"handle",j)}if(i){i.elem=a;b=b.split(/\s+/);for(var n,o=0;n=b[o++];){var m=n.split(".");n=m.shift();if(o>1){d=c.proxy(d);if(f!==v)d.data=f}d.type=m.slice(0).sort().join(".");var s=e[n],x=this.special[n]||{};if(!s){s=e[n]={};if(!x.setup||x.setup.call(a,f,m,d)===false)if(a.addEventListener)a.addEventListener(n,i,false);else a.attachEvent&&a.attachEvent("on"+n,i)}if(x.add)if((m=x.add.call(a,
d,f,m,s))&&c.isFunction(m)){m.guid=m.guid||d.guid;m.data=m.data||d.data;m.type=m.type||d.type;d=m}s[d.guid]=d;this.global[n]=true}a=null}}},global:{},remove:function(a,b,d){if(!(a.nodeType===3||a.nodeType===8)){var f=c.data(a,"events"),e,i,j;if(f){if(b===v||typeof b==="string"&&b.charAt(0)===".")for(i in f)this.remove(a,i+(b||""));else{if(b.type){d=b.handler;b=b.type}b=b.split(/\s+/);for(var n=0;i=b[n++];){var o=i.split(".");i=o.shift();var m=!o.length,s=c.map(o.slice(0).sort(),ab);s=new RegExp("(^|\\.)"+
s.join("\\.(?:.*\\.)?")+"(\\.|$)");var x=this.special[i]||{};if(f[i]){if(d){j=f[i][d.guid];delete f[i][d.guid]}else for(var A in f[i])if(m||s.test(f[i][A].type))delete f[i][A];x.remove&&x.remove.call(a,o,j);for(e in f[i])break;if(!e){if(!x.teardown||x.teardown.call(a,o)===false)if(a.removeEventListener)a.removeEventListener(i,c.data(a,"handle"),false);else a.detachEvent&&a.detachEvent("on"+i,c.data(a,"handle"));e=null;delete f[i]}}}}for(e in f)break;if(!e){if(A=c.data(a,"handle"))A.elem=null;c.removeData(a,
"events");c.removeData(a,"handle")}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();this.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return v;a.result=v;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,
b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(i){}if(!a.isPropagationStopped()&&f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){d=a.target;var j;if(!(c.nodeName(d,"a")&&e==="click")&&!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()])){try{if(d[e]){if(j=d["on"+e])d["on"+e]=null;this.triggered=true;d[e]()}}catch(n){}if(j)d["on"+e]=j;this.triggered=false}}},handle:function(a){var b,
d;a=arguments[0]=c.event.fix(a||z.event);a.currentTarget=this;d=a.type.split(".");a.type=d.shift();b=!d.length&&!a.exclusive;var f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)");d=(c.data(this,"events")||{})[a.type];for(var e in d){var i=d[e];if(b||f.test(i.type)){a.handler=i;a.data=i.data;i=i.apply(this,arguments);if(i!==v){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||r;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=r.documentElement;d=r.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==v)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a,b){c.extend(a,b||{});a.guid+=b.selector+b.live;b.liveProxy=a;c.event.add(this,b.live,na,b)},remove:function(a){if(a.length){var b=
0,d=new RegExp("(^|\\.)"+a[0]+"(\\.|$)");c.each(c.data(this,"events").live||{},function(){d.test(this.type)&&b++});b<1&&c.event.remove(this,a[0],na)}},special:{}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};
c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y};var Aa=function(a){for(var b=
a.relatedTarget;b&&b!==this;)try{b=b.parentNode}catch(d){break}if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}},Ba=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ba:Aa,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ba:Aa)}}});if(!c.support.submitBubbles)c.event.special.submit={setup:function(a,b,d){if(this.nodeName.toLowerCase()!==
"form"){c.event.add(this,"click.specialSubmit."+d.guid,function(f){var e=f.target,i=e.type;if((i==="submit"||i==="image")&&c(e).closest("form").length)return ma("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit."+d.guid,function(f){var e=f.target,i=e.type;if((i==="text"||i==="password")&&c(e).closest("form").length&&f.keyCode===13)return ma("submit",this,arguments)})}else return false},remove:function(a,b){c.event.remove(this,"click.specialSubmit"+(b?"."+b.guid:""));c.event.remove(this,
"keypress.specialSubmit"+(b?"."+b.guid:""))}};if(!c.support.changeBubbles){var da=/textarea|input|select/i;function Ca(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d}function ea(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Ca(d);if(a.type!=="focusout"||
d.type!=="radio")c.data(d,"_change_data",e);if(!(f===v||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}}c.event.special.change={filters:{focusout:ea,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return ea.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return ea.call(this,a)},beforeactivate:function(a){a=
a.target;a.nodeName.toLowerCase()==="input"&&a.type==="radio"&&c.data(a,"_change_data",Ca(a))}},setup:function(a,b,d){for(var f in T)c.event.add(this,f+".specialChange."+d.guid,T[f]);return da.test(this.nodeName)},remove:function(a,b){for(var d in T)c.event.remove(this,d+".specialChange"+(b?"."+b.guid:""),T[d]);return da.test(this.nodeName)}};var T=c.event.special.change.filters}r.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,
f)}c.event.special[b]={setup:function(){this.addEventListener(a,d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var i in d)this[b](i,f,d[i],e);return this}if(c.isFunction(f)){e=f;f=v}var j=b==="one"?c.proxy(e,function(n){c(this).unbind(n,j);return e.apply(this,arguments)}):e;return d==="unload"&&b!=="one"?this.one(d,f,e):this.each(function(){c.event.add(this,d,j,f)})}});c.fn.extend({unbind:function(a,
b){if(typeof a==="object"&&!a.preventDefault){for(var d in a)this.unbind(d,a[d]);return this}return this.each(function(){c.event.remove(this,a,b)})},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+
a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e){var i,j=0;if(c.isFunction(f)){e=f;f=v}for(d=(d||"").split(/\s+/);(i=d[j++])!=null;){i=i==="focus"?"focusin":i==="blur"?"focusout":i==="hover"?d.push("mouseleave")&&"mouseenter":i;b==="live"?c(this.context).bind(oa(i,this.selector),{data:f,selector:this.selector,
live:i},e):c(this.context).unbind(oa(i,this.selector),e?{guid:e.guid+this.selector+i}:null)}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});z.attachEvent&&!z.addEventListener&&z.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});
(function(){function a(g){for(var h="",k,l=0;g[l];l++){k=g[l];if(k.nodeType===3||k.nodeType===4)h+=k.nodeValue;else if(k.nodeType!==8)h+=a(k.childNodes)}return h}function b(g,h,k,l,q,p){q=0;for(var u=l.length;q<u;q++){var t=l[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===k){y=l[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=k;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}l[q]=y}}}function d(g,h,k,l,q,p){q=0;for(var u=l.length;q<u;q++){var t=l[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===
k){y=l[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=k;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(o.filter(h,[t]).length>0){y=t;break}}t=t[g]}l[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,i=Object.prototype.toString,j=false,n=true;[0,0].sort(function(){n=false;return 0});var o=function(g,h,k,l){k=k||[];var q=h=h||r;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||
typeof g!=="string")return k;for(var p=[],u,t,y,R,H=true,M=w(h),I=g;(f.exec(""),u=f.exec(I))!==null;){I=u[3];p.push(u[1]);if(u[2]){R=u[3];break}}if(p.length>1&&s.exec(g))if(p.length===2&&m.relative[p[0]])t=fa(p[0]+p[1],h);else for(t=m.relative[p[0]]?[h]:o(p.shift(),h);p.length;){g=p.shift();if(m.relative[g])g+=p.shift();t=fa(g,t)}else{if(!l&&p.length>1&&h.nodeType===9&&!M&&m.match.ID.test(p[0])&&!m.match.ID.test(p[p.length-1])){u=o.find(p.shift(),h,M);h=u.expr?o.filter(u.expr,u.set)[0]:u.set[0]}if(h){u=
l?{expr:p.pop(),set:A(l)}:o.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=u.expr?o.filter(u.expr,u.set):u.set;if(p.length>0)y=A(t);else H=false;for(;p.length;){var D=p.pop();u=D;if(m.relative[D])u=p.pop();else D="";if(u==null)u=h;m.relative[D](y,u,M)}}else y=[]}y||(y=t);y||o.error(D||g);if(i.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))k.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&
y[g].nodeType===1&&k.push(t[g]);else k.push.apply(k,y);else A(y,k);if(R){o(R,q,k,l);o.uniqueSort(k)}return k};o.uniqueSort=function(g){if(C){j=n;g.sort(C);if(j)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};o.matches=function(g,h){return o(g,null,null,h)};o.find=function(g,h,k){var l,q;if(!g)return[];for(var p=0,u=m.order.length;p<u;p++){var t=m.order[p];if(q=m.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");l=m.find[t](q,
h,k);if(l!=null){g=g.replace(m.match[t],"");break}}}}l||(l=h.getElementsByTagName("*"));return{set:l,expr:g}};o.filter=function(g,h,k,l){for(var q=g,p=[],u=h,t,y,R=h&&h[0]&&w(h[0]);g&&h.length;){for(var H in m.filter)if((t=m.leftMatch[H].exec(g))!=null&&t[2]){var M=m.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-1)!=="\\"){if(u===p)p=[];if(m.preFilter[H])if(t=m.preFilter[H](t,u,k,p,l,R)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=u[U])!=null;U++)if(D){I=M(D,t,U,u);var Da=
l^!!I;if(k&&I!=null)if(Da)y=true;else u[U]=false;else if(Da){p.push(D);y=true}}if(I!==v){k||(u=p);g=g.replace(m.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)o.error(g);else break;q=g}return u};o.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var m=o.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},relative:{"+":function(g,h){var k=typeof h==="string",l=k&&!/\W/.test(h);k=k&&!l;if(l)h=h.toLowerCase();l=0;for(var q=g.length,
p;l<q;l++)if(p=g[l]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[l]=k||p&&p.nodeName.toLowerCase()===h?p||false:p===h}k&&o.filter(h,g,true)},">":function(g,h){var k=typeof h==="string";if(k&&!/\W/.test(h)){h=h.toLowerCase();for(var l=0,q=g.length;l<q;l++){var p=g[l];if(p){k=p.parentNode;g[l]=k.nodeName.toLowerCase()===h?k:false}}}else{l=0;for(q=g.length;l<q;l++)if(p=g[l])g[l]=k?p.parentNode:p.parentNode===h;k&&o.filter(h,g,true)}},"":function(g,h,k){var l=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=
h=h.toLowerCase();q=b}q("parentNode",h,l,g,p,k)},"~":function(g,h,k){var l=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,l,g,p,k)}},find:{ID:function(g,h,k){if(typeof h.getElementById!=="undefined"&&!k)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var k=[];h=h.getElementsByName(g[1]);for(var l=0,q=h.length;l<q;l++)h[l].getAttribute("name")===g[1]&&k.push(h[l]);return k.length===0?null:k}},
TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,k,l,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var u;(u=h[p])!=null;p++)if(u)if(q^(u.className&&(" "+u.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))k||l.push(u);else if(k)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&
"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,k,l,q,p){h=g[1].replace(/\\/g,"");if(!p&&m.attrMap[h])g[1]=m.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,k,l,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=o(g[3],null,null,h);else{g=o.filter(g[3],h,k,true^q);k||l.push.apply(l,g);return false}else if(m.match.POS.test(g[0])||m.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);
return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,k){return!!o(k[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===
g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},setFilters:{first:function(g,h){return h===0},last:function(g,h,k,l){return h===l.length-1},even:function(g,h){return h%2===
0},odd:function(g,h){return h%2===1},lt:function(g,h,k){return h<k[3]-0},gt:function(g,h,k){return h>k[3]-0},nth:function(g,h,k){return k[3]-0===h},eq:function(g,h,k){return k[3]-0===h}},filter:{PSEUDO:function(g,h,k,l){var q=h[1],p=m.filters[q];if(p)return p(g,k,h,l);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=h[3];k=0;for(l=h.length;k<l;k++)if(h[k]===g)return false;return true}else o.error("Syntax error, unrecognized expression: "+
q)},CHILD:function(g,h){var k=h[1],l=g;switch(k){case "only":case "first":for(;l=l.previousSibling;)if(l.nodeType===1)return false;if(k==="first")return true;l=g;case "last":for(;l=l.nextSibling;)if(l.nodeType===1)return false;return true;case "nth":k=h[2];var q=h[3];if(k===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var u=0;for(l=p.firstChild;l;l=l.nextSibling)if(l.nodeType===1)l.nodeIndex=++u;p.sizcache=h}g=g.nodeIndex-q;return k===0?g===0:g%k===0&&g/k>=
0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var k=h[1];g=m.attrHandle[k]?m.attrHandle[k](g):g[k]!=null?g[k]:g.getAttribute(k);k=g+"";var l=h[2];h=h[4];return g==null?l==="!=":l==="="?k===h:l==="*="?k.indexOf(h)>=0:l==="~="?(" "+k+" ").indexOf(h)>=0:!h?k&&g!==false:l==="!="?k!==h:l==="^="?
k.indexOf(h)===0:l==="$="?k.substr(k.length-h.length)===h:l==="|="?k===h||k.substr(0,h.length+1)===h+"-":false},POS:function(g,h,k,l){var q=m.setFilters[h[2]];if(q)return q(g,k,h,l)}}},s=m.match.POS;for(var x in m.match){m.match[x]=new RegExp(m.match[x].source+/(?![^\[]*\])(?![^\(]*\))/.source);m.leftMatch[x]=new RegExp(/(^(?:.|\r|\n)*?)/.source+m.match[x].source.replace(/\\(\d+)/g,function(g,h){return"\\"+(h-0+1)}))}var A=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};
try{Array.prototype.slice.call(r.documentElement.childNodes,0)}catch(B){A=function(g,h){h=h||[];if(i.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var k=0,l=g.length;k<l;k++)h.push(g[k]);else for(k=0;g[k];k++)h.push(g[k]);return h}}var C;if(r.documentElement.compareDocumentPosition)C=function(g,h){if(!g.compareDocumentPosition||!h.compareDocumentPosition){if(g==h)j=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===
h?0:1;if(g===0)j=true;return g};else if("sourceIndex"in r.documentElement)C=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)j=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)j=true;return g};else if(r.createRange)C=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)j=true;return g.ownerDocument?-1:1}var k=g.ownerDocument.createRange(),l=h.ownerDocument.createRange();k.setStart(g,0);k.setEnd(g,0);l.setStart(h,0);l.setEnd(h,0);g=k.compareBoundaryPoints(Range.START_TO_END,
l);if(g===0)j=true;return g};(function(){var g=r.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var k=r.documentElement;k.insertBefore(g,k.firstChild);if(r.getElementById(h)){m.find.ID=function(l,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(l[1]))?q.id===l[1]||typeof q.getAttributeNode!=="undefined"&&q.getAttributeNode("id").nodeValue===l[1]?[q]:v:[]};m.filter.ID=function(l,q){var p=typeof l.getAttributeNode!=="undefined"&&l.getAttributeNode("id");
return l.nodeType===1&&p&&p.nodeValue===q}}k.removeChild(g);k=g=null})();(function(){var g=r.createElement("div");g.appendChild(r.createComment(""));if(g.getElementsByTagName("*").length>0)m.find.TAG=function(h,k){k=k.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var l=0;k[l];l++)k[l].nodeType===1&&h.push(k[l]);k=h}return k};g.innerHTML="<a href='#'></a>";if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")m.attrHandle.href=function(h){return h.getAttribute("href",
2)};g=null})();r.querySelectorAll&&function(){var g=o,h=r.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){o=function(l,q,p,u){q=q||r;if(!u&&q.nodeType===9&&!w(q))try{return A(q.querySelectorAll(l),p)}catch(t){}return g(l,q,p,u)};for(var k in g)o[k]=g[k];h=null}}();(function(){var g=r.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===
0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){m.order.splice(1,0,"CLASS");m.find.CLASS=function(h,k,l){if(typeof k.getElementsByClassName!=="undefined"&&!l)return k.getElementsByClassName(h[1])};g=null}}})();var E=r.compareDocumentPosition?function(g,h){return g.compareDocumentPosition(h)&16}:function(g,h){return g!==h&&(g.contains?g.contains(h):true)},w=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},fa=function(g,h){var k=[],
l="",q;for(h=h.nodeType?[h]:h;q=m.match.PSEUDO.exec(g);){l+=q[0];g=g.replace(m.match.PSEUDO,"")}g=m.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)o(g,h[q],k);return o.filter(l,k)};c.find=o;c.expr=o.selectors;c.expr[":"]=c.expr.filters;c.unique=o.uniqueSort;c.getText=a;c.isXMLDoc=w;c.contains=E})();var bb=/Until$/,cb=/^(?:parents|prevUntil|prevAll)/,db=/,/;Q=Array.prototype.slice;var Ea=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,i){return!!b.call(e,i,e)===d});else if(b.nodeType)return c.grep(a,
function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Qa.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;c.find(a,this[f],b);if(f>0)for(var i=d;i<b.length;i++)for(var j=0;j<d;j++)if(b[j]===b[i]){b.splice(i--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=
0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ea(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ea(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,i={},j;if(f&&a.length){e=0;for(var n=a.length;e<n;e++){j=a[e];i[j]||(i[j]=c.expr.match.POS.test(j)?c(j,b||this.context):j)}for(;f&&f.ownerDocument&&f!==b;){for(j in i){e=i[j];if(e.jquery?e.index(f)>
-1:c(f).is(e)){d.push({selector:j,elem:f});delete i[j]}}f=f.parentNode}}return d}var o=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(m,s){for(;s&&s.ownerDocument&&s!==b;){if(o?o.index(s)>-1:c(s).is(a))return s;s=s.parentNode}return null})},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),
a);return this.pushStack(pa(a[0])||pa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},
nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);bb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):
e;if((this.length>1||db.test(f))&&cb.test(a))e=e.reverse();return this.pushStack(e,a,Q.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===v||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==
b&&d.push(a);return d}});var Fa=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ga=/(<([\w:]+)[^>]*?)\/>/g,eb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,Ha=/<([\w:]+)/,fb=/<tbody/i,gb=/<|&\w+;/,sa=/checked\s*(?:[^=]|=\s*.checked.)/i,Ia=function(a,b,d){return eb.test(d)?a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],
col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==v)return this.empty().append((this[0]&&this[0].ownerDocument||r).createTextNode(a));return c.getText(this)},
wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?
d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,
false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&
!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Fa,"").replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){qa(this,b);qa(this.find("*"),b.find("*"))}return b},html:function(a){if(a===v)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Fa,""):null;else if(typeof a==="string"&&!/<script/i.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(Ha.exec(a)||
["",""])[1].toLowerCase()]){a=a.replace(Ga,Ia);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var i=c(this),j=i.html();i.empty().append(function(){return a.call(this,e,j)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,
b,f))});else a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(s){return c.nodeName(s,"table")?s.getElementsByTagName("tbody")[0]||s.appendChild(s.ownerDocument.createElement("tbody")):s}var e,i,j=a[0],n=[];if(!c.support.checkClone&&arguments.length===3&&typeof j===
"string"&&sa.test(j))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(j))return this.each(function(s){var x=c(this);a[0]=j.call(this,s,b?x.html():v);x.domManip(a,b,d)});if(this[0]){e=a[0]&&a[0].parentNode&&a[0].parentNode.nodeType===11?{fragment:a[0].parentNode}:ra(a,this,n);if(i=e.fragment.firstChild){b=b&&c.nodeName(i,"tr");for(var o=0,m=this.length;o<m;o++)d.call(b?f(this[o],i):this[o],e.cacheable||this.length>1||o>0?e.fragment.cloneNode(true):e.fragment)}n&&c.each(n,
Ma)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);for(var e=0,i=d.length;e<i;e++){var j=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),j);f=f.concat(j)}return this.pushStack(f,a,d.selector)}});c.each({remove:function(a,b){if(!a||c.filter(a,[this]).length){if(!b&&this.nodeType===1){c.cleanData(this.getElementsByTagName("*"));c.cleanData([this])}this.parentNode&&
this.parentNode.removeChild(this)}},empty:function(){for(this.nodeType===1&&c.cleanData(this.getElementsByTagName("*"));this.firstChild;)this.removeChild(this.firstChild)}},function(a,b){c.fn[a]=function(){return this.each(b,arguments)}});c.extend({clean:function(a,b,d,f){b=b||r;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||r;var e=[];c.each(a,function(i,j){if(typeof j==="number")j+="";if(j){if(typeof j==="string"&&!gb.test(j))j=b.createTextNode(j);else if(typeof j===
"string"){j=j.replace(Ga,Ia);var n=(Ha.exec(j)||["",""])[1].toLowerCase(),o=F[n]||F._default,m=o[0];i=b.createElement("div");for(i.innerHTML=o[1]+j+o[2];m--;)i=i.lastChild;if(!c.support.tbody){m=fb.test(j);n=n==="table"&&!m?i.firstChild&&i.firstChild.childNodes:o[1]==="<table>"&&!m?i.childNodes:[];for(o=n.length-1;o>=0;--o)c.nodeName(n[o],"tbody")&&!n[o].childNodes.length&&n[o].parentNode.removeChild(n[o])}!c.support.leadingWhitespace&&V.test(j)&&i.insertBefore(b.createTextNode(V.exec(j)[0]),i.firstChild);
j=c.makeArray(i.childNodes)}if(j.nodeType)e.push(j);else e=c.merge(e,j)}});if(d)for(a=0;e[a];a++)if(f&&c.nodeName(e[a],"script")&&(!e[a].type||e[a].type.toLowerCase()==="text/javascript"))f.push(e[a].parentNode?e[a].parentNode.removeChild(e[a]):e[a]);else{e[a].nodeType===1&&e.splice.apply(e,[a+1,0].concat(c.makeArray(e[a].getElementsByTagName("script"))));d.appendChild(e[a])}return e},cleanData:function(a){for(var b=0,d;(d=a[b])!=null;b++){c.event.remove(d);c.removeData(d)}}});var hb=/z-?index|font-?weight|opacity|zoom|line-?height/i,
Ja=/alpha\([^)]*\)/,Ka=/opacity=([^)]*)/,ga=/float/i,ha=/-([a-z])/ig,ib=/([A-Z])/g,jb=/^-?\d+(?:px)?$/i,kb=/^-?\d/,lb={position:"absolute",visibility:"hidden",display:"block"},mb=["Left","Right"],nb=["Top","Bottom"],ob=r.defaultView&&r.defaultView.getComputedStyle,La=c.support.cssFloat?"cssFloat":"styleFloat",ia=function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===v)return c.curCSS(d,f);if(typeof e==="number"&&!hb.test(f))e+="px";c.style(d,f,e)})};
c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return v;if((b==="width"||b==="height")&&parseFloat(d)<0)d=v;var f=a.style||a,e=d!==v;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=Ja.test(a)?a.replace(Ja,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Ka.exec(f.filter)[1])/100+"":""}if(ga.test(b))b=La;b=b.replace(ha,ia);if(e)f[b]=d;return f[b]},css:function(a,
b,d,f){if(b==="width"||b==="height"){var e,i=b==="width"?mb:nb;function j(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(i,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,"border"+this+"Width",true))||0})}a.offsetWidth!==0?j():c.swap(a,lb,j);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&
a.currentStyle){f=Ka.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ga.test(b))b=La;if(!d&&e&&e[b])f=e[b];else if(ob){if(ga.test(b))b="float";b=b.replace(ib,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ha,ia);f=a.currentStyle[b]||a.currentStyle[d];if(!jb.test(f)&&kb.test(f)){b=e.left;var i=a.runtimeStyle.left;a.runtimeStyle.left=
a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=i}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var pb=
J(),qb=/<script(.|\s)*?\/script>/gi,rb=/select|textarea/i,sb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ja=/\?/,tb=/(\?|&)_=.*?(&|$)/,ub=/^(\w+:)?\/\/([^\/?#]+)/,vb=/%20/g;c.fn.extend({_load:c.fn.load,load:function(a,b,d){if(typeof a!=="string")return this._load(a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=
c.param(b,c.ajaxSettings.traditional);f="POST"}var i=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(j,n){if(n==="success"||n==="notmodified")i.html(e?c("<div />").append(j.responseText.replace(qb,"")).find(e):j.responseText);d&&i.each(d,[j.responseText,n,j])}});return this},serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&
(this.checked||rb.test(this.nodeName)||sb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,
b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:z.XMLHttpRequest&&(z.location.protocol!=="file:"||!z.ActiveXObject)?function(){return new z.XMLHttpRequest}:
function(){try{return new z.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&e.success.call(o,n,j,w);e.global&&f("ajaxSuccess",[w,e])}function d(){e.complete&&e.complete.call(o,w,j);e.global&&f("ajaxComplete",[w,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}
function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),i,j,n,o=a&&a.context||e,m=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(m==="GET")N.test(e.url)||(e.url+=(ja.test(e.url)?"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||
N.test(e.url))){i=e.jsonpCallback||"jsonp"+pb++;if(e.data)e.data=(e.data+"").replace(N,"="+i+"$1");e.url=e.url.replace(N,"="+i+"$1");e.dataType="script";z[i]=z[i]||function(q){n=q;b();d();z[i]=v;try{delete z[i]}catch(p){}A&&A.removeChild(B)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===false&&m==="GET"){var s=J(),x=e.url.replace(tb,"$1_="+s+"$2");e.url=x+(x===e.url?(ja.test(e.url)?"&":"?")+"_="+s:"")}if(e.data&&m==="GET")e.url+=(ja.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&
c.event.trigger("ajaxStart");s=(s=ub.exec(e.url))&&(s[1]&&s[1]!==location.protocol||s[2]!==location.host);if(e.dataType==="script"&&m==="GET"&&s){var A=r.getElementsByTagName("head")[0]||r.documentElement,B=r.createElement("script");B.src=e.url;if(e.scriptCharset)B.charset=e.scriptCharset;if(!i){var C=false;B.onload=B.onreadystatechange=function(){if(!C&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){C=true;b();d();B.onload=B.onreadystatechange=null;A&&B.parentNode&&
A.removeChild(B)}}}A.insertBefore(B,A.firstChild);return v}var E=false,w=e.xhr();if(w){e.username?w.open(m,e.url,e.async,e.username,e.password):w.open(m,e.url,e.async);try{if(e.data||a&&a.contentType)w.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&w.setRequestHeader("If-Modified-Since",c.lastModified[e.url]);c.etag[e.url]&&w.setRequestHeader("If-None-Match",c.etag[e.url])}s||w.setRequestHeader("X-Requested-With","XMLHttpRequest");w.setRequestHeader("Accept",
e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(fa){}if(e.beforeSend&&e.beforeSend.call(o,w,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");w.abort();return false}e.global&&f("ajaxSend",[w,e]);var g=w.onreadystatechange=function(q){if(!w||w.readyState===0||q==="abort"){E||d();E=true;if(w)w.onreadystatechange=c.noop}else if(!E&&w&&(w.readyState===4||q==="timeout")){E=true;w.onreadystatechange=c.noop;j=q==="timeout"?"timeout":!c.httpSuccess(w)?
"error":e.ifModified&&c.httpNotModified(w,e.url)?"notmodified":"success";var p;if(j==="success")try{n=c.httpData(w,e.dataType,e)}catch(u){j="parsererror";p=u}if(j==="success"||j==="notmodified")i||b();else c.handleError(e,w,j,p);d();q==="timeout"&&w.abort();if(e.async)w=null}};try{var h=w.abort;w.abort=function(){w&&h.call(w);g("abort")}}catch(k){}e.async&&e.timeout>0&&setTimeout(function(){w&&!E&&g("timeout")},e.timeout);try{w.send(m==="POST"||m==="PUT"||m==="DELETE"?e.data:null)}catch(l){c.handleError(e,
w,null,l);d()}e.async||g();return w}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=
f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b==="json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(j,n){if(c.isArray(n))c.each(n,
function(o,m){b?f(j,m):d(j+"["+(typeof m==="object"||c.isArray(m)?o:"")+"]",m)});else!b&&n!=null&&typeof n==="object"?c.each(n,function(o,m){d(j+"["+o+"]",m)}):f(j,n)}function f(j,n){n=c.isFunction(n)?n():n;e[e.length]=encodeURIComponent(j)+"="+encodeURIComponent(n)}var e=[];if(b===v)b=c.ajaxSettings.traditional;if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var i in a)d(i,a[i]);return e.join("&").replace(vb,"+")}});var ka={},wb=/toggle|show|hide/,xb=/^([+-]=)?([\d+-.]+)(.*)$/,
W,ta=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(ka[d])f=ka[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();
ka[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&
c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var i=c.extend({},e),j,n=this.nodeType===1&&c(this).is(":hidden"),
o=this;for(j in a){var m=j.replace(ha,ia);if(j!==m){a[m]=a[j];delete a[j];j=m}if(a[j]==="hide"&&n||a[j]==="show"&&!n)return i.complete.call(this);if((j==="height"||j==="width")&&this.style){i.display=c.css(this,"display");i.overflow=this.style.overflow}if(c.isArray(a[j])){(i.specialEasing=i.specialEasing||{})[j]=a[j][1];a[j]=a[j][0]}}if(i.overflow!=null)this.style.overflow="hidden";i.curAnim=c.extend({},a);c.each(a,function(s,x){var A=new c.fx(o,i,s);if(wb.test(x))A[x==="toggle"?n?"show":"hide":x](a);
else{var B=xb.exec(x),C=A.cur(true)||0;if(B){x=parseFloat(B[2]);var E=B[3]||"px";if(E!=="px"){o.style[s]=(x||1)+E;C=(x||1)/A.cur(true)*C;o.style[s]=C+E}if(B[1])x=(B[1]==="-="?-1:1)*x+C;A.custom(C,x,E)}else A.custom(C,x,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",
1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration==="number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,
b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==
null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(i){return e.step(i)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop===
"width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=
this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=
c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=
null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in r.documentElement?function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),
f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=this[0];if(a)return this.each(function(s){c.offset.setOffset(this,a,s)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=
b,e=b.ownerDocument,i,j=e.documentElement,n=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var o=b.offsetTop,m=b.offsetLeft;(b=b.parentNode)&&b!==n&&b!==j;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;i=e?e.getComputedStyle(b,null):b.currentStyle;o-=b.scrollTop;m-=b.scrollLeft;if(b===d){o+=b.offsetTop;m+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){o+=parseFloat(i.borderTopWidth)||
0;m+=parseFloat(i.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&i.overflow!=="visible"){o+=parseFloat(i.borderTopWidth)||0;m+=parseFloat(i.borderLeftWidth)||0}f=i}if(f.position==="relative"||f.position==="static"){o+=n.offsetTop;m+=n.offsetLeft}if(c.offset.supportsFixedPosition&&f.position==="fixed"){o+=Math.max(j.scrollTop,n.scrollTop);m+=Math.max(j.scrollLeft,n.scrollLeft)}return{top:o,left:m}};c.offset={initialize:function(){var a=r.body,b=r.createElement("div"),
d,f,e,i=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";a.insertBefore(b,a.firstChild);
d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i;a.removeChild(b);c.offset.initialize=c.noop},
bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),i=parseInt(c.curCSS(a,"top",true),10)||0,j=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,d,e);d={top:b.top-e.top+i,left:b.left-
e.left+j};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=
this.offsetParent||r.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],i;if(!e)return null;if(f!==v)return this.each(function(){if(i=ua(this))i.scrollTo(!a?f:c(i).scrollLeft(),a?f:c(i).scrollTop());else this[d]=f});else return(i=ua(e))?"pageXOffset"in i?i[a?"pageYOffset":"pageXOffset"]:c.support.boxModel&&i.document.documentElement[d]||i.document.body[d]:e[d]}});
c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(i){var j=c(this);j[d](f.call(this,i,j[d]()))});return"scrollTo"in e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||
e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===v?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});z.jQuery=z.$=c})(window);

View File

@ -1,239 +0,0 @@
jQuery(function ($) {
var rails = {
update: function (selector, content, position) {
var element = $('#' + selector);
if (position) {
switch (position) {
case "before":
element.before(content);
break;
case "after":
element.after(content);
break;
case "top":
element.prepend(content);
break;
case "bottom":
element.append(content);
break;
default:
element.append(content);
break;
}
} else {
element.html(content);
}
},
remote: function (e) {
var el = $(this),
data = [],
condition = el.attr('data-condition') ? eval(el.attr('data-condition')) : true,
method = el.attr('method') || el.attr('data-method') || 'GET',
url = el.attr('action') || el.attr('data-url') || '#',
async = el.attr('data-remote-type') === 'synchronous' ? false : true;
if (el.attr('data-submit')) {
data = $('#' + el.attr('data-submit')).serializeArray();
} else if (el.attr('data-with')) {
if (e && e.target.tagName.toUpperCase() == 'SCRIPT' && el.attr('data-observed') !== null) {
var observed = $('#' + el.attr('data-observed'));
if(observed[0].tagName.toUpperCase() === 'FORM'){
data = el.attr('data-with') + '=' + observed.serialize();
} else if(observed[0].tagName.toUpperCase() === 'INPUT' && observed.attr('type').toUpperCase() !== "BUTTON" && observed.attr('type').toUpperCase() !== "SUBMIT") {
data = el.attr('data-with') + '=' + observed.val();
}
} else {
// TODO: remove eval when deprecated
data = eval(el.attr('data-with'));
}
} else if (e && e.target.tagName.toUpperCase() == 'FORM') {
data = el.serializeArray();
} else if (e && e.target.tagName.toUpperCase() == 'INPUT') {
data = el.closest('form').serializeArray();
}
if (condition) {
el.trigger('rails:before');
$.ajax({
async: async,
url: url,
data: data,
type: method.toUpperCase(),
beforeSend: function (xhr) {
xhr.setRequestHeader("Accept", "text/javascript")
el.trigger('rails:after', xhr);
el.trigger('rails:loading', xhr);
},
success: function (data, status, xhr) {
el.trigger('rails:success', [data, status, xhr]);
if (el.attr('data-update-success')) {
rails.update(el.attr('data-update-success'), data, el.attr('data-update-position'));
}
},
complete: function (xhr) {
// enable disabled_with buttons
if (el[0].tagName.toUpperCase() == 'FORM') {
el.children('input[type="button"][data-enable-with],input[type="submit"][data-enable-with]').each(function(i, button){
button = $(button);
button.attr('value', button.attr('data-enable-with'));
button.removeAttr('data-enable-with');
button.removeAttr('disabled');
});
} else {
el.attr('value', el.attr('data-enable-with'));
el.removeAttr('data-enable-with');
el.removeAttr('disabled');
}
el.trigger('rails:complete', xhr);
el.trigger('rails:loaded', xhr);
},
error: function (xhr, status, error) {
el.trigger('rails:failure', [xhr, status, error]);
if (el.attr('data-update-failure')) {
rails.update(el.attr('data-update-failure'), xhr.responseText, el.attr('data-update-position'));
}
}
});
}
e.preventDefault();
}
}
/**
* observe_form, and observe_field
*/
$('script[data-observe="true"]').each(function (index, e) {
var el = $(e),
observed = $('#' + $(e).attr('data-observed'));
frequency = el.attr('data-frequency') ? el.attr('data-frequency') : 10,
value = observed[0].tagName.toUpperCase() === 'FORM' ? observed.serialize() : observed.val();
var observe = function (observed, frequency, value, e) {
return function () {
var event = new jQuery.Event('periodical'),
newValue = observed[0].tagName.toUpperCase() === 'FORM' ? observed.serialize() : observed.val();
event.target = e;
if(value !== newValue) {
value = newValue;
$(e).trigger('rails:observe');
rails.remote.call(el, event);
}
}
}(observed, frequency, value, e);
setInterval(observe, frequency * 1000);
});
/**
* confirm
* make sure this event is first!
*/
$('a[data-confirm],input[type="submit"][data-confirm],input[type="button"][data-confirm]').live('click', function(e){
var el = $(this);
if(!confirm(el.attr('data-confirm'))){
return false;
}
});
/**
* periodically_call_remote
*/
$('script[data-periodical="true"]').each(function (index, e) {
var el = $(e),
frequency = el.attr('data-frequency') ? el.attr('data-frequency') : 10;
setInterval(function () {
return function () {
var event = new jQuery.Event('periodical');
event.target = e;
rails.remote.call(el, event);
}
}(e, el), frequency * 1000);
});
/**
* disable_with
*/
$('input[type="button"][data-disable-with],input[type="submit"][data-disable-with]').live('click', function(e){
var el = $(this);
el.attr('data-enable-with', el.attr('value'));
el.attr('disabled', 'disabled');
el.attr('value', el.attr('data-disable-with'));
});
/**
* remote_form_tag, and remote_form_for
*/
$('form[data-remote="true"]').live('submit', rails.remote);
/**
* link_to_remote, button_to_remote, and submit_to_remote
*/
$('a[data-remote="true"],input[data-remote="true"],input[data-remote-submit="true"]').live('click', rails.remote);
/*
* popup
*/
$('a[data-popup],input[type="button"][data-popup]').live('click', function(e){
var el = $(this),
url = el.attr('data-url') || el.attr('href');
e.preventDefault();
if(el.attr('data-popup') === "true"){
window.open(url);
} else {
window.open(url, el.attr('data-popup'));
}
});
/**
*
* Rails 2.x Helper / Event Handlers
* By default we listen to all callbacks, and status code callbacks and
* check the element for data-<callback> attribute and eval it.
*
*/
rails.compat = {
evalAttribute: function (element, attribute) {
var el = $(element),
attr = el.attr('data-' + attribute);
return (attr) ? eval(attr) : true;
}
};
$('form[data-remote="true"],a[data-remote="true"],input[data-remote="true"],script[data-observe="true"]')
.live('rails:before', function (e) {
rails.compat.evalAttribute(this, 'onbefore');
})
.live('rails:after', function (e, xhr) {
rails.compat.evalAttribute(this, 'onafter');
})
.live('rails:loading', function (e, xhr) {
rails.compat.evalAttribute(this, 'onloading');
})
.live('rails:loaded', function (e, xhr) {
rails.compat.evalAttribute(this, 'onloaded');
})
.live('rails:complete', function (e, xhr) {
rails.compat.evalAttribute(this, 'oncomplete');
rails.compat.evalAttribute(this, 'on' + xhr.status);
})
.live('rails:success', function (e, data, status, xhr) {
rails.compat.evalAttribute(this, 'onsuccess');
})
.live('rails:failure', function (e, xhr, status, error) {
rails.compat.evalAttribute(this, 'onfailure');
})
.live('rails:observe', function (e) {
rails.compat.evalAttribute(this, 'onobserve');
});
});

View File

@ -1,324 +0,0 @@
Event.observe(document, 'dom:loaded', function() {
function handle_remote(el, e){
var data = null,
method = el.readAttribute('method') || el.readAttribute('data-method') || 'GET',
url = el.readAttribute('action') || el.readAttribute('data-url') || '#',
async = el.readAttribute('data-remote-type') === 'synchronous' ? false : true,
update = el.readAttribute('data-update-success'),
position = el.readAttribute('data-update-position');
if (el.readAttribute('data-submit')) {
var submit_el = $(el.readAttribute('data-submit'));
if(submit_el !== undefined && submit_el.tagName.toUpperCase() == 'FORM'){
data = submit_el.serialize();
}
} else if (el.readAttribute('data-with')) {
// It seems there is a big inconsistency between what :with means depending on the element type
// so this is going to look a bit crazy
if(el.tagName.toUpperCase() === 'SCRIPT' && el.readAttribute('data-observed') !== null){
// Handle observe_field and observe_form
var observed_element = $(el.readAttribute('data-observed'));
if(observed_element.tagName.toUpperCase() === 'FORM'){
data = el.readAttribute('data-with') + '=' + observed_element.serialize();
} else if(observed_element.tagName.toUpperCase() === 'INPUT' && observed_element.readAttribute('type').toUpperCase() !== "BUTTON" && observed_element.readAttribute('type').toUpperCase() !== "SUBMIT") {
data = el.readAttribute('data-with') + '=' + observed_element.getValue();
}
} else {
// Handle link_to and button_to
data = evalAttribute(el, 'data-with');
}
} else if(el.tagName.toUpperCase() === 'FORM') {
data = el.serialize();
}
document.fire('rails:before');
var request = new Ajax.Request(url, {
method: method,
asynchronous: async,
parameters: data,
evalJS: true,
evalJSON: true,
onComplete: function(xhr){
document.fire('rails:complete', {xhr: xhr, element: el, submitted_button: getEventProperty(e, 'submitted_button')});
},
onLoading: function(xhr){
document.fire('rails:after', {xhr: xhr, element: el});
document.fire('rails:loading', {xhr: xhr, element: el});
},
onLoaded: function(xhr){
document.fire('rails:loaded', {xhr: xhr, element: el});
},
onSuccess: function(xhr){
document.fire('rails:success', {xhr: xhr, element: el});
},
onFailure: function(xhr){
document.fire('rails:failure', {xhr: xhr, element: el});
}
});
}
function setEventProperty(e, property, value){
if(e.memo === undefined){
e.memo = {};
}
e.memo[property] = value;
}
function getEventProperty(e, property){
if(e !== null && e.memo !== undefined && e.memo[property] !== undefined){
return e.memo[property];
}
}
function confirmed(e, el){
if(getEventProperty(e,'confirm_checked') !== true){
setEventProperty(e, 'confirm_checked', true);
el = Event.findElement(e, 'form') || el;
var confirm_msg = el.readAttribute('data-confirm');
if(confirm_msg !== null){
var result = el.fire('rails:confirm', {confirm_msg: confirm_msg});
if(result.memo.stop_event === true){
Event.stop(e);
return false;
}
}
}
return true;
}
function disable_button(el){
var disable_with = el.readAttribute('data-disable-with');
if(disable_with !== null){
el.writeAttribute('data-enable-with', el.readAttribute('value'));
el.writeAttribute('value', disable_with);
el.writeAttribute('disabled', true);
}
}
function enable_button(el){
var enable_with = el.readAttribute('data-enable-with');
if(enable_with !== null){
el.writeAttribute('value', enable_with);
}
el.writeAttribute('disabled', false);
}
function updateHTML(el, content, result){
var element_id = null;
if(result === 'success'){
element_id = el.readAttribute('data-update-success');
} else if(result === 'failure'){
element_id = el.readAttribute('data-update-failure');
}
var element_to_update = $(element_id);
if(element_to_update !== null){
var position = el.readAttribute('data-update-position');
if(position !== null){
var options = {};
options[position] = content;
element_to_update.insert(options);
} else {
element_to_update.update(content);
}
}
}
$$("script[data-periodical=true]").each(function(el){
var executor = new PeriodicalExecuter(function() { handle_remote(el);}, el.readAttribute('data-frequency'));
});
$$("script[data-observe=true]").each(function(el){
var observed_element = $(el.readAttribute('data-observed'));
var original_value = observed_element.tagName.toUpperCase() === 'FORM' ? observed_element.serialize() : observed_element.getValue();
var callback = el.readAttribute('data-onobserve');
var executor = new PeriodicalExecuter(function() {
var value = observed_element.tagName.toUpperCase() === 'FORM' ? observed_element.serialize() : observed_element.getValue();
if(original_value !== value){
original_value = value;
if(callback !== null){
evalAttribute(el, 'onobserve');
} else if(el.readAttribute('data-url') !== null){
handle_remote(el);
}
}
}, el.readAttribute('data-frequency'));
});
/**
*
* Event Listeners
*
* the original element is contained inside the event,
* for some reason prototype wont let me listen for custom events on document
* if the event wasn't fired on document
*
*/
Event.observe(document, 'submit', function (e) {
var form = Event.findElement(e, 'form');
// Make sure conditions and confirm have not already run
if(form !== undefined && conditions_met(e, form) && confirmed(e, form)){
var button = form.down('input[data-submitted=true]');
button.writeAttribute('data-submitted', null);
setEventProperty(e, 'submitted_button', button);
disable_button(button);
if(form.readAttribute('data-remote') === 'true'){
Event.stop(e);
handle_remote(form, e);
}
}
});
Event.observe(document, 'click', function (e) {
var el = Event.findElement(e, 'a') || Event.findElement(e, 'input');
if(el !== undefined && el.tagName.toUpperCase() === 'INPUT' && el.readAttribute('type').toUpperCase() === 'SUBMIT'){
el.writeAttribute('data-submitted', 'true');
// Submit is handled by submit event, don't continue on this path
el = undefined;
} else if(el !== undefined && el.tagName.toUpperCase() === 'INPUT' && el.readAttribute('type').toUpperCase() !== 'BUTTON'){
// Make sure other inputs do not send this event
el = undefined;
}
if(el !== undefined && conditions_met(e, el) && confirmed(e, el)){
if(el.tagName.toUpperCase() === 'INPUT' && el.readAttribute('type').toUpperCase() === 'BUTTON'){
disable_button(el);
}
if(el.readAttribute('data-remote') === 'true'){
Event.stop(e);
handle_remote(el, e);
} else if(el.readAttribute('data-popup') !== null){
Event.stop(e);
document.fire('rails:popup', {element: el});
}
}
});
/**
*
* Default Event Handlers
*
*/
Event.observe(document, 'rails:confirm', function(e){
setEventProperty(e, 'stop_event', !confirm(getEventProperty(e,'confirm_msg')));
});
Event.observe(document, 'rails:popup', function(e){
var el = getEventProperty(e, 'element');
var url = el.readAttribute('href') || el.readAttribute('data-url');
if(el.readAttribute('data-popup') === true){
window.open(url);
} else {
window.open(url, el.readAttribute('data-popup'));
}
});
Event.observe(document, 'rails:complete', function(e){
var el = getEventProperty(e, 'element');
if(el.tagName.toUpperCase() === 'FORM'){
var button = getEventProperty(e, 'submitted_button') ;
enable_button(button);
}
});
Event.observe(document, 'rails:success', function(e){
var el = getEventProperty(e, 'element'),
xhr = getEventProperty(e, 'xhr');
if(xhr.responseText !== null){
updateHTML(el, xhr.responseText, 'success');
}
});
Event.observe(document, 'rails:failure', function(e){
var el = getEventProperty(e, 'element'),
xhr = getEventProperty(e, 'xhr');
if(xhr.responseText !== null){
updateHTML(el, xhr.responseText, 'failure');
}
});
/**
*
* Rails 2.x Helpers / Event Handlers
*
*/
function evalAttribute(el, attribute){
var js = el.readAttribute('data-' + attribute);
if(js){
eval(js);
}
}
function conditions_met(e, el){
if(getEventProperty(e,'condition_checked') !== true){
setEventProperty(e, 'condition_checked', true);
el = Event.findElement(e, 'form') || el;
var conditions = el.readAttribute('data-condition');
if(conditions !== null){
if(eval(conditions) === false){
Event.stop(e);
return false;
}
}
}
return true;
}
Event.observe(document, 'rails:success', function(e){
evalAttribute(el, 'onsuccess');
});
Event.observe(document, 'rails:failure', function(e){
evalAttribute(el, 'onfailure');
});
Event.observe(document, 'rails:complete', function(e){
var el = getEventProperty(e, 'element');
evalAttribute(el, 'oncomplete');
evalAttribute(el, 'on' + getEventProperty('xhr', xhr.status));
if(el.readAttribute('data-periodical') === 'true'){
evalAttribute(el, 'onobserve');
}
});
Event.observe(document, 'rails:loading', function(e){
evalAttribute(el, 'onloading');
});
Event.observe(document, 'rails:loaded', function(e){
evalAttribute(el, 'onloaded');
});
Event.observe(document, 'rails:before', function(e){
evalAttribute(el, 'onbefore');
});
Event.observe(document, 'rails:after', function(e){
evalAttribute(el, 'onafter');
});
});

View File

@ -0,0 +1,77 @@
document.observe("dom:loaded", function() {
function handleRemote(element) {
var method, url, params;
if (element.tagName.toLowerCase() == 'form') {
method = element.readAttribute('method') || 'post';
url = element.readAttribute('action');
params = element.serialize(true);
} else {
method = element.readAttribute('data-method') || 'get';
// TODO: data-url support is going away, just use href
url = element.readAttribute('data-url') || element.readAttribute('href');
params = {};
}
var event = element.fire("ajax:before");
if (event.stopped) return false;
new Ajax.Request(url, {
method: method,
parameters: params,
asynchronous: true,
evalScripts: true,
onLoading: function(request) { element.fire("ajax:loading", {request: request}); },
onLoaded: function(request) { element.fire("ajax:loaded", {request: request}); },
onInteractive: function(request) { element.fire("ajax:interactive", {request: request}); },
onComplete: function(request) { element.fire("ajax:complete", {request: request}); },
onSuccess: function(request) { element.fire("ajax:success", {request: request}); },
onFailure: function(request) { element.fire("ajax:failure", {request: request}); }
});
element.fire("ajax:after");
}
$(document.body).observe("click", function(event) {
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();
});
// TODO: I don't think submit bubbles in IE
$(document.body).observe("submit", function(event) {
var inputs = event.element().select("input[type=submit][data-disable-with]");
inputs.each(function(input) {
input.disabled = true;
input.writeAttribute('data-original-value', input.value);
input.value = input.readAttribute('data-disable-with');
});
var element = event.findElement("form[data-remote=true]");
if (element) {
handleRemote(element);
event.stop();
}
});
$(document.body).observe("ajax:complete", function(event) {
var element = event.element();
if (element.tagName.toLowerCase() == 'form') {
var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
inputs.each(function(input) {
input.value = input.readAttribute('data-original-value');
input.writeAttribute('data-original-value', null);
input.disabled = false;
});
}
});
});

View File

@ -1,5 +1,4 @@
require 'rails/generators'
Rails::Generators.configure!
if ARGV.size == 0
Rails::Generators.help

View File

@ -1,5 +1,4 @@
require 'rails/generators'
Rails::Generators.configure!
if ARGV.size == 0
Rails::Generators.help

View File

@ -128,61 +128,61 @@ module Rails
def view_path=(value)
ActiveSupport::Deprecation.warn "config.view_path= is deprecated, " <<
"please do config.paths.app.views= instead", caller
"please do paths.app.views= instead", caller
paths.app.views = value
end
def view_path
ActiveSupport::Deprecation.warn "config.view_path is deprecated, " <<
"please do config.paths.app.views instead", caller
"please do paths.app.views instead", caller
paths.app.views.to_a.first
end
def routes_configuration_file=(value)
ActiveSupport::Deprecation.warn "config.routes_configuration_file= is deprecated, " <<
"please do config.paths.config.routes= instead", caller
"please do paths.config.routes= instead", caller
paths.config.routes = value
end
def routes_configuration_file
ActiveSupport::Deprecation.warn "config.routes_configuration_file is deprecated, " <<
"please do config.paths.config.routes instead", caller
"please do paths.config.routes instead", caller
paths.config.routes.to_a.first
end
def database_configuration_file=(value)
ActiveSupport::Deprecation.warn "config.database_configuration_file= is deprecated, " <<
"please do config.paths.config.database= instead", caller
"please do paths.config.database= instead", caller
paths.config.database = value
end
def database_configuration_file
ActiveSupport::Deprecation.warn "config.database_configuration_file is deprecated, " <<
"please do config.paths.config.database instead", caller
"please do paths.config.database instead", caller
paths.config.database.to_a.first
end
def log_path=(value)
ActiveSupport::Deprecation.warn "config.log_path= is deprecated, " <<
"please do config.paths.log= instead", caller
"please do paths.log= instead", caller
paths.config.log = value
end
def log_path
ActiveSupport::Deprecation.warn "config.log_path is deprecated, " <<
"please do config.paths.log instead", caller
"please do paths.log instead", caller
paths.config.log.to_a.first
end
def controller_paths=(value)
ActiveSupport::Deprecation.warn "config.controller_paths= is deprecated, " <<
"please do config.paths.app.controllers= instead", caller
"please do paths.app.controllers= instead", caller
paths.app.controllers = value
end
def controller_paths
ActiveSupport::Deprecation.warn "config.controller_paths is deprecated, " <<
"please do config.paths.app.controllers instead", caller
"please do paths.app.controllers instead", caller
paths.app.controllers.to_a.uniq
end
end

View File

@ -286,3 +286,6 @@ module Rails
end
end
# If the application was already defined, configure generators,
# otherwise you have to configure it by hand.
Rails::Generators.configure! if Rails.respond_to?(:application) && Rails.application

View File

@ -1,3 +1,5 @@
require 'tsort'
module Rails
module Initializable
def self.included(base)
@ -30,29 +32,21 @@ module Rails
end
class Collection < Array
include TSort
alias :tsort_each_node :each
def tsort_each_child(initializer, &block)
select { |i| i.before == initializer.name || i.name == initializer.after }.each(&block)
end
def initialize(initializers = [])
super()
initializers.each do |initializer|
if initializer.before
index = index_for(initializer.before)
elsif initializer.after
index = index_for(initializer.after)
index += 1 if index
else
index = length
end
insert(index || -1, initializer)
end
super(initializers)
replace(tsort)
end
def +(other)
Collection.new(to_a + other.to_a)
end
def index_for(name)
initializer = find { |i| i.name == name }
initializer && index(initializer)
end
end
def run_initializers(*args)
@ -87,6 +81,7 @@ module Rails
def initializer(name, opts = {}, &blk)
raise ArgumentError, "A block must be passed when defining an initializer" unless blk
opts[:after] ||= initializers.last.name unless initializers.empty? || initializers.find { |i| i.name == opts[:before] }
initializers << Initializer.new(name, nil, opts, &blk)
end

View File

@ -43,7 +43,7 @@ module Rails
@config ||= Engine::Configuration.new
end
initializer :load_init_rb do |app|
initializer :load_init_rb, :before => :load_application_initializers do |app|
file = Dir["#{root}/{rails/init,init}.rb"].first
config = app.config
eval(File.read(file), binding, file) if file && File.file?(file)

View File

@ -11,7 +11,7 @@ module Rails
end
rake_tasks do
load "rails/tasks/testing.rake"
end
load "rails/test_unit/testing.rake"
end
end
end

View File

@ -37,6 +37,13 @@ module ApplicationTests
require "#{app_path}/config/environment"
assert_respond_to Rails::Application, :routes_reloader
assert_equal Rails::Application.routes_reloader, Rails.application.routes_reloader
assert_equal Rails::Application.routes_reloader, AppTemplate::Application.routes_reloader
end
test "Rails::Application responds to paths" do
require "#{app_path}/config/environment"
assert_respond_to AppTemplate::Application, :paths
assert_equal AppTemplate::Application.paths.app.views.to_a, ["#{app_path}/app/views"]
end
test "the application root is set correctly" do

View File

@ -9,7 +9,7 @@ Rails.application.config.root = Rails.root
require 'rails/generators'
require 'rails/generators/test_case'
Rails::Generators.configure!
require 'active_record'
require 'action_dispatch'

View File

@ -50,7 +50,7 @@ module InitializableTests
$arr << 3
end
initializer :four, :after => :one do
initializer :four, :after => :one, :before => :two do
$arr << 4
end
end
@ -97,7 +97,7 @@ module InitializableTests
$arr << 3
end
initializer :terminate, :after => :first do
initializer :terminate, :after => :first, :before => :startup do
$arr << two
end
@ -121,6 +121,39 @@ module InitializableTests
end
end
module Interdependent
class PluginA
include Rails::Initializable
initializer "plugin_a.startup" do
$arr << 1
end
initializer "plugin_a.terminate" do
$arr << 4
end
end
class PluginB
include Rails::Initializable
initializer "plugin_b.startup", :after => "plugin_a.startup" do
$arr << 2
end
initializer "plugin_b.terminate", :before => "plugin_a.terminate" do
$arr << 3
end
end
class Application
include Rails::Initializable
def self.initializers
PluginB.initializers + PluginA.initializers
end
end
end
class Basic < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
@ -174,6 +207,12 @@ module InitializableTests
Child.run_initializers
assert_equal [5, 3, 1, 4, 2], $arr
end
test "handles dependencies introduced before all initializers are loaded" do
$arr = []
Interdependent::Application.run_initializers
assert_equal [1, 2, 3, 4], $arr
end
end
class InstanceTest < ActiveSupport::TestCase

View File

@ -47,7 +47,7 @@ module RailtiesTest
assert_equal :debug, LEVEL
end
test "plugin_init_is_ran_before_application_ones" do
test "plugin_init_is_run_before_application_ones" do
plugin "foo", "$foo = true" do |plugin|
plugin.write "lib/foo.rb", "module Foo; end"
end