TeX and CSS tweaks.
Sync with latest Instiki Trunk (Updates Rails to 1.2.2)
This commit is contained in:
parent
0ac586ee25
commit
c358389f25
443 changed files with 24218 additions and 9823 deletions
|
@ -1,5 +1,5 @@
|
|||
#--
|
||||
# Copyright (c) 2004 David Heinemeier Hansson
|
||||
# Copyright (c) 2004-2006 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
|
@ -30,7 +30,7 @@ unless defined?(ActiveSupport)
|
|||
require 'active_support'
|
||||
rescue LoadError
|
||||
require 'rubygems'
|
||||
require_gem 'activesupport'
|
||||
gem 'activesupport'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -43,7 +43,7 @@ require 'action_controller/benchmarking'
|
|||
require 'action_controller/flash'
|
||||
require 'action_controller/filters'
|
||||
require 'action_controller/layout'
|
||||
require 'action_controller/dependencies'
|
||||
require 'action_controller/deprecated_dependencies'
|
||||
require 'action_controller/mime_responds'
|
||||
require 'action_controller/pagination'
|
||||
require 'action_controller/scaffolding'
|
||||
|
|
|
@ -1,320 +1,82 @@
|
|||
require 'test/unit'
|
||||
require 'test/unit/assertions'
|
||||
require 'rexml/document'
|
||||
require File.dirname(__FILE__) + "/vendor/html-scanner/html/document"
|
||||
|
||||
module Test #:nodoc:
|
||||
module Unit #:nodoc:
|
||||
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
|
||||
# can be used against. These collections are:
|
||||
#
|
||||
# * assigns: Instance variables assigned in the action that are available for the view.
|
||||
# * session: Objects being saved in the session.
|
||||
# * flash: The flash objects currently in the session.
|
||||
# * cookies: Cookies being sent to the user on this request.
|
||||
#
|
||||
# These collections can be used just like any other hash:
|
||||
#
|
||||
# assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
|
||||
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
|
||||
# assert flash.empty? # makes sure that there's nothing in the flash
|
||||
#
|
||||
# For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To
|
||||
# appease our yearning for symbols, though, an alternative accessor has been deviced using a method call instead of index referencing.
|
||||
# So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
|
||||
#
|
||||
# On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
|
||||
#
|
||||
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
|
||||
# action call which can then be asserted against.
|
||||
#
|
||||
# == Manipulating the request collections
|
||||
#
|
||||
# The collections described above link to the response, so you can test if what the actions were expected to do happened. But
|
||||
# sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions
|
||||
# and cookies, though. For sessions, you just do:
|
||||
#
|
||||
# @request.session[:key] = "value"
|
||||
#
|
||||
# For cookies, you need to manually create the cookie, like this:
|
||||
#
|
||||
# @request.cookies["key"] = CGI::Cookie.new("key", "value")
|
||||
#
|
||||
# == Testing named routes
|
||||
#
|
||||
# If you're using named routes, they can be easily tested using the original named routes methods straight in the test case.
|
||||
# Example:
|
||||
#
|
||||
# assert_redirected_to page_url(:title => 'foo')
|
||||
module Assertions
|
||||
# Asserts that the response is one of the following types:
|
||||
#
|
||||
# * <tt>:success</tt>: Status code was 200
|
||||
# * <tt>:redirect</tt>: Status code was in the 300-399 range
|
||||
# * <tt>:missing</tt>: Status code was 404
|
||||
# * <tt>:error</tt>: Status code was in the 500-599 range
|
||||
#
|
||||
# You can also pass an explicit status code number as the type, like assert_response(501)
|
||||
def assert_response(type, message = nil)
|
||||
clean_backtrace do
|
||||
if [ :success, :missing, :redirect, :error ].include?(type) && @response.send("#{type}?")
|
||||
assert_block("") { true } # to count the assertion
|
||||
elsif type.is_a?(Fixnum) && @response.response_code == type
|
||||
assert_block("") { true } # to count the assertion
|
||||
else
|
||||
assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false }
|
||||
end
|
||||
end
|
||||
module ActionController #:nodoc:
|
||||
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
|
||||
# can be used against. These collections are:
|
||||
#
|
||||
# * assigns: Instance variables assigned in the action that are available for the view.
|
||||
# * session: Objects being saved in the session.
|
||||
# * flash: The flash objects currently in the session.
|
||||
# * cookies: Cookies being sent to the user on this request.
|
||||
#
|
||||
# These collections can be used just like any other hash:
|
||||
#
|
||||
# assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
|
||||
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
|
||||
# assert flash.empty? # makes sure that there's nothing in the flash
|
||||
#
|
||||
# For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To
|
||||
# appease our yearning for symbols, though, an alternative accessor has been deviced using a method call instead of index referencing.
|
||||
# So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
|
||||
#
|
||||
# On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
|
||||
#
|
||||
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
|
||||
# action call which can then be asserted against.
|
||||
#
|
||||
# == Manipulating the request collections
|
||||
#
|
||||
# The collections described above link to the response, so you can test if what the actions were expected to do happened. But
|
||||
# sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions
|
||||
# and cookies, though. For sessions, you just do:
|
||||
#
|
||||
# @request.session[:key] = "value"
|
||||
#
|
||||
# For cookies, you need to manually create the cookie, like this:
|
||||
#
|
||||
# @request.cookies["key"] = CGI::Cookie.new("key", "value")
|
||||
#
|
||||
# == Testing named routes
|
||||
#
|
||||
# If you're using named routes, they can be easily tested using the original named routes methods straight in the test case.
|
||||
# Example:
|
||||
#
|
||||
# assert_redirected_to page_url(:title => 'foo')
|
||||
module Assertions
|
||||
def self.included(klass)
|
||||
klass.class_eval do
|
||||
include ActionController::Assertions::ResponseAssertions
|
||||
include ActionController::Assertions::SelectorAssertions
|
||||
include ActionController::Assertions::RoutingAssertions
|
||||
include ActionController::Assertions::TagAssertions
|
||||
include ActionController::Assertions::DomAssertions
|
||||
include ActionController::Assertions::ModelAssertions
|
||||
include ActionController::Assertions::DeprecatedAssertions
|
||||
end
|
||||
end
|
||||
|
||||
# Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial,
|
||||
# such that assert_redirected_to(:controller => "weblog") will also match the redirection of
|
||||
# redirect_to(:controller => "weblog", :action => "show") and so on.
|
||||
def assert_redirected_to(options = {}, message=nil)
|
||||
clean_backtrace do
|
||||
assert_response(:redirect, message)
|
||||
|
||||
if options.is_a?(String)
|
||||
msg = build_message(message, "expected a redirect to <?>, found one to <?>", options, @response.redirect_url)
|
||||
url_regexp = %r{^(\w+://.*?(/|$|\?))(.*)$}
|
||||
eurl, epath, url, path = [options, @response.redirect_url].collect do |url|
|
||||
u, p = (url_regexp =~ url) ? [$1, $3] : [nil, url]
|
||||
[u, (p[0..0] == '/') ? p : '/' + p]
|
||||
end.flatten
|
||||
|
||||
assert_equal(eurl, url, msg) if eurl && url
|
||||
assert_equal(epath, path, msg) if epath && path
|
||||
else
|
||||
@response_diff = options.diff(@response.redirected_to) if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
|
||||
msg = build_message(message, "response is not a redirection to all of the options supplied (redirection is <?>)#{', difference: <?>' if @response_diff}",
|
||||
@response.redirected_to || @response.redirect_url, @response_diff)
|
||||
|
||||
assert_block(msg) do
|
||||
if options.is_a?(Symbol)
|
||||
@response.redirected_to == options
|
||||
else
|
||||
options.keys.all? do |k|
|
||||
if k == :controller then options[k] == ActionController::Routing.controller_relative_to(@response.redirected_to[k], @controller.class.controller_path)
|
||||
else options[k] == (@response.redirected_to[k].respond_to?(:to_param) ? @response.redirected_to[k].to_param : @response.redirected_to[k] unless @response.redirected_to[k].nil?)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that the request was rendered with the appropriate template file.
|
||||
def assert_template(expected = nil, message=nil)
|
||||
clean_backtrace do
|
||||
rendered = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file
|
||||
msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
|
||||
assert_block(msg) do
|
||||
if expected.nil?
|
||||
!@response.rendered_with_file?
|
||||
else
|
||||
expected == rendered
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that the routing of the given path was handled correctly and that the parsed options match.
|
||||
def assert_recognizes(expected_options, path, extras={}, message=nil)
|
||||
clean_backtrace do
|
||||
path = "/#{path}" unless path[0..0] == '/'
|
||||
# Load routes.rb if it hasn't been loaded.
|
||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
||||
|
||||
# Assume given controller
|
||||
request = ActionController::TestRequest.new({}, {}, nil)
|
||||
request.path = path
|
||||
ActionController::Routing::Routes.recognize!(request)
|
||||
|
||||
expected_options = expected_options.clone
|
||||
extras.each_key { |key| expected_options.delete key } unless extras.nil?
|
||||
|
||||
expected_options.stringify_keys!
|
||||
msg = build_message(message, "The recognized options <?> did not match <?>",
|
||||
request.path_parameters, expected_options)
|
||||
assert_block(msg) { request.path_parameters == expected_options }
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that the provided options can be used to generate the provided path.
|
||||
def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
|
||||
clean_backtrace do
|
||||
expected_path = "/#{expected_path}" unless expected_path[0] == ?/
|
||||
# Load routes.rb if it hasn't been loaded.
|
||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
||||
|
||||
generated_path, extra_keys = ActionController::Routing::Routes.generate(options, extras)
|
||||
found_extras = options.reject {|k, v| ! extra_keys.include? k}
|
||||
|
||||
msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
|
||||
assert_block(msg) { found_extras == extras }
|
||||
|
||||
msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
|
||||
expected_path)
|
||||
assert_block(msg) { expected_path == generated_path }
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that path and options match both ways; in other words, the URL generated from
|
||||
# options is the same as path, and also that the options recognized from path are the same as options
|
||||
def assert_routing(path, options, defaults={}, extras={}, message=nil)
|
||||
assert_recognizes(options, path, extras, message)
|
||||
|
||||
controller, default_controller = options[:controller], defaults[:controller]
|
||||
if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
|
||||
options[:controller] = "/#{controller}"
|
||||
end
|
||||
|
||||
assert_generates(path, options, defaults, extras, message)
|
||||
end
|
||||
|
||||
# Asserts that there is a tag/node/element in the body of the response
|
||||
# that meets all of the given conditions. The +conditions+ parameter must
|
||||
# be a hash of any of the following keys (all are optional):
|
||||
#
|
||||
# * <tt>:tag</tt>: the node type must match the corresponding value
|
||||
# * <tt>:attributes</tt>: a hash. The node's attributes must match the
|
||||
# corresponding values in the hash.
|
||||
# * <tt>:parent</tt>: a hash. The node's parent must match the
|
||||
# corresponding hash.
|
||||
# * <tt>:child</tt>: a hash. At least one of the node's immediate children
|
||||
# must meet the criteria described by the hash.
|
||||
# * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
|
||||
# meet the criteria described by the hash.
|
||||
# * <tt>:descendant</tt>: a hash. At least one of the node's descendants
|
||||
# must meet the criteria described by the hash.
|
||||
# * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
|
||||
# meet the criteria described by the hash.
|
||||
# * <tt>:after</tt>: a hash. The node must be after any sibling meeting
|
||||
# the criteria described by the hash, and at least one sibling must match.
|
||||
# * <tt>:before</tt>: a hash. The node must be before any sibling meeting
|
||||
# the criteria described by the hash, and at least one sibling must match.
|
||||
# * <tt>:children</tt>: a hash, for counting children of a node. Accepts
|
||||
# the keys:
|
||||
# * <tt>:count</tt>: either a number or a range which must equal (or
|
||||
# include) the number of children that match.
|
||||
# * <tt>:less_than</tt>: the number of matching children must be less
|
||||
# than this number.
|
||||
# * <tt>:greater_than</tt>: the number of matching children must be
|
||||
# greater than this number.
|
||||
# * <tt>:only</tt>: another hash consisting of the keys to use
|
||||
# to match on the children, and only matching children will be
|
||||
# counted.
|
||||
# * <tt>:content</tt>: the textual content of the node must match the
|
||||
# given value. This will not match HTML tags in the body of a
|
||||
# tag--only text.
|
||||
#
|
||||
# Conditions are matched using the following algorithm:
|
||||
#
|
||||
# * if the condition is a string, it must be a substring of the value.
|
||||
# * if the condition is a regexp, it must match the value.
|
||||
# * if the condition is a number, the value must match number.to_s.
|
||||
# * if the condition is +true+, the value must not be +nil+.
|
||||
# * if the condition is +false+ or +nil+, the value must be +nil+.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# # assert that there is a "span" tag
|
||||
# assert_tag :tag => "span"
|
||||
#
|
||||
# # assert that there is a "span" tag with id="x"
|
||||
# assert_tag :tag => "span", :attributes => { :id => "x" }
|
||||
#
|
||||
# # assert that there is a "span" tag using the short-hand
|
||||
# assert_tag :span
|
||||
#
|
||||
# # assert that there is a "span" tag with id="x" using the short-hand
|
||||
# assert_tag :span, :attributes => { :id => "x" }
|
||||
#
|
||||
# # assert that there is a "span" inside of a "div"
|
||||
# assert_tag :tag => "span", :parent => { :tag => "div" }
|
||||
#
|
||||
# # assert that there is a "span" somewhere inside a table
|
||||
# assert_tag :tag => "span", :ancestor => { :tag => "table" }
|
||||
#
|
||||
# # assert that there is a "span" with at least one "em" child
|
||||
# assert_tag :tag => "span", :child => { :tag => "em" }
|
||||
#
|
||||
# # assert that there is a "span" containing a (possibly nested)
|
||||
# # "strong" tag.
|
||||
# assert_tag :tag => "span", :descendant => { :tag => "strong" }
|
||||
#
|
||||
# # assert that there is a "span" containing between 2 and 4 "em" tags
|
||||
# # as immediate children
|
||||
# assert_tag :tag => "span",
|
||||
# :children => { :count => 2..4, :only => { :tag => "em" } }
|
||||
#
|
||||
# # get funky: assert that there is a "div", with an "ul" ancestor
|
||||
# # and an "li" parent (with "class" = "enum"), and containing a
|
||||
# # "span" descendant that contains text matching /hello world/
|
||||
# assert_tag :tag => "div",
|
||||
# :ancestor => { :tag => "ul" },
|
||||
# :parent => { :tag => "li",
|
||||
# :attributes => { :class => "enum" } },
|
||||
# :descendant => { :tag => "span",
|
||||
# :child => /hello world/ }
|
||||
#
|
||||
# <strong>Please note</strong: #assert_tag and #assert_no_tag only work
|
||||
# with well-formed XHTML. They recognize a few tags as implicitly self-closing
|
||||
# (like br and hr and such) but will not work correctly with tags
|
||||
# that allow optional closing tags (p, li, td). <em>You must explicitly
|
||||
# close all of your tags to use these assertions.</em>
|
||||
def assert_tag(*opts)
|
||||
clean_backtrace do
|
||||
opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
|
||||
tag = find_tag(opts)
|
||||
assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
# Identical to #assert_tag, but asserts that a matching tag does _not_
|
||||
# exist. (See #assert_tag for a full discussion of the syntax.)
|
||||
def assert_no_tag(*opts)
|
||||
clean_backtrace do
|
||||
opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
|
||||
tag = find_tag(opts)
|
||||
assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
# test 2 html strings to be equivalent, i.e. identical up to reordering of attributes
|
||||
def assert_dom_equal(expected, actual, message="")
|
||||
clean_backtrace do
|
||||
expected_dom = HTML::Document.new(expected).root
|
||||
actual_dom = HTML::Document.new(actual).root
|
||||
full_message = build_message(message, "<?> expected to be == to\n<?>.", expected_dom.to_s, actual_dom.to_s)
|
||||
assert_block(full_message) { expected_dom == actual_dom }
|
||||
end
|
||||
end
|
||||
|
||||
# negated form of +assert_dom_equivalent+
|
||||
def assert_dom_not_equal(expected, actual, message="")
|
||||
clean_backtrace do
|
||||
expected_dom = HTML::Document.new(expected).root
|
||||
actual_dom = HTML::Document.new(actual).root
|
||||
full_message = build_message(message, "<?> expected to be != to\n<?>.", expected_dom.to_s, actual_dom.to_s)
|
||||
assert_block(full_message) { expected_dom != actual_dom }
|
||||
end
|
||||
end
|
||||
|
||||
# ensures that the passed record is valid by active record standards. returns the error messages if not
|
||||
def assert_valid(record)
|
||||
clean_backtrace do
|
||||
assert record.valid?, record.errors.full_messages.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
def clean_backtrace(&block)
|
||||
yield
|
||||
rescue AssertionFailedError => e
|
||||
path = File.expand_path(__FILE__)
|
||||
raise AssertionFailedError, e.message, e.backtrace.reject { |line| File.expand_path(line) =~ /#{path}/ }
|
||||
end
|
||||
def clean_backtrace(&block)
|
||||
yield
|
||||
rescue Test::Unit::AssertionFailedError => e
|
||||
path = File.expand_path(__FILE__)
|
||||
raise Test::Unit::AssertionFailedError, e.message, e.backtrace.reject { |line| File.expand_path(line) =~ /#{path}/ }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require File.dirname(__FILE__) + '/assertions/response_assertions'
|
||||
require File.dirname(__FILE__) + '/assertions/selector_assertions'
|
||||
require File.dirname(__FILE__) + '/assertions/tag_assertions'
|
||||
require File.dirname(__FILE__) + '/assertions/dom_assertions'
|
||||
require File.dirname(__FILE__) + '/assertions/routing_assertions'
|
||||
require File.dirname(__FILE__) + '/assertions/model_assertions'
|
||||
require File.dirname(__FILE__) + '/assertions/deprecated_assertions'
|
||||
|
||||
module Test #:nodoc:
|
||||
module Unit #:nodoc:
|
||||
class TestCase #:nodoc:
|
||||
include ActionController::Assertions
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load diff
|
@ -8,11 +8,8 @@ module ActionController #:nodoc:
|
|||
base.extend(ClassMethods)
|
||||
|
||||
base.class_eval do
|
||||
alias_method :perform_action_without_benchmark, :perform_action
|
||||
alias_method :perform_action, :perform_action_with_benchmark
|
||||
|
||||
alias_method :render_without_benchmark, :render
|
||||
alias_method :render, :render_with_benchmark
|
||||
alias_method_chain :perform_action, :benchmark
|
||||
alias_method_chain :render, :benchmark
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -68,7 +65,7 @@ module ActionController #:nodoc:
|
|||
else
|
||||
runtime = [Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001].max
|
||||
log_message = "Completed in #{sprintf("%.5f", runtime)} (#{(1 / runtime).floor} reqs/sec)"
|
||||
log_message << rendering_runtime(runtime) if @rendering_runtime
|
||||
log_message << rendering_runtime(runtime) if defined?(@rendering_runtime)
|
||||
log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
log_message << " | #{headers["Status"]}"
|
||||
log_message << " [#{complete_request_uri rescue "unknown"}]"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'fileutils'
|
||||
require 'uri'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
# Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
|
||||
|
@ -117,24 +118,24 @@ module ActionController #:nodoc:
|
|||
return unless perform_caching
|
||||
if options[:action].is_a?(Array)
|
||||
options[:action].dup.each do |action|
|
||||
self.class.expire_page(url_for(options.merge({ :only_path => true, :skip_relative_url_root => true, :action => action })))
|
||||
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action)))
|
||||
end
|
||||
else
|
||||
self.class.expire_page(url_for(options.merge({ :only_path => true, :skip_relative_url_root => true })))
|
||||
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true)))
|
||||
end
|
||||
end
|
||||
|
||||
# Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of @response.body is used
|
||||
# Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used
|
||||
# If no options are provided, the current +options+ for this action is used. Example:
|
||||
# cache_page "I'm the cached content", :controller => "lists", :action => "show"
|
||||
def cache_page(content = nil, options = {})
|
||||
return unless perform_caching && caching_allowed
|
||||
self.class.cache_page(content || @response.body, url_for(options.merge({ :only_path => true, :skip_relative_url_root => true })))
|
||||
self.class.cache_page(content || response.body, url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format])))
|
||||
end
|
||||
|
||||
private
|
||||
def caching_allowed
|
||||
!@request.post? && @response.headers['Status'] && @response.headers['Status'].to_i < 400
|
||||
request.get? && response.headers['Status'].to_i == 200
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -155,9 +156,12 @@ module ActionController #:nodoc:
|
|||
# the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named
|
||||
# "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and
|
||||
# "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern.
|
||||
#
|
||||
# Different representations of the same resource, e.g. <tt>http://david.somewhere.com/lists</tt> and <tt>http://david.somewhere.com/lists.xml</tt>
|
||||
# are treated like separate requests and so are cached separately. Keep in mind when expiring an action cache that <tt>:action => 'lists'</tt> is not the same
|
||||
# as <tt>:action => 'list', :format => :xml</tt>.
|
||||
module Actions
|
||||
def self.append_features(base) #:nodoc:
|
||||
super
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
base.send(:attr_accessor, :rendered_action_cache)
|
||||
end
|
||||
|
@ -173,22 +177,24 @@ module ActionController #:nodoc:
|
|||
return unless perform_caching
|
||||
if options[:action].is_a?(Array)
|
||||
options[:action].dup.each do |action|
|
||||
expire_fragment(url_for(options.merge({ :action => action })).split("://").last)
|
||||
expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action })))
|
||||
end
|
||||
else
|
||||
expire_fragment(url_for(options).split("://").last)
|
||||
expire_fragment(ActionCachePath.path_for(self, options))
|
||||
end
|
||||
end
|
||||
|
||||
class ActionCacheFilter #:nodoc:
|
||||
def initialize(*actions)
|
||||
def initialize(*actions, &block)
|
||||
@actions = actions
|
||||
end
|
||||
|
||||
def before(controller)
|
||||
return unless @actions.include?(controller.action_name.intern)
|
||||
if cache = controller.read_fragment(controller.url_for.split("://").last)
|
||||
action_cache_path = ActionCachePath.new(controller)
|
||||
if cache = controller.read_fragment(action_cache_path.path)
|
||||
controller.rendered_action_cache = true
|
||||
set_content_type!(action_cache_path)
|
||||
controller.send(:render_text, cache)
|
||||
false
|
||||
end
|
||||
|
@ -196,8 +202,60 @@ module ActionController #:nodoc:
|
|||
|
||||
def after(controller)
|
||||
return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache
|
||||
controller.write_fragment(controller.url_for.split("://").last, controller.response.body)
|
||||
controller.write_fragment(ActionCachePath.path_for(controller), controller.response.body)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_content_type!(action_cache_path)
|
||||
if extention = action_cache_path.extension
|
||||
content_type = Mime::EXTENSION_LOOKUP[extention]
|
||||
action_cache_path.controller.response.content_type = content_type.to_s
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ActionCachePath
|
||||
attr_reader :controller, :options
|
||||
|
||||
class << self
|
||||
def path_for(*args, &block)
|
||||
new(*args).path
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(controller, options = {})
|
||||
@controller = controller
|
||||
@options = options
|
||||
end
|
||||
|
||||
def path
|
||||
return @path if @path
|
||||
@path = controller.url_for(options).split('://').last
|
||||
normalize!
|
||||
add_extension!
|
||||
URI.unescape(@path)
|
||||
end
|
||||
|
||||
def extension
|
||||
@extension ||= extract_extension(controller.request.path)
|
||||
end
|
||||
|
||||
private
|
||||
def normalize!
|
||||
@path << 'index' if @path.last == '/'
|
||||
end
|
||||
|
||||
def add_extension!
|
||||
@path << ".#{extension}" if extension
|
||||
end
|
||||
|
||||
def extract_extension(file_path)
|
||||
# Don't want just what comes after the last '.' to accomodate multi part extensions
|
||||
# such as tar.gz.
|
||||
file_path[/^[^.]+\.(.+)$/, 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -208,7 +266,7 @@ module ActionController #:nodoc:
|
|||
# <b>Hello <%= @name %></b>
|
||||
# <% cache do %>
|
||||
# All the topics in the system:
|
||||
# <%= render_collection_of_partials "topic", Topic.find_all %>
|
||||
# <%= render :partial => "topic", :collection => Topic.find(:all) %>
|
||||
# <% end %>
|
||||
#
|
||||
# This cache will bind to the name of action that called it. So you would be able to invalidate it using
|
||||
|
@ -246,8 +304,7 @@ module ActionController #:nodoc:
|
|||
# ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost"
|
||||
# ActionController::Base.fragment_cache_store = MyOwnStore.new("parameter")
|
||||
module Fragments
|
||||
def self.append_features(base) #:nodoc:
|
||||
super
|
||||
def self.included(base) #:nodoc:
|
||||
base.class_eval do
|
||||
@@fragment_cache_store = MemoryStore.new
|
||||
cattr_reader :fragment_cache_store
|
||||
|
@ -306,7 +363,12 @@ module ActionController #:nodoc:
|
|||
# Name can take one of three forms:
|
||||
# * String: This would normally take the form of a path like "pages/45/notes"
|
||||
# * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
|
||||
# * Regexp: Will destroy all the matched fragments, example: %r{pages/\d*/notes} Ensure you do not specify start and finish in the regex (^$) because the actual filename matched looks like ./cache/filename/path.cache
|
||||
# * Regexp: Will destroy all the matched fragments, example:
|
||||
# %r{pages/\d*/notes}
|
||||
# Ensure you do not specify start and finish in the regex (^$) because
|
||||
# the actual filename matched looks like ./cache/filename/path.cache
|
||||
# Regexp expiration is not supported on caches which can't iterate over
|
||||
# all keys, such as memcached.
|
||||
def expire_fragment(name, options = nil)
|
||||
return unless perform_caching
|
||||
|
||||
|
@ -327,6 +389,7 @@ module ActionController #:nodoc:
|
|||
def expire_matched_fragments(matcher = /.*/, options = nil) #:nodoc:
|
||||
expire_fragment(matcher, options)
|
||||
end
|
||||
deprecate :expire_matched_fragments => :expire_fragment
|
||||
|
||||
|
||||
class UnthreadedMemoryStore #:nodoc:
|
||||
|
@ -430,7 +493,7 @@ module ActionController #:nodoc:
|
|||
if f =~ matcher
|
||||
begin
|
||||
File.delete(f)
|
||||
rescue Object => e
|
||||
rescue SystemCallError => e
|
||||
# If there's no cache, then there's nothing to complain about
|
||||
end
|
||||
end
|
||||
|
@ -493,8 +556,7 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# In the example above, four actions are cached and three actions are responsible for expiring those caches.
|
||||
module Sweeping
|
||||
def self.append_features(base) #:nodoc:
|
||||
super
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
|
@ -503,8 +565,7 @@ module ActionController #:nodoc:
|
|||
return unless perform_caching
|
||||
configuration = sweepers.last.is_a?(Hash) ? sweepers.pop : {}
|
||||
sweepers.each do |sweeper|
|
||||
observer(sweeper)
|
||||
|
||||
ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
|
||||
sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance
|
||||
|
||||
if sweeper_instance.is_a?(Sweeper)
|
||||
|
@ -523,7 +584,7 @@ module ActionController #:nodoc:
|
|||
|
||||
# ActiveRecord::Observer will mark this class as reloadable even though it should not be.
|
||||
# However, subclasses of ActionController::Caching::Sweeper should be Reloadable
|
||||
include Reloadable::Subclasses
|
||||
include Reloadable::Deprecated
|
||||
|
||||
def before(controller)
|
||||
self.controller = controller
|
||||
|
|
|
@ -27,13 +27,6 @@ class CGI #:nodoc:
|
|||
def request_parameters
|
||||
CGIMethods.parse_request_parameters(params, env_table)
|
||||
end
|
||||
|
||||
def redirect(where)
|
||||
header({
|
||||
"Status" => "302 Moved",
|
||||
"location" => "#{where}"
|
||||
})
|
||||
end
|
||||
|
||||
def session(parameters = nil)
|
||||
parameters = {} if parameters.nil?
|
||||
|
|
|
@ -1,217 +1,211 @@
|
|||
require 'cgi'
|
||||
require 'action_controller/vendor/xml_simple'
|
||||
require 'action_controller/vendor/xml_node'
|
||||
require 'strscan'
|
||||
|
||||
# Static methods for parsing the query and request parameters that can be used in
|
||||
# a CGI extension class or testing in isolation.
|
||||
class CGIMethods #:nodoc:
|
||||
public
|
||||
# Returns a hash with the pairs from the query string. The implicit hash construction that is done in
|
||||
# parse_request_params is not done here.
|
||||
def CGIMethods.parse_query_parameters(query_string)
|
||||
parsed_params = {}
|
||||
|
||||
query_string.split(/[&;]/).each { |p|
|
||||
# Ignore repeated delimiters.
|
||||
next if p.empty?
|
||||
class << self
|
||||
# DEPRECATED: Use parse_form_encoded_parameters
|
||||
def parse_query_parameters(query_string)
|
||||
pairs = query_string.split('&').collect do |chunk|
|
||||
next if chunk.empty?
|
||||
key, value = chunk.split('=', 2)
|
||||
next if key.empty?
|
||||
value = (value.nil? || value.empty?) ? nil : CGI.unescape(value)
|
||||
[ CGI.unescape(key), value ]
|
||||
end.compact
|
||||
|
||||
k, v = p.split('=',2)
|
||||
v = nil if (v && v.empty?)
|
||||
|
||||
k = CGI.unescape(k) if k
|
||||
v = CGI.unescape(v) if v
|
||||
|
||||
unless k.include?(?[)
|
||||
parsed_params[k] = v
|
||||
else
|
||||
keys = split_key(k)
|
||||
last_key = keys.pop
|
||||
last_key = keys.pop if (use_array = last_key.empty?)
|
||||
parent = keys.inject(parsed_params) {|h, k| h[k] ||= {}}
|
||||
|
||||
if use_array then (parent[last_key] ||= []) << v
|
||||
else parent[last_key] = v
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
parsed_params
|
||||
FormEncodedPairParser.new(pairs).result
|
||||
end
|
||||
|
||||
# Returns the request (POST/GET) parameters in a parsed form where pairs such as "customer[address][street]" /
|
||||
# "Somewhere cool!" are translated into a full hash hierarchy, like
|
||||
# { "customer" => { "address" => { "street" => "Somewhere cool!" } } }
|
||||
def CGIMethods.parse_request_parameters(params)
|
||||
parsed_params = {}
|
||||
# DEPRECATED: Use parse_form_encoded_parameters
|
||||
def parse_request_parameters(params)
|
||||
parser = FormEncodedPairParser.new
|
||||
|
||||
for key, value in params
|
||||
value = [value] if key =~ /.*\[\]$/
|
||||
unless key.include?('[')
|
||||
# much faster to test for the most common case first (GET)
|
||||
# and avoid the call to build_deep_hash
|
||||
parsed_params[key] = get_typed_value(value[0])
|
||||
else
|
||||
build_deep_hash(get_typed_value(value[0]), parsed_params, get_levels(key))
|
||||
params = params.dup
|
||||
until params.empty?
|
||||
for key, value in params
|
||||
if key.blank?
|
||||
params.delete key
|
||||
elsif !key.include?('[')
|
||||
# much faster to test for the most common case first (GET)
|
||||
# and avoid the call to build_deep_hash
|
||||
parser.result[key] = get_typed_value(value[0])
|
||||
params.delete key
|
||||
elsif value.is_a?(Array)
|
||||
parser.parse(key, get_typed_value(value.shift))
|
||||
params.delete key if value.empty?
|
||||
else
|
||||
raise TypeError, "Expected array, found #{value.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
parsed_params
|
||||
parser.result
|
||||
end
|
||||
|
||||
def self.parse_formatted_request_parameters(mime_type, raw_post_data)
|
||||
params = case strategy = ActionController::Base.param_parsers[mime_type]
|
||||
def parse_formatted_request_parameters(mime_type, raw_post_data)
|
||||
case strategy = ActionController::Base.param_parsers[mime_type]
|
||||
when Proc
|
||||
strategy.call(raw_post_data)
|
||||
when :xml_simple
|
||||
raw_post_data.blank? ? nil :
|
||||
typecast_xml_value(XmlSimple.xml_in(raw_post_data,
|
||||
'forcearray' => false,
|
||||
'forcecontent' => true,
|
||||
'keeproot' => true,
|
||||
'contentkey' => '__content__'))
|
||||
raw_post_data.blank? ? {} : Hash.from_xml(raw_post_data)
|
||||
when :yaml
|
||||
YAML.load(raw_post_data)
|
||||
when :xml_node
|
||||
node = XmlNode.from_xml(raw_post_data)
|
||||
{ node.node_name => node }
|
||||
end
|
||||
|
||||
dasherize_keys(params || {})
|
||||
rescue Object => e
|
||||
rescue Exception => e # YAML, XML or Ruby code block errors
|
||||
{ "exception" => "#{e.message} (#{e.class})", "backtrace" => e.backtrace,
|
||||
"raw_post_data" => raw_post_data, "format" => mime_type }
|
||||
end
|
||||
|
||||
def self.typecast_xml_value(value)
|
||||
case value
|
||||
when Hash
|
||||
if value.has_key?("__content__")
|
||||
content = translate_xml_entities(value["__content__"])
|
||||
case value["type"]
|
||||
when "integer" then content.to_i
|
||||
when "boolean" then content == "true"
|
||||
when "datetime" then Time.parse(content)
|
||||
when "date" then Date.parse(content)
|
||||
else content
|
||||
end
|
||||
else
|
||||
value.empty? ? nil : value.inject({}) do |h,(k,v)|
|
||||
h[k] = typecast_xml_value(v)
|
||||
h
|
||||
end
|
||||
end
|
||||
when Array
|
||||
value.map! { |i| typecast_xml_value(i) }
|
||||
case value.length
|
||||
when 0 then nil
|
||||
when 1 then value.first
|
||||
else value
|
||||
end
|
||||
else
|
||||
raise "can't typecast #{value.inspect}"
|
||||
end
|
||||
end
|
||||
private
|
||||
def get_typed_value(value)
|
||||
case value
|
||||
when String
|
||||
value
|
||||
when NilClass
|
||||
''
|
||||
when Array
|
||||
value.map { |v| get_typed_value(v) }
|
||||
else
|
||||
# Uploaded file provides content type and filename.
|
||||
if value.respond_to?(:content_type) &&
|
||||
!value.content_type.blank? &&
|
||||
!value.original_filename.blank?
|
||||
unless value.respond_to?(:full_original_filename)
|
||||
class << value
|
||||
alias_method :full_original_filename, :original_filename
|
||||
|
||||
private
|
||||
|
||||
def self.translate_xml_entities(value)
|
||||
value.gsub(/</, "<").
|
||||
gsub(/>/, ">").
|
||||
gsub(/"/, '"').
|
||||
gsub(/'/, "'").
|
||||
gsub(/&/, "&")
|
||||
end
|
||||
|
||||
def self.dasherize_keys(params)
|
||||
case params.class.to_s
|
||||
when "Hash"
|
||||
params.inject({}) do |h,(k,v)|
|
||||
h[k.to_s.tr("-", "_")] = dasherize_keys(v)
|
||||
h
|
||||
end
|
||||
when "Array"
|
||||
params.map { |v| dasherize_keys(v) }
|
||||
else
|
||||
params
|
||||
end
|
||||
end
|
||||
|
||||
# Splits the given key into several pieces. Example keys are 'name', 'person[name]',
|
||||
# 'person[name][first]', and 'people[]'. In each instance, an Array instance is returned.
|
||||
# 'person[name][first]' produces ['person', 'name', 'first']; 'people[]' produces ['people', '']
|
||||
def CGIMethods.split_key(key)
|
||||
if /^([^\[]+)((?:\[[^\]]*\])+)$/ =~ key
|
||||
keys = [$1]
|
||||
|
||||
keys.concat($2[1..-2].split(']['))
|
||||
keys << '' if key[-2..-1] == '[]' # Have to add it since split will drop empty strings
|
||||
|
||||
keys
|
||||
else
|
||||
[key]
|
||||
end
|
||||
end
|
||||
|
||||
def CGIMethods.get_typed_value(value)
|
||||
# test most frequent case first
|
||||
if value.is_a?(String)
|
||||
value
|
||||
elsif value.respond_to?(:content_type) && ! value.content_type.blank?
|
||||
# Uploaded file
|
||||
unless value.respond_to?(:full_original_filename)
|
||||
class << value
|
||||
alias_method :full_original_filename, :original_filename
|
||||
|
||||
# Take the basename of the upload's original filename.
|
||||
# This handles the full Windows paths given by Internet Explorer
|
||||
# (and perhaps other broken user agents) without affecting
|
||||
# those which give the lone filename.
|
||||
# The Windows regexp is adapted from Perl's File::Basename.
|
||||
def original_filename
|
||||
if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
|
||||
md.captures.first
|
||||
else
|
||||
File.basename full_original_filename
|
||||
# Take the basename of the upload's original filename.
|
||||
# This handles the full Windows paths given by Internet Explorer
|
||||
# (and perhaps other broken user agents) without affecting
|
||||
# those which give the lone filename.
|
||||
# The Windows regexp is adapted from Perl's File::Basename.
|
||||
def original_filename
|
||||
if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
|
||||
md.captures.first
|
||||
else
|
||||
File.basename full_original_filename
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Return the same value after overriding original_filename.
|
||||
value
|
||||
|
||||
# Multipart values may have content type, but no filename.
|
||||
elsif value.respond_to?(:read)
|
||||
result = value.read
|
||||
value.rewind
|
||||
result
|
||||
|
||||
# Unknown value, neither string nor multipart.
|
||||
else
|
||||
raise "Unknown form value: #{value.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class FormEncodedPairParser < StringScanner #:nodoc:
|
||||
attr_reader :top, :parent, :result
|
||||
|
||||
def initialize(pairs = [])
|
||||
super('')
|
||||
@result = {}
|
||||
pairs.each { |key, value| parse(key, value) }
|
||||
end
|
||||
|
||||
KEY_REGEXP = %r{([^\[\]=&]+)}
|
||||
BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
|
||||
|
||||
# Parse the query string
|
||||
def parse(key, value)
|
||||
self.string = key
|
||||
@top, @parent = result, nil
|
||||
|
||||
# First scan the bare key
|
||||
key = scan(KEY_REGEXP) or return
|
||||
key = post_key_check(key)
|
||||
|
||||
# Then scan as many nestings as present
|
||||
until eos?
|
||||
r = scan(BRACKETED_KEY_REGEXP) or return
|
||||
key = self[1]
|
||||
key = post_key_check(key)
|
||||
end
|
||||
|
||||
bind(key, value)
|
||||
end
|
||||
|
||||
private
|
||||
# After we see a key, we must look ahead to determine our next action. Cases:
|
||||
#
|
||||
# [] follows the key. Then the value must be an array.
|
||||
# = follows the key. (A value comes next)
|
||||
# & or the end of string follows the key. Then the key is a flag.
|
||||
# otherwise, a hash follows the key.
|
||||
def post_key_check(key)
|
||||
if scan(/\[\]/) # a[b][] indicates that b is an array
|
||||
container(key, Array)
|
||||
nil
|
||||
elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
|
||||
container(key, Hash)
|
||||
nil
|
||||
else # End of key? We do nothing.
|
||||
key
|
||||
end
|
||||
end
|
||||
|
||||
# Add a container to the stack.
|
||||
#
|
||||
def container(key, klass)
|
||||
type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
|
||||
value = bind(key, klass.new)
|
||||
type_conflict! klass, value unless value.is_a?(klass)
|
||||
push(value)
|
||||
end
|
||||
|
||||
# Push a value onto the 'stack', which is actually only the top 2 items.
|
||||
def push(value)
|
||||
@parent, @top = @top, value
|
||||
end
|
||||
|
||||
# Bind a key (which may be nil for items in an array) to the provided value.
|
||||
def bind(key, value)
|
||||
if top.is_a? Array
|
||||
if key
|
||||
if top[-1].is_a?(Hash) && ! top[-1].key?(key)
|
||||
top[-1][key] = value
|
||||
else
|
||||
top << {key => value}.with_indifferent_access
|
||||
push top.last
|
||||
end
|
||||
else
|
||||
top << value
|
||||
end
|
||||
elsif top.is_a? Hash
|
||||
key = CGI.unescape(key)
|
||||
parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
|
||||
return top[key] ||= value
|
||||
else
|
||||
raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
|
||||
end
|
||||
|
||||
# Return the same value after overriding original_filename.
|
||||
value
|
||||
|
||||
elsif value.respond_to?(:read)
|
||||
# Value as part of a multipart request
|
||||
value.read
|
||||
elsif value.class == Array
|
||||
value.collect { |v| CGIMethods.get_typed_value(v) }
|
||||
else
|
||||
# other value (neither string nor a multipart request)
|
||||
value.to_s
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/
|
||||
def CGIMethods.get_levels(key)
|
||||
all, main, bracketed, trailing = PARAMS_HASH_RE.match(key).to_a
|
||||
if main.nil?
|
||||
[]
|
||||
elsif trailing
|
||||
[key]
|
||||
elsif bracketed
|
||||
[main] + bracketed.slice(1...-1).split('][')
|
||||
else
|
||||
[main]
|
||||
end
|
||||
end
|
||||
|
||||
def CGIMethods.build_deep_hash(value, hash, levels)
|
||||
if levels.length == 0
|
||||
value
|
||||
elsif hash.nil?
|
||||
{ levels.first => CGIMethods.build_deep_hash(value, nil, levels[1..-1]) }
|
||||
else
|
||||
hash.update({ levels.first => CGIMethods.build_deep_hash(value, hash[levels.first], levels[1..-1]) })
|
||||
|
||||
def type_conflict!(klass, value)
|
||||
raise TypeError,
|
||||
"Conflicting types for parameter containers. " +
|
||||
"Expected an instance of #{klass}, but found an instance of #{value.class}. " +
|
||||
"This can be caused by passing Array and Hash based paramters qs[]=value&qs[key]=value. "
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,27 +1,48 @@
|
|||
class CGI #:nodoc:
|
||||
# Add @request.env['RAW_POST_DATA'] for the vegans.
|
||||
module QueryExtension
|
||||
# Initialize the data from the query.
|
||||
#
|
||||
# Handles multipart forms (in particular, forms that involve file uploads).
|
||||
# Reads query parameters in the @params field, and cookies into @cookies.
|
||||
def initialize_query()
|
||||
def initialize_query
|
||||
@cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])
|
||||
|
||||
#fix some strange request environments
|
||||
# Fix some strange request environments.
|
||||
if method = env_table['REQUEST_METHOD']
|
||||
method = method.to_s.downcase.intern
|
||||
else
|
||||
method = :get
|
||||
end
|
||||
|
||||
if method == :post && (boundary = multipart_form_boundary)
|
||||
@multipart = true
|
||||
@params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH']))
|
||||
else
|
||||
@multipart = false
|
||||
@params = CGI::parse(read_query_params(method) || "")
|
||||
# POST assumes missing Content-Type is application/x-www-form-urlencoded.
|
||||
content_type = env_table['CONTENT_TYPE']
|
||||
if content_type.blank? && method == :post
|
||||
content_type = 'application/x-www-form-urlencoded'
|
||||
end
|
||||
|
||||
# Force content length to zero if missing.
|
||||
content_length = env_table['CONTENT_LENGTH'].to_i
|
||||
|
||||
# Set multipart to false by default.
|
||||
@multipart = false
|
||||
|
||||
# POST and PUT may have params in entity body. If content type is
|
||||
# missing for POST, assume urlencoded. If content type is missing
|
||||
# for PUT, don't assume anything and don't parse the parameters:
|
||||
# it's likely binary data.
|
||||
#
|
||||
# The other HTTP methods have their params in the query string.
|
||||
if method == :post || method == :put
|
||||
if boundary = extract_multipart_form_boundary(content_type)
|
||||
@multipart = true
|
||||
@params = read_multipart(boundary, content_length)
|
||||
elsif content_type.blank? || content_type !~ %r{application/x-www-form-urlencoded}i
|
||||
read_params(method, content_length)
|
||||
@params = {}
|
||||
end
|
||||
end
|
||||
|
||||
@params ||= CGI.parse(read_params(method, content_length))
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -29,16 +50,16 @@ class CGI #:nodoc:
|
|||
MULTIPART_FORM_BOUNDARY_RE = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n #"
|
||||
end
|
||||
|
||||
def multipart_form_boundary
|
||||
MULTIPART_FORM_BOUNDARY_RE.match(env_table['CONTENT_TYPE']).to_a.pop
|
||||
def extract_multipart_form_boundary(content_type)
|
||||
MULTIPART_FORM_BOUNDARY_RE.match(content_type).to_a.pop
|
||||
end
|
||||
|
||||
if defined? MOD_RUBY
|
||||
def read_params_from_query
|
||||
def read_query
|
||||
Apache::request.args || ''
|
||||
end
|
||||
else
|
||||
def read_params_from_query
|
||||
def read_query
|
||||
# fixes CGI querystring parsing for lighttpd
|
||||
env_qs = env_table['QUERY_STRING']
|
||||
if env_qs.blank? && !(uri = env_table['REQUEST_URI']).blank?
|
||||
|
@ -49,25 +70,25 @@ class CGI #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
def read_params_from_post
|
||||
def read_body(content_length)
|
||||
stdinput.binmode if stdinput.respond_to?(:binmode)
|
||||
content = stdinput.read(Integer(env_table['CONTENT_LENGTH'])) || ''
|
||||
# fix for Safari Ajax postings that always append \000
|
||||
content = stdinput.read(content_length) || ''
|
||||
# Fix for Safari Ajax postings that always append \000
|
||||
content.chop! if content[-1] == 0
|
||||
content.gsub! /&_=$/, ''
|
||||
content.gsub!(/&_=$/, '')
|
||||
env_table['RAW_POST_DATA'] = content.freeze
|
||||
end
|
||||
|
||||
def read_query_params(method)
|
||||
def read_params(method, content_length)
|
||||
case method
|
||||
when :get
|
||||
read_params_from_query
|
||||
read_query
|
||||
when :post, :put
|
||||
read_params_from_post
|
||||
read_body(content_length)
|
||||
when :cmd
|
||||
read_from_cmdline
|
||||
else # when :head, :delete, :options
|
||||
read_params_from_query
|
||||
else # :head, :delete, :options, :trace, :connect
|
||||
read_query
|
||||
end
|
||||
end
|
||||
end # module QueryExtension
|
||||
|
|
|
@ -8,13 +8,13 @@ module ActionController #:nodoc:
|
|||
# sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
|
||||
#
|
||||
# * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
|
||||
# (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
|
||||
# (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
|
||||
# lib/action_controller/session.
|
||||
# * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
|
||||
# * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ parameter
|
||||
# of the request, or automatically generated for a new session.
|
||||
# * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
|
||||
# exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
|
||||
# exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
|
||||
# an ArgumentError is raised.
|
||||
# * <tt>:session_expires</tt> - the time the current session expires, as a +Time+ object. If not set, the session will continue
|
||||
# indefinitely.
|
||||
|
@ -22,10 +22,10 @@ module ActionController #:nodoc:
|
|||
# server.
|
||||
# * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
|
||||
# * <tt>:session_path</tt> - the path for which this session applies. Defaults to the directory of the CGI script.
|
||||
def self.process_cgi(cgi = CGI.new, session_options = {})
|
||||
def self.process_cgi(cgi = CGI.new, session_options = {})
|
||||
new.process_cgi(cgi, session_options)
|
||||
end
|
||||
|
||||
|
||||
def process_cgi(cgi, session_options = {}) #:nodoc:
|
||||
process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
|
||||
end
|
||||
|
@ -51,7 +51,7 @@ module ActionController #:nodoc:
|
|||
if (qs = @cgi.query_string) && !qs.empty?
|
||||
qs
|
||||
elsif uri = @env['REQUEST_URI']
|
||||
parts = uri.split('?')
|
||||
parts = uri.split('?')
|
||||
parts.shift
|
||||
parts.join('?')
|
||||
else
|
||||
|
@ -60,7 +60,8 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def query_parameters
|
||||
(qs = self.query_string).empty? ? {} : CGIMethods.parse_query_parameters(qs)
|
||||
@query_parameters ||=
|
||||
(qs = self.query_string).empty? ? {} : CGIMethods.parse_query_parameters(qs)
|
||||
end
|
||||
|
||||
def request_parameters
|
||||
|
@ -71,7 +72,7 @@ module ActionController #:nodoc:
|
|||
CGIMethods.parse_request_parameters(@cgi.params)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def cookies
|
||||
@cgi.cookies.freeze
|
||||
end
|
||||
|
@ -101,15 +102,26 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def session
|
||||
unless @session
|
||||
unless defined?(@session)
|
||||
if @session_options == false
|
||||
@session = Hash.new
|
||||
else
|
||||
stale_session_check! do
|
||||
if session_options_with_string_keys['new_session'] == true
|
||||
@session = new_session
|
||||
else
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
case value = session_options_with_string_keys['new_session']
|
||||
when true
|
||||
@session = new_session
|
||||
when false
|
||||
begin
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
# CGI::Session raises ArgumentError if 'new_session' == false
|
||||
# and no session cookie or query param is present.
|
||||
rescue ArgumentError
|
||||
@session = Hash.new
|
||||
end
|
||||
when nil
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
else
|
||||
raise ArgumentError, "Invalid new_session option: #{value}"
|
||||
end
|
||||
@session['__valid_session']
|
||||
end
|
||||
|
@ -119,7 +131,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def reset_session
|
||||
@session.delete if CGI::Session === @session
|
||||
@session.delete if defined?(@session) && @session.is_a?(CGI::Session)
|
||||
@session = new_session
|
||||
end
|
||||
|
||||
|
@ -141,11 +153,11 @@ module ActionController #:nodoc:
|
|||
def stale_session_check!
|
||||
yield
|
||||
rescue ArgumentError => argument_error
|
||||
if argument_error.message =~ %r{undefined class/module (\w+)}
|
||||
if argument_error.message =~ %r{undefined class/module ([\w:]+)}
|
||||
begin
|
||||
Module.const_missing($1)
|
||||
rescue LoadError, NameError => const_error
|
||||
raise ActionController::SessionRestoreError, <<end_msg
|
||||
raise ActionController::SessionRestoreError, <<-end_msg
|
||||
Session contains objects whose class definition isn\'t available.
|
||||
Remember to require the classes for all objects kept in the session.
|
||||
(Original exception: #{const_error.message} [#{const_error.class}])
|
||||
|
@ -159,7 +171,7 @@ end_msg
|
|||
end
|
||||
|
||||
def session_options_with_string_keys
|
||||
@session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).inject({}) { |options, (k,v)| options[k.to_s] = v; options }
|
||||
@session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -170,38 +182,49 @@ end_msg
|
|||
end
|
||||
|
||||
def out(output = $stdout)
|
||||
convert_content_type!(@headers)
|
||||
convert_content_type!
|
||||
set_content_length!
|
||||
|
||||
output.binmode if output.respond_to?(:binmode)
|
||||
output.sync = false if output.respond_to?(:sync=)
|
||||
|
||||
|
||||
begin
|
||||
output.write(@cgi.header(@headers))
|
||||
|
||||
if @cgi.send(:env_table)['REQUEST_METHOD'] == 'HEAD'
|
||||
return
|
||||
elsif @body.respond_to?(:call)
|
||||
# Flush the output now in case the @body Proc uses
|
||||
# #syswrite.
|
||||
output.flush if output.respond_to?(:flush)
|
||||
@body.call(self, output)
|
||||
else
|
||||
output.write(@body)
|
||||
end
|
||||
|
||||
output.flush if output.respond_to?(:flush)
|
||||
rescue Errno::EPIPE => e
|
||||
# lost connection to the FCGI process -- ignore the output, then
|
||||
rescue Errno::EPIPE, Errno::ECONNRESET
|
||||
# lost connection to parent process, ignore output
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def convert_content_type!(headers)
|
||||
if header = headers.delete("Content-Type")
|
||||
headers["type"] = header
|
||||
def convert_content_type!
|
||||
if content_type = @headers.delete("Content-Type")
|
||||
@headers["type"] = content_type
|
||||
end
|
||||
if header = headers.delete("Content-type")
|
||||
headers["type"] = header
|
||||
if content_type = @headers.delete("Content-type")
|
||||
@headers["type"] = content_type
|
||||
end
|
||||
if header = headers.delete("content-type")
|
||||
headers["type"] = header
|
||||
if content_type = @headers.delete("content-type")
|
||||
@headers["type"] = content_type
|
||||
end
|
||||
end
|
||||
|
||||
# Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice
|
||||
# for, say, a 2GB streaming file.
|
||||
def set_content_length!
|
||||
@headers["Content-Length"] = @body.size unless @body.respond_to?(:call)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -50,14 +50,9 @@ module ActionController #:nodoc:
|
|||
base.send :attr_accessor, :parent_controller
|
||||
|
||||
base.class_eval do
|
||||
alias_method :process_cleanup_without_components, :process_cleanup
|
||||
alias_method :process_cleanup, :process_cleanup_with_components
|
||||
|
||||
alias_method :set_session_options_without_components, :set_session_options
|
||||
alias_method :set_session_options, :set_session_options_with_components
|
||||
|
||||
alias_method :flash_without_components, :flash
|
||||
alias_method :flash, :flash_with_components
|
||||
alias_method_chain :process_cleanup, :components
|
||||
alias_method_chain :set_session_options, :components
|
||||
alias_method_chain :flash, :components
|
||||
|
||||
alias_method :component_request?, :parent_controller
|
||||
end
|
||||
|
@ -80,11 +75,13 @@ module ActionController #:nodoc:
|
|||
# will also use /code/weblog/components as template root
|
||||
# and find templates in /code/weblog/components/admin/parties/users/
|
||||
def uses_component_template_root
|
||||
path_of_calling_controller = File.dirname(caller[0].split(/:\d+:/).first)
|
||||
path_of_controller_root = path_of_calling_controller.sub(/#{controller_path.split("/")[0..-2]}$/, "") # " (for ruby-mode)
|
||||
path_of_calling_controller = File.dirname(caller[1].split(/:\d+:/, 2).first)
|
||||
path_of_controller_root = path_of_calling_controller.sub(/#{Regexp.escape(File.dirname(controller_path))}$/, "")
|
||||
|
||||
self.template_root = path_of_controller_root
|
||||
end
|
||||
|
||||
deprecate :uses_component_template_root => 'Components are deprecated and will be removed in Rails 2.0.'
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
|
@ -116,27 +113,26 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def flash_with_components(refresh = false) #:nodoc:
|
||||
if @flash.nil? || refresh
|
||||
@flash =
|
||||
if @parent_controller
|
||||
if !defined?(@_flash) || refresh
|
||||
@_flash =
|
||||
if defined?(@parent_controller)
|
||||
@parent_controller.flash
|
||||
else
|
||||
flash_without_components
|
||||
end
|
||||
end
|
||||
|
||||
@flash
|
||||
@_flash
|
||||
end
|
||||
|
||||
private
|
||||
def component_response(options, reuse_response)
|
||||
klass = component_class(options)
|
||||
request = request_for_component(klass.controller_name, options)
|
||||
response = reuse_response ? @response : @response.dup
|
||||
new_response = reuse_response ? response : response.dup
|
||||
|
||||
klass.process_with_components(request, response, self)
|
||||
klass.process_with_components(request, new_response, self)
|
||||
end
|
||||
|
||||
|
||||
# determine the controller class for the component request
|
||||
def component_class(options)
|
||||
if controller = options[:controller]
|
||||
|
@ -145,22 +141,22 @@ module ActionController #:nodoc:
|
|||
self.class
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Create a new request object based on the current request.
|
||||
# The new request inherits the session from the current request,
|
||||
# bypassing any session options set for the component controller's class
|
||||
def request_for_component(controller_name, options)
|
||||
request = @request.dup
|
||||
request.session = @request.session
|
||||
|
||||
request.instance_variable_set(
|
||||
new_request = request.dup
|
||||
new_request.session = request.session
|
||||
|
||||
new_request.instance_variable_set(
|
||||
:@parameters,
|
||||
(options[:params] || {}).with_indifferent_access.update(
|
||||
"controller" => controller_name, "action" => options[:action], "id" => options[:id]
|
||||
)
|
||||
)
|
||||
|
||||
request
|
||||
|
||||
new_request
|
||||
end
|
||||
|
||||
def component_logging(options)
|
||||
|
|
|
@ -4,13 +4,14 @@ module ActionController #:nodoc:
|
|||
# itself back -- just the value it holds). Examples for writing:
|
||||
#
|
||||
# cookies[:user_name] = "david" # => Will set a simple session cookie
|
||||
# cookies[:login] = { :value => "XJ-122", :expires => Time.now + 360} # => Will set a cookie that expires in 1 hour
|
||||
#
|
||||
# cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
|
||||
# # => Will set a cookie that expires in 1 hour
|
||||
#
|
||||
# Examples for reading:
|
||||
#
|
||||
# cookies[:user_name] # => "david"
|
||||
# cookies.size # => 2
|
||||
#
|
||||
#
|
||||
# Example for deleting:
|
||||
#
|
||||
# cookies.delete :user_name
|
||||
|
@ -32,13 +33,13 @@ module ActionController #:nodoc:
|
|||
|
||||
# Deprecated cookie writer method
|
||||
def cookie(*options)
|
||||
@response.headers["cookie"] << CGI::Cookie.new(*options)
|
||||
response.headers['cookie'] << CGI::Cookie.new(*options)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class CookieJar < Hash #:nodoc:
|
||||
def initialize(controller)
|
||||
@controller, @cookies = controller, controller.instance_variable_get("@cookies")
|
||||
@controller, @cookies = controller, controller.request.cookies
|
||||
super()
|
||||
update(@cookies)
|
||||
end
|
||||
|
@ -48,7 +49,7 @@ module ActionController #:nodoc:
|
|||
def [](name)
|
||||
@cookies[name.to_s].value.first if @cookies[name.to_s] && @cookies[name.to_s].respond_to?(:value)
|
||||
end
|
||||
|
||||
|
||||
def []=(name, options)
|
||||
if options.is_a?(Hash)
|
||||
options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options }
|
||||
|
@ -56,10 +57,10 @@ module ActionController #:nodoc:
|
|||
else
|
||||
options = { "name" => name.to_s, "value" => options }
|
||||
end
|
||||
|
||||
|
||||
set_cookie(options)
|
||||
end
|
||||
|
||||
|
||||
# Removes the cookie on the client machine by setting the value to an empty string
|
||||
# and setting its expiration date into the past
|
||||
def delete(name)
|
||||
|
|
|
@ -5,19 +5,14 @@ module ActionController #:nodoc:
|
|||
base.send(:include, ActionController::Filters::InstanceMethods)
|
||||
end
|
||||
|
||||
# Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do
|
||||
# authentication, caching, or auditing before the intended action is performed. Or to do localization or output
|
||||
# compression after the action has been performed.
|
||||
#
|
||||
# Filters have access to the request, response, and all the instance variables set by other filters in the chain
|
||||
# or by the action (in the case of after filters). Additionally, it's possible for a pre-processing <tt>before_filter</tt>
|
||||
# to halt the processing before the intended action is processed by returning false or performing a redirect or render.
|
||||
# This is especially useful for filters like authentication where you're not interested in allowing the action to be
|
||||
# performed if the proper credentials are not in order.
|
||||
# Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do
|
||||
# authentication, caching, or auditing before the intended action is performed. Or to do localization or output
|
||||
# compression after the action has been performed. Filters have access to the request, response, and all the instance
|
||||
# variables set by other filters in the chain or by the action (in the case of after filters).
|
||||
#
|
||||
# == Filter inheritance
|
||||
#
|
||||
# Controller inheritance hierarchies share filters downwards, but subclasses can also add new filters without
|
||||
# Controller inheritance hierarchies share filters downwards, but subclasses can also add or skip filters without
|
||||
# affecting the superclass. For example:
|
||||
#
|
||||
# class BankController < ActionController::Base
|
||||
|
@ -39,7 +34,7 @@ module ActionController #:nodoc:
|
|||
# end
|
||||
#
|
||||
# Now any actions performed on the BankController will have the audit method called before. On the VaultController,
|
||||
# first the audit method is called, then the verify_credentials method. If the audit method returns false, then
|
||||
# first the audit method is called, then the verify_credentials method. If the audit method returns false, then
|
||||
# verify_credentials and the intended action are never called.
|
||||
#
|
||||
# == Filter types
|
||||
|
@ -64,7 +59,7 @@ module ActionController #:nodoc:
|
|||
# The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can
|
||||
# manipulate them as it sees fit.
|
||||
#
|
||||
# The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation.
|
||||
# The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation.
|
||||
# Or just as a quick test. It works like this:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
|
@ -76,6 +71,9 @@ module ActionController #:nodoc:
|
|||
# session, template, and assigns. Note: The inline method doesn't strictly have to be a block; any object that responds to call
|
||||
# and returns 1 or -1 on arity will do (such as a Proc or an Method object).
|
||||
#
|
||||
# Please note that around_filters function a little differently than the normal before and after filters with regard to filter
|
||||
# types. Please see the section dedicated to around_filters below.
|
||||
#
|
||||
# == Filter chain ordering
|
||||
#
|
||||
# Using <tt>before_filter</tt> and <tt>after_filter</tt> appends the specified filters to the existing chain. That's usually
|
||||
|
@ -83,14 +81,14 @@ module ActionController #:nodoc:
|
|||
# can use <tt>prepend_before_filter</tt> and <tt>prepend_after_filter</tt>. Filters added by these methods will be put at the
|
||||
# beginning of their respective chain and executed before the rest. For example:
|
||||
#
|
||||
# class ShoppingController
|
||||
# class ShoppingController < ActionController::Base
|
||||
# before_filter :verify_open_shop
|
||||
#
|
||||
# class CheckoutController
|
||||
# class CheckoutController < ShoppingController
|
||||
# prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
|
||||
#
|
||||
# The filter chain for the CheckoutController is now <tt>:ensure_items_in_cart, :ensure_items_in_stock,</tt>
|
||||
# <tt>:verify_open_shop</tt>. So if either of the ensure filters return false, we'll never get around to see if the shop
|
||||
# <tt>:verify_open_shop</tt>. So if either of the ensure filters return false, we'll never get around to see if the shop
|
||||
# is open or not.
|
||||
#
|
||||
# You may pass multiple filter arguments of each type as well as a filter block.
|
||||
|
@ -98,250 +96,511 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# == Around filters
|
||||
#
|
||||
# In addition to the individual before and after filters, it's also possible to specify that a single object should handle
|
||||
# both the before and after call. That's especially useful when you need to keep state active between the before and after,
|
||||
# such as the example of a benchmark filter below:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# around_filter BenchmarkingFilter.new
|
||||
#
|
||||
# # Before this action is performed, BenchmarkingFilter#before(controller) is executed
|
||||
# def index
|
||||
# Around filters wrap an action, executing code both before and after.
|
||||
# They may be declared as method references, blocks, or objects responding
|
||||
# to #filter or to both #before and #after.
|
||||
#
|
||||
# To use a method as an around_filter, pass a symbol naming the Ruby method.
|
||||
# Yield (or block.call) within the method to run the action.
|
||||
#
|
||||
# around_filter :catch_exceptions
|
||||
#
|
||||
# private
|
||||
# def catch_exceptions
|
||||
# yield
|
||||
# rescue => exception
|
||||
# logger.debug "Caught exception! #{exception}"
|
||||
# raise
|
||||
# end
|
||||
# # After this action has been performed, BenchmarkingFilter#after(controller) is executed
|
||||
#
|
||||
# To use a block as an around_filter, pass a block taking as args both
|
||||
# the controller and the action block. You can't call yield directly from
|
||||
# an around_filter block; explicitly call the action block instead:
|
||||
#
|
||||
# around_filter do |controller, action|
|
||||
# logger.debug "before #{controller.action_name}"
|
||||
# action.call
|
||||
# logger.debug "after #{controller.action_name}"
|
||||
# end
|
||||
#
|
||||
# To use a filter object with around_filter, pass an object responding
|
||||
# to :filter or both :before and :after. With a filter method, yield to
|
||||
# the block as above:
|
||||
#
|
||||
# around_filter BenchmarkingFilter
|
||||
#
|
||||
# class BenchmarkingFilter
|
||||
# def initialize
|
||||
# @runtime
|
||||
# end
|
||||
#
|
||||
# def before
|
||||
# start_timer
|
||||
# end
|
||||
#
|
||||
# def after
|
||||
# stop_timer
|
||||
# report_result
|
||||
# def self.filter(controller, &block)
|
||||
# Benchmark.measure(&block)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# With before and after methods:
|
||||
#
|
||||
# around_filter Authorizer.new
|
||||
#
|
||||
# class Authorizer
|
||||
# # This will run before the action. Returning false aborts the action.
|
||||
# def before(controller)
|
||||
# if user.authorized?
|
||||
# return true
|
||||
# else
|
||||
# redirect_to login_url
|
||||
# return false
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # This will run after the action if and only if before returned true.
|
||||
# def after(controller)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# If the filter has before and after methods, the before method will be
|
||||
# called before the action. If before returns false, the filter chain is
|
||||
# halted and after will not be run. See Filter Chain Halting below for
|
||||
# an example.
|
||||
#
|
||||
# == Filter chain skipping
|
||||
#
|
||||
# Some times its convenient to specify a filter chain in a superclass that'll hold true for the majority of the
|
||||
# subclasses, but not necessarily all of them. The subclasses that behave in exception can then specify which filters
|
||||
# they would like to be relieved of. Examples
|
||||
# Declaring a filter on a base class conveniently applies to its subclasses,
|
||||
# but sometimes a subclass should skip some of its superclass' filters:
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# before_filter :authenticate
|
||||
# around_filter :catch_exceptions
|
||||
# end
|
||||
#
|
||||
# class WeblogController < ApplicationController
|
||||
# # will run the :authenticate filter
|
||||
# # Will run the :authenticate and :catch_exceptions filters.
|
||||
# end
|
||||
#
|
||||
# class SignupController < ApplicationController
|
||||
# # will not run the :authenticate filter
|
||||
# # Skip :authenticate, run :catch_exceptions.
|
||||
# skip_before_filter :authenticate
|
||||
# end
|
||||
#
|
||||
# class ProjectsController < ApplicationController
|
||||
# # Skip :catch_exceptions, run :authenticate.
|
||||
# skip_filter :catch_exceptions
|
||||
# end
|
||||
#
|
||||
# class ClientsController < ApplicationController
|
||||
# # Skip :catch_exceptions and :authenticate unless action is index.
|
||||
# skip_filter :catch_exceptions, :authenticate, :except => :index
|
||||
# end
|
||||
#
|
||||
# == Filter conditions
|
||||
#
|
||||
# Filters can be limited to run for only specific actions. This can be expressed either by listing the actions to
|
||||
# exclude or the actions to include when executing the filter. Available conditions are +:only+ or +:except+, both
|
||||
# of which accept an arbitrary number of method references. For example:
|
||||
# Filters may be limited to specific actions by declaring the actions to
|
||||
# include or exclude. Both options accept single actions (:only => :index)
|
||||
# or arrays of actions (:except => [:foo, :bar]).
|
||||
#
|
||||
# class Journal < ActionController::Base
|
||||
# # only require authentication if the current action is edit or delete
|
||||
# before_filter :authorize, :only => [ :edit, :delete ]
|
||||
#
|
||||
# # Require authentication for edit and delete.
|
||||
# before_filter :authorize, :only => [:edit, :delete]
|
||||
#
|
||||
# # Passing options to a filter with a block.
|
||||
# around_filter(:except => :index) do |controller, action_block|
|
||||
# results = Profiler.run(&action_block)
|
||||
# controller.response.sub! "</body>", "#{results}</body>"
|
||||
# end
|
||||
#
|
||||
# private
|
||||
# def authorize
|
||||
# # redirect to login unless authenticated
|
||||
# # Redirect to login unless authenticated.
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# When setting conditions on inline method (proc) filters the condition must come first and be placed in parentheses.
|
||||
#
|
||||
# class UserPreferences < ActionController::Base
|
||||
# before_filter(:except => :new) { # some proc ... }
|
||||
# # ...
|
||||
# end
|
||||
# == Filter Chain Halting
|
||||
#
|
||||
# <tt>before_filter</tt> and <tt>around_filter</tt> may halt the request
|
||||
# before controller action is run. This is useful, for example, to deny
|
||||
# access to unauthenticated users or to redirect from http to https.
|
||||
# Simply return false from the filter or call render or redirect.
|
||||
#
|
||||
# Around filters halt the request unless the action block is called.
|
||||
# Given these filters
|
||||
# after_filter :after
|
||||
# around_filter :around
|
||||
# before_filter :before
|
||||
#
|
||||
# The filter chain will look like:
|
||||
#
|
||||
# ...
|
||||
# . \
|
||||
# . #around (code before yield)
|
||||
# . . \
|
||||
# . . #before (actual filter code is run)
|
||||
# . . . \
|
||||
# . . . execute controller action
|
||||
# . . . /
|
||||
# . . ...
|
||||
# . . /
|
||||
# . #around (code after yield)
|
||||
# . /
|
||||
# #after (actual filter code is run)
|
||||
#
|
||||
# If #around returns before yielding, only #after will be run. The #before
|
||||
# filter and controller action will not be run. If #before returns false,
|
||||
# the second half of #around and all of #after will still run but the
|
||||
# action will not.
|
||||
module ClassMethods
|
||||
# The passed <tt>filters</tt> will be appended to the array of filters that's run _before_ actions
|
||||
# on this controller are performed.
|
||||
# The passed <tt>filters</tt> will be appended to the filter_chain and
|
||||
# will execute before the action on this controller is performed.
|
||||
def append_before_filter(*filters, &block)
|
||||
conditions = extract_conditions!(filters)
|
||||
filters << block if block_given?
|
||||
add_action_conditions(filters, conditions)
|
||||
append_filter_to_chain('before', filters)
|
||||
append_filter_to_chain(filters, :before, &block)
|
||||
end
|
||||
|
||||
# The passed <tt>filters</tt> will be prepended to the array of filters that's run _before_ actions
|
||||
# on this controller are performed.
|
||||
# The passed <tt>filters</tt> will be prepended to the filter_chain and
|
||||
# will execute before the action on this controller is performed.
|
||||
def prepend_before_filter(*filters, &block)
|
||||
conditions = extract_conditions!(filters)
|
||||
filters << block if block_given?
|
||||
add_action_conditions(filters, conditions)
|
||||
prepend_filter_to_chain('before', filters)
|
||||
prepend_filter_to_chain(filters, :before, &block)
|
||||
end
|
||||
|
||||
# Short-hand for append_before_filter since that's the most common of the two.
|
||||
# Shorthand for append_before_filter since it's the most common.
|
||||
alias :before_filter :append_before_filter
|
||||
|
||||
# The passed <tt>filters</tt> will be appended to the array of filters that's run _after_ actions
|
||||
# on this controller are performed.
|
||||
|
||||
# The passed <tt>filters</tt> will be appended to the array of filters
|
||||
# that run _after_ actions on this controller are performed.
|
||||
def append_after_filter(*filters, &block)
|
||||
conditions = extract_conditions!(filters)
|
||||
filters << block if block_given?
|
||||
add_action_conditions(filters, conditions)
|
||||
append_filter_to_chain('after', filters)
|
||||
prepend_filter_to_chain(filters, :after, &block)
|
||||
end
|
||||
|
||||
# The passed <tt>filters</tt> will be prepended to the array of filters that's run _after_ actions
|
||||
# on this controller are performed.
|
||||
# The passed <tt>filters</tt> will be prepended to the array of filters
|
||||
# that run _after_ actions on this controller are performed.
|
||||
def prepend_after_filter(*filters, &block)
|
||||
conditions = extract_conditions!(filters)
|
||||
filters << block if block_given?
|
||||
add_action_conditions(filters, conditions)
|
||||
prepend_filter_to_chain("after", filters)
|
||||
append_filter_to_chain(filters, :after, &block)
|
||||
end
|
||||
|
||||
# Short-hand for append_after_filter since that's the most common of the two.
|
||||
# Shorthand for append_after_filter since it's the most common.
|
||||
alias :after_filter :append_after_filter
|
||||
|
||||
# The passed <tt>filters</tt> will have their +before+ method appended to the array of filters that's run both before actions
|
||||
# on this controller are performed and have their +after+ method prepended to the after actions. The filter objects must all
|
||||
# respond to both +before+ and +after+. So if you do append_around_filter A.new, B.new, the callstack will look like:
|
||||
|
||||
|
||||
# If you append_around_filter A.new, B.new, the filter chain looks like
|
||||
#
|
||||
# B#before
|
||||
# A#before
|
||||
# # run the action
|
||||
# A#after
|
||||
# B#after
|
||||
def append_around_filter(*filters)
|
||||
conditions = extract_conditions!(filters)
|
||||
for filter in filters.flatten
|
||||
ensure_filter_responds_to_before_and_after(filter)
|
||||
append_before_filter(conditions || {}) { |c| filter.before(c) }
|
||||
prepend_after_filter(conditions || {}) { |c| filter.after(c) }
|
||||
#
|
||||
# With around filters which yield to the action block, #before and #after
|
||||
# are the code before and after the yield.
|
||||
def append_around_filter(*filters, &block)
|
||||
filters, conditions = extract_conditions(filters, &block)
|
||||
filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
|
||||
append_filter_to_chain([filter, conditions])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The passed <tt>filters</tt> will have their +before+ method prepended to the array of filters that's run both before actions
|
||||
# on this controller are performed and have their +after+ method appended to the after actions. The filter objects must all
|
||||
# respond to both +before+ and +after+. So if you do prepend_around_filter A.new, B.new, the callstack will look like:
|
||||
# If you prepend_around_filter A.new, B.new, the filter chain looks like:
|
||||
#
|
||||
# A#before
|
||||
# B#before
|
||||
# # run the action
|
||||
# B#after
|
||||
# A#after
|
||||
def prepend_around_filter(*filters)
|
||||
for filter in filters.flatten
|
||||
ensure_filter_responds_to_before_and_after(filter)
|
||||
prepend_before_filter { |c| filter.before(c) }
|
||||
append_after_filter { |c| filter.after(c) }
|
||||
#
|
||||
# With around filters which yield to the action block, #before and #after
|
||||
# are the code before and after the yield.
|
||||
def prepend_around_filter(*filters, &block)
|
||||
filters, conditions = extract_conditions(filters, &block)
|
||||
filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
|
||||
prepend_filter_to_chain([filter, conditions])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Short-hand for append_around_filter since that's the most common of the two.
|
||||
# Shorthand for append_around_filter since it's the most common.
|
||||
alias :around_filter :append_around_filter
|
||||
|
||||
# Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference
|
||||
|
||||
# Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference
|
||||
# filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
|
||||
# of many sub-controllers need a different hierarchy.
|
||||
#
|
||||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||
# just like when you apply the filters.
|
||||
def skip_before_filter(*filters)
|
||||
if conditions = extract_conditions!(filters)
|
||||
remove_contradicting_conditions!(filters, conditions)
|
||||
conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
|
||||
add_action_conditions(filters, conditions)
|
||||
else
|
||||
for filter in filters.flatten
|
||||
write_inheritable_attribute("before_filters", read_inheritable_attribute("before_filters") - [ filter ])
|
||||
end
|
||||
end
|
||||
skip_filter_in_chain(*filters, &:before?)
|
||||
end
|
||||
|
||||
# Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
|
||||
# Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
|
||||
# filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
|
||||
# of many sub-controllers need a different hierarchy.
|
||||
#
|
||||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||
# just like when you apply the filters.
|
||||
def skip_after_filter(*filters)
|
||||
if conditions = extract_conditions!(filters)
|
||||
remove_contradicting_conditions!(filters, conditions)
|
||||
conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
|
||||
add_action_conditions(filters, conditions)
|
||||
else
|
||||
for filter in filters.flatten
|
||||
write_inheritable_attribute("after_filters", read_inheritable_attribute("after_filters") - [ filter ])
|
||||
end
|
||||
end
|
||||
skip_filter_in_chain(*filters, &:after?)
|
||||
end
|
||||
|
||||
|
||||
# Removes the specified filters from the filter chain. This only works for method reference (symbol)
|
||||
# filters, not procs. This method is different from skip_after_filter and skip_before_filter in that
|
||||
# it will match any before, after or yielding around filter.
|
||||
#
|
||||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||
# just like when you apply the filters.
|
||||
def skip_filter(*filters)
|
||||
skip_filter_in_chain(*filters)
|
||||
end
|
||||
|
||||
# Returns an array of Filter objects for this controller.
|
||||
def filter_chain
|
||||
read_inheritable_attribute("filter_chain") || []
|
||||
end
|
||||
|
||||
# Returns all the before filters for this class and all its ancestors.
|
||||
# This method returns the actual filter that was assigned in the controller to maintain existing functionality.
|
||||
def before_filters #:nodoc:
|
||||
@before_filters ||= read_inheritable_attribute("before_filters") || []
|
||||
filter_chain.select(&:before?).map(&:filter)
|
||||
end
|
||||
|
||||
|
||||
# Returns all the after filters for this class and all its ancestors.
|
||||
# This method returns the actual filter that was assigned in the controller to maintain existing functionality.
|
||||
def after_filters #:nodoc:
|
||||
@after_filters ||= read_inheritable_attribute("after_filters") || []
|
||||
filter_chain.select(&:after?).map(&:filter)
|
||||
end
|
||||
|
||||
|
||||
# Returns a mapping between filters and the actions that may run them.
|
||||
def included_actions #:nodoc:
|
||||
@included_actions ||= read_inheritable_attribute("included_actions") || {}
|
||||
read_inheritable_attribute("included_actions") || {}
|
||||
end
|
||||
|
||||
|
||||
# Returns a mapping between filters and actions that may not run them.
|
||||
def excluded_actions #:nodoc:
|
||||
@excluded_actions ||= read_inheritable_attribute("excluded_actions") || {}
|
||||
read_inheritable_attribute("excluded_actions") || {}
|
||||
end
|
||||
|
||||
private
|
||||
def append_filter_to_chain(condition, filters)
|
||||
write_inheritable_array("#{condition}_filters", filters)
|
||||
|
||||
# Find a filter in the filter_chain where the filter method matches the _filter_ param
|
||||
# and (optionally) the passed block evaluates to true (mostly used for testing before?
|
||||
# and after? on the filter). Useful for symbol filters.
|
||||
#
|
||||
# The object of type Filter is passed to the block when yielded, not the filter itself.
|
||||
def find_filter(filter, &block) #:nodoc:
|
||||
filter_chain.select { |f| f.filter == filter && (!block_given? || yield(f)) }.first
|
||||
end
|
||||
|
||||
# Returns true if the filter is excluded from the given action
|
||||
def filter_excluded_from_action?(filter,action) #:nodoc:
|
||||
if (ia = included_actions[filter]) && !ia.empty?
|
||||
!ia.include?(action)
|
||||
else
|
||||
(excluded_actions[filter] || []).include?(action)
|
||||
end
|
||||
end
|
||||
|
||||
# Filter class is an abstract base class for all filters. Handles all of the included/excluded actions but
|
||||
# contains no logic for calling the actual filters.
|
||||
class Filter #:nodoc:
|
||||
attr_reader :filter, :included_actions, :excluded_actions
|
||||
|
||||
def initialize(filter)
|
||||
@filter = filter
|
||||
end
|
||||
|
||||
def prepend_filter_to_chain(condition, filters)
|
||||
old_filters = read_inheritable_attribute("#{condition}_filters") || []
|
||||
write_inheritable_attribute("#{condition}_filters", filters + old_filters)
|
||||
def before?
|
||||
false
|
||||
end
|
||||
|
||||
def ensure_filter_responds_to_before_and_after(filter)
|
||||
unless filter.respond_to?(:before) && filter.respond_to?(:after)
|
||||
raise ActionControllerError, "Filter object must respond to both before and after"
|
||||
def after?
|
||||
false
|
||||
end
|
||||
|
||||
def around?
|
||||
true
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
raise(ActionControllerError, 'No filter type: Nothing to do here.')
|
||||
end
|
||||
end
|
||||
|
||||
# Abstract base class for filter proxies. FilterProxy objects are meant to mimic the behaviour of the old
|
||||
# before_filter and after_filter by moving the logic into the filter itself.
|
||||
class FilterProxy < Filter #:nodoc:
|
||||
def filter
|
||||
@filter.filter
|
||||
end
|
||||
|
||||
def around?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class BeforeFilterProxy < FilterProxy #:nodoc:
|
||||
def before?
|
||||
true
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
if false == @filter.call(controller) # must only stop if equal to false. only filters returning false are halted.
|
||||
controller.halt_filter_chain(@filter, :returned_false)
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class AfterFilterProxy < FilterProxy #:nodoc:
|
||||
def after?
|
||||
true
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
yield
|
||||
@filter.call(controller)
|
||||
end
|
||||
end
|
||||
|
||||
class SymbolFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
controller.send(@filter, &block)
|
||||
end
|
||||
end
|
||||
|
||||
class ProcFilter < Filter #:nodoc:
|
||||
def call(controller)
|
||||
@filter.call(controller)
|
||||
rescue LocalJumpError # a yield from a proc... no no bad dog.
|
||||
raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
|
||||
end
|
||||
end
|
||||
|
||||
class ProcWithCallFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
@filter.call(controller, block)
|
||||
rescue LocalJumpError # a yield from a proc... no no bad dog.
|
||||
raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
|
||||
end
|
||||
end
|
||||
|
||||
class MethodFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
@filter.call(controller, &block)
|
||||
end
|
||||
end
|
||||
|
||||
class ClassFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
@filter.filter(controller, &block)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def append_filter_to_chain(filters, position = :around, &block)
|
||||
write_inheritable_array('filter_chain', create_filters(filters, position, &block) )
|
||||
end
|
||||
|
||||
def prepend_filter_to_chain(filters, position = :around, &block)
|
||||
write_inheritable_attribute('filter_chain', create_filters(filters, position, &block) + filter_chain)
|
||||
end
|
||||
|
||||
def create_filters(filters, position, &block) #:nodoc:
|
||||
filters, conditions = extract_conditions(filters, &block)
|
||||
filters.map! { |filter| find_or_create_filter(filter,position) }
|
||||
update_conditions(filters, conditions)
|
||||
filters
|
||||
end
|
||||
|
||||
def find_or_create_filter(filter,position)
|
||||
if found_filter = find_filter(filter) { |f| f.send("#{position}?") }
|
||||
found_filter
|
||||
else
|
||||
f = class_for_filter(filter).new(filter)
|
||||
# apply proxy to filter if necessary
|
||||
case position
|
||||
when :before
|
||||
BeforeFilterProxy.new(f)
|
||||
when :after
|
||||
AfterFilterProxy.new(f)
|
||||
else
|
||||
f
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def extract_conditions!(filters)
|
||||
return nil unless filters.last.is_a? Hash
|
||||
filters.pop
|
||||
# The determination of the filter type was once done at run time.
|
||||
# This method is here to extract as much logic from the filter run time as possible
|
||||
def class_for_filter(filter) #:nodoc:
|
||||
case
|
||||
when filter.is_a?(Symbol)
|
||||
SymbolFilter
|
||||
when filter.respond_to?(:call)
|
||||
if filter.is_a?(Method)
|
||||
MethodFilter
|
||||
elsif filter.arity == 1
|
||||
ProcFilter
|
||||
else
|
||||
ProcWithCallFilter
|
||||
end
|
||||
when filter.respond_to?(:filter)
|
||||
ClassFilter
|
||||
else
|
||||
raise(ActionControllerError, 'A filters must be a Symbol, Proc, Method, or object responding to filter.')
|
||||
end
|
||||
end
|
||||
|
||||
def add_action_conditions(filters, conditions)
|
||||
return unless conditions
|
||||
included, excluded = conditions[:only], conditions[:except]
|
||||
write_inheritable_hash('included_actions', condition_hash(filters, included)) && return if included
|
||||
write_inheritable_hash('excluded_actions', condition_hash(filters, excluded)) if excluded
|
||||
def extract_conditions(*filters, &block) #:nodoc:
|
||||
filters.flatten!
|
||||
conditions = filters.last.is_a?(Hash) ? filters.pop : {}
|
||||
filters << block if block_given?
|
||||
return filters, conditions
|
||||
end
|
||||
|
||||
def update_conditions(filters, conditions)
|
||||
return if conditions.empty?
|
||||
if conditions[:only]
|
||||
write_inheritable_hash('included_actions', condition_hash(filters, conditions[:only]))
|
||||
else
|
||||
write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except])) if conditions[:except]
|
||||
end
|
||||
end
|
||||
|
||||
def condition_hash(filters, *actions)
|
||||
filters.inject({}) {|hash, filter| hash.merge(filter => actions.flatten.map {|action| action.to_s})}
|
||||
actions = actions.flatten.map(&:to_s)
|
||||
filters.inject({}) { |h,f| h.update( f => (actions.blank? ? nil : actions)) }
|
||||
end
|
||||
|
||||
def remove_contradicting_conditions!(filters, conditions)
|
||||
return unless conditions[:only]
|
||||
filters.each do |filter|
|
||||
next unless included_actions_for_filter = (read_inheritable_attribute('included_actions') || {})[filter]
|
||||
[*conditions[:only]].each do |conditional_action|
|
||||
conditional_action = conditional_action.to_s
|
||||
included_actions_for_filter.delete(conditional_action) if included_actions_for_filter.include?(conditional_action)
|
||||
|
||||
def skip_filter_in_chain(*filters, &test) #:nodoc:
|
||||
filters, conditions = extract_conditions(filters)
|
||||
filters.map! { |f| block_given? ? find_filter(f, &test) : find_filter(f) }
|
||||
filters.compact!
|
||||
|
||||
if conditions.empty?
|
||||
delete_filters_in_chain(filters)
|
||||
else
|
||||
remove_actions_from_included_actions!(filters,conditions[:only] || [])
|
||||
conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
|
||||
update_conditions(filters,conditions)
|
||||
end
|
||||
end
|
||||
|
||||
def remove_actions_from_included_actions!(filters,*actions)
|
||||
actions = actions.flatten.map(&:to_s)
|
||||
updated_hash = filters.inject(included_actions) do |hash,filter|
|
||||
ia = (hash[filter] || []) - actions
|
||||
ia.blank? ? hash.delete(filter) : hash[filter] = ia
|
||||
hash
|
||||
end
|
||||
write_inheritable_attribute('included_actions', updated_hash)
|
||||
end
|
||||
|
||||
def delete_filters_in_chain(filters) #:nodoc:
|
||||
write_inheritable_attribute('filter_chain', filter_chain.reject { |f| filters.include?(f) })
|
||||
end
|
||||
|
||||
def filter_responds_to_before_and_after(filter) #:nodoc:
|
||||
filter.respond_to?(:before) && filter.respond_to?(:after)
|
||||
end
|
||||
|
||||
def proxy_before_and_after_filter(filter) #:nodoc:
|
||||
return filter unless filter_responds_to_before_and_after(filter)
|
||||
Proc.new do |controller, action|
|
||||
unless filter.before(controller) == false
|
||||
begin
|
||||
action.call
|
||||
ensure
|
||||
filter.after(controller)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -350,26 +609,14 @@ module ActionController #:nodoc:
|
|||
module InstanceMethods # :nodoc:
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
alias_method :perform_action_without_filters, :perform_action
|
||||
alias_method :perform_action, :perform_action_with_filters
|
||||
|
||||
alias_method :process_without_filters, :process
|
||||
alias_method :process, :process_with_filters
|
||||
|
||||
alias_method :process_cleanup_without_filters, :process_cleanup
|
||||
alias_method :process_cleanup, :process_cleanup_with_filters
|
||||
alias_method_chain :perform_action, :filters
|
||||
alias_method_chain :process, :filters
|
||||
alias_method_chain :process_cleanup, :filters
|
||||
end
|
||||
end
|
||||
|
||||
def perform_action_with_filters
|
||||
before_action_result = before_action
|
||||
|
||||
unless before_action_result == false || performed?
|
||||
perform_action_without_filters
|
||||
after_action
|
||||
end
|
||||
|
||||
@before_filter_chain_aborted = (before_action_result == false)
|
||||
call_filter(self.class.filter_chain, 0)
|
||||
end
|
||||
|
||||
def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
|
||||
|
@ -377,61 +624,37 @@ module ActionController #:nodoc:
|
|||
process_without_filters(request, response, method, *arguments)
|
||||
end
|
||||
|
||||
# Calls all the defined before-filter filters, which are added by using "before_filter :method".
|
||||
# If any of the filters return false, no more filters will be executed and the action is aborted.
|
||||
def before_action #:doc:
|
||||
call_filters(self.class.before_filters)
|
||||
def filter_chain
|
||||
self.class.filter_chain
|
||||
end
|
||||
|
||||
# Calls all the defined after-filter filters, which are added by using "after_filter :method".
|
||||
# If any of the filters return false, no more filters will be executed.
|
||||
def after_action #:doc:
|
||||
call_filters(self.class.after_filters)
|
||||
def call_filter(chain, index)
|
||||
return (performed? || perform_action_without_filters) if index >= chain.size
|
||||
filter = chain[index]
|
||||
return call_filter(chain, index.next) if self.class.filter_excluded_from_action?(filter,action_name)
|
||||
|
||||
halted = false
|
||||
filter.call(self) do
|
||||
halted = call_filter(chain, index.next)
|
||||
end
|
||||
halt_filter_chain(filter.filter, :no_yield) if halted == false unless @before_filter_chain_aborted
|
||||
halted
|
||||
end
|
||||
|
||||
|
||||
def halt_filter_chain(filter, reason)
|
||||
if logger
|
||||
case reason
|
||||
when :no_yield
|
||||
logger.info "Filter chain halted as [#{filter.inspect}] did not yield."
|
||||
when :returned_false
|
||||
logger.info "Filter chain halted as [#{filter.inspect}] returned false."
|
||||
end
|
||||
end
|
||||
@before_filter_chain_aborted = true
|
||||
return false
|
||||
end
|
||||
|
||||
private
|
||||
def call_filters(filters)
|
||||
filters.each do |filter|
|
||||
next if action_exempted?(filter)
|
||||
|
||||
filter_result = case
|
||||
when filter.is_a?(Symbol)
|
||||
self.send(filter)
|
||||
when filter_block?(filter)
|
||||
filter.call(self)
|
||||
when filter_class?(filter)
|
||||
filter.filter(self)
|
||||
else
|
||||
raise(
|
||||
ActionControllerError,
|
||||
'Filters need to be either a symbol, proc/method, or class implementing a static filter method'
|
||||
)
|
||||
end
|
||||
|
||||
if filter_result == false
|
||||
logger.info "Filter chain halted as [#{filter}] returned false" if logger
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def filter_block?(filter)
|
||||
filter.respond_to?('call') && (filter.arity == 1 || filter.arity == -1)
|
||||
end
|
||||
|
||||
def filter_class?(filter)
|
||||
filter.respond_to?('filter')
|
||||
end
|
||||
|
||||
def action_exempted?(filter)
|
||||
case
|
||||
when ia = self.class.included_actions[filter]
|
||||
!ia.include?(action_name)
|
||||
when ea = self.class.excluded_actions[filter]
|
||||
ea.include?(action_name)
|
||||
end
|
||||
end
|
||||
|
||||
def process_cleanup_with_filters
|
||||
if @before_filter_chain_aborted
|
||||
close_session
|
||||
|
|
|
@ -17,7 +17,7 @@ module ActionController #:nodoc:
|
|||
# end
|
||||
#
|
||||
# display.rhtml
|
||||
# <% if @flash[:notice] %><div class="notice"><%= @flash[:notice] %></div><% end %>
|
||||
# <% if flash[:notice] %><div class="notice"><%= flash[:notice] %></div><% end %>
|
||||
#
|
||||
# This example just places a string in the flash, but you can put any object in there. And of course, you can put as many
|
||||
# as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
|
||||
|
@ -28,11 +28,9 @@ module ActionController #:nodoc:
|
|||
base.send :include, InstanceMethods
|
||||
|
||||
base.class_eval do
|
||||
alias_method :assign_shortcuts_without_flash, :assign_shortcuts
|
||||
alias_method :assign_shortcuts, :assign_shortcuts_with_flash
|
||||
|
||||
alias_method :process_cleanup_without_flash, :process_cleanup
|
||||
alias_method :process_cleanup, :process_cleanup_with_flash
|
||||
alias_method_chain :assign_shortcuts, :flash
|
||||
alias_method_chain :process_cleanup, :flash
|
||||
alias_method_chain :reset_session, :flash
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -94,7 +92,7 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# flash.keep # keeps the entire flash
|
||||
# flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
|
||||
def keep(k=nil)
|
||||
def keep(k = nil)
|
||||
use(k, false)
|
||||
end
|
||||
|
||||
|
@ -102,7 +100,7 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# flash.keep # keep entire flash available for the next action
|
||||
# flash.discard(:warning) # discard the "warning" entry (it'll still be available for the current action)
|
||||
def discard(k=nil)
|
||||
def discard(k = nil)
|
||||
use(k)
|
||||
end
|
||||
|
||||
|
@ -118,6 +116,7 @@ module ActionController #:nodoc:
|
|||
@used.delete(k)
|
||||
end
|
||||
end
|
||||
|
||||
(@used.keys - keys).each{|k| @used.delete k } # clean up after keys that could have been left over by calling reject! or shift on the flash
|
||||
end
|
||||
|
||||
|
@ -143,36 +142,41 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def process_cleanup_with_flash
|
||||
flash.sweep if @session
|
||||
flash.sweep if @_session
|
||||
process_cleanup_without_flash
|
||||
end
|
||||
|
||||
def reset_session_with_flash
|
||||
reset_session_without_flash
|
||||
remove_instance_variable(:@_flash)
|
||||
flash(:refresh)
|
||||
end
|
||||
|
||||
protected
|
||||
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to read a notice you put there or
|
||||
# <tt>flash["notice"] = "hello"</tt> to put a new one.
|
||||
# Note that if sessions are disabled only flash.now will work.
|
||||
def flash(refresh = false) #:doc:
|
||||
if @flash.nil? || refresh
|
||||
@flash =
|
||||
if @session.is_a?(Hash)
|
||||
# @session is a Hash, if sessions are disabled
|
||||
# we don't put the flash in the session in this case
|
||||
if !defined?(@_flash) || refresh
|
||||
@_flash =
|
||||
if session.is_a?(Hash)
|
||||
# don't put flash in session if disabled
|
||||
FlashHash.new
|
||||
else
|
||||
# otherwise, @session is a CGI::Session or a TestSession
|
||||
# otherwise, session is a CGI::Session or a TestSession
|
||||
# so make sure it gets retrieved from/saved to session storage after request processing
|
||||
@session["flash"] ||= FlashHash.new
|
||||
session["flash"] ||= FlashHash.new
|
||||
end
|
||||
end
|
||||
|
||||
@flash
|
||||
|
||||
@_flash
|
||||
end
|
||||
|
||||
# deprecated. use <tt>flash.keep</tt> instead
|
||||
def keep_flash #:doc:
|
||||
warn 'keep_flash is deprecated; use flash.keep instead.'
|
||||
ActiveSupport::Deprecation.warn 'keep_flash is deprecated; use flash.keep instead.', caller
|
||||
flash.keep
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
module ActionController #:nodoc:
|
||||
module Helpers #:nodoc:
|
||||
def self.append_features(base)
|
||||
super
|
||||
|
||||
def self.included(base)
|
||||
# Initialize the base module to aggregate its helpers.
|
||||
base.class_inheritable_accessor :master_helper_module
|
||||
base.master_helper_module = Module.new
|
||||
|
@ -13,8 +11,7 @@ module ActionController #:nodoc:
|
|||
base.class_eval do
|
||||
# Wrap inherited to create a new master helper module for subclasses.
|
||||
class << self
|
||||
alias_method :inherited_without_helper, :inherited
|
||||
alias_method :inherited, :inherited_with_helper
|
||||
alias_method_chain :inherited, :helper
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
require 'dispatcher'
|
||||
require 'stringio'
|
||||
require 'uri'
|
||||
require 'action_controller/test_process'
|
||||
|
||||
module ActionController
|
||||
module Integration #:nodoc:
|
||||
|
@ -13,6 +14,7 @@ module ActionController
|
|||
# rather than instantiating Integration::Session directly.
|
||||
class Session
|
||||
include Test::Unit::Assertions
|
||||
include ActionController::Assertions
|
||||
include ActionController::TestProcess
|
||||
|
||||
# The integer HTTP status code of the last request.
|
||||
|
@ -73,11 +75,11 @@ module ActionController
|
|||
unless @named_routes_configured
|
||||
# install the named routes in this session instance.
|
||||
klass = class<<self; self; end
|
||||
Routing::NamedRoutes.install(klass)
|
||||
Routing::Routes.named_routes.install(klass)
|
||||
|
||||
# the helpers are made protected by default--we make them public for
|
||||
# easier access during testing and troubleshooting.
|
||||
klass.send(:public, *Routing::NamedRoutes::Helpers)
|
||||
klass.send(:public, *Routing::Routes.named_routes.helpers)
|
||||
@named_routes_configured = true
|
||||
end
|
||||
end
|
||||
|
@ -111,7 +113,7 @@ module ActionController
|
|||
# performed on the location header.
|
||||
def follow_redirect!
|
||||
raise "not a redirect! #{@status} #{@status_message}" unless redirect?
|
||||
get(interpret_uri(headers["location"].first))
|
||||
get(interpret_uri(headers['location'].first))
|
||||
status
|
||||
end
|
||||
|
||||
|
@ -143,19 +145,33 @@ module ActionController
|
|||
# (application/x-www-form-urlencoded or multipart/form-data). The headers
|
||||
# should be a hash. The keys will automatically be upcased, with the
|
||||
# prefix 'HTTP_' added if needed.
|
||||
#
|
||||
# You can also perform POST, PUT, DELETE, and HEAD requests with #post,
|
||||
# #put, #delete, and #head.
|
||||
def get(path, parameters=nil, headers=nil)
|
||||
process :get, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs a POST request with the given parameters. The parameters may
|
||||
# be +nil+, a Hash, or a string that is appropriately encoded
|
||||
# (application/x-www-form-urlencoded or multipart/form-data). The headers
|
||||
# should be a hash. The keys will automatically be upcased, with the
|
||||
# prefix 'HTTP_' added if needed.
|
||||
# Performs a POST request with the given parameters. See get() for more details.
|
||||
def post(path, parameters=nil, headers=nil)
|
||||
process :post, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs a PUT request with the given parameters. See get() for more details.
|
||||
def put(path, parameters=nil, headers=nil)
|
||||
process :put, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs a DELETE request with the given parameters. See get() for more details.
|
||||
def delete(path, parameters=nil, headers=nil)
|
||||
process :delete, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs a HEAD request with the given parameters. See get() for more details.
|
||||
def head(path, parameters=nil, headers=nil)
|
||||
process :head, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs an XMLHttpRequest request with the given parameters, mimicing
|
||||
# the request environment created by the Prototype library. The parameters
|
||||
# may be +nil+, a Hash, or a string that is appropriately encoded
|
||||
|
@ -163,7 +179,11 @@ module ActionController
|
|||
# should be a hash. The keys will automatically be upcased, with the
|
||||
# prefix 'HTTP_' added if needed.
|
||||
def xml_http_request(path, parameters=nil, headers=nil)
|
||||
headers = (headers || {}).merge("X-Requested-With" => "XMLHttpRequest")
|
||||
headers = (headers || {}).merge(
|
||||
"X-Requested-With" => "XMLHttpRequest",
|
||||
"Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
|
||||
)
|
||||
|
||||
post(path, parameters, headers)
|
||||
end
|
||||
|
||||
|
@ -174,7 +194,6 @@ module ActionController
|
|||
end
|
||||
|
||||
private
|
||||
|
||||
class MockCGI < CGI #:nodoc:
|
||||
attr_accessor :stdinput, :stdoutput, :env_table
|
||||
|
||||
|
@ -224,7 +243,7 @@ module ActionController
|
|||
|
||||
(headers || {}).each do |key, value|
|
||||
key = key.to_s.upcase.gsub(/-/, "_")
|
||||
key = "HTTP_#{key}" unless env.has_key?(key) || env =~ /^X|HTTP/
|
||||
key = "HTTP_#{key}" unless env.has_key?(key) || key =~ /^HTTP_/
|
||||
env[key] = value
|
||||
end
|
||||
|
||||
|
@ -247,6 +266,8 @@ module ActionController
|
|||
# tests.
|
||||
@response.extend(TestResponseBehavior)
|
||||
|
||||
@html_document = nil
|
||||
|
||||
parse_result
|
||||
return status
|
||||
end
|
||||
|
@ -317,9 +338,8 @@ module ActionController
|
|||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
class <<self
|
||||
alias_method :new_without_capture, :new
|
||||
alias_method :new, :new_with_capture
|
||||
class << self
|
||||
alias_method_chain :new, :capture
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -330,9 +350,11 @@ module ActionController
|
|||
def clear_last_instantiation!
|
||||
self.last_instantiation = nil
|
||||
end
|
||||
|
||||
|
||||
def new_with_capture(*args)
|
||||
self.last_instantiation ||= new_without_capture(*args)
|
||||
controller = new_without_capture(*args)
|
||||
self.last_instantiation ||= controller
|
||||
controller
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -471,6 +493,8 @@ module ActionController
|
|||
%w(get post cookies assigns xml_http_request).each do |method|
|
||||
define_method(method) do |*args|
|
||||
reset! unless @integration_session
|
||||
# reset the html_document variable, but only for new get/post calls
|
||||
@html_document = nil unless %w(cookies assigns).include?(method)
|
||||
returning @integration_session.send(method, *args) do
|
||||
copy_session_variables!
|
||||
end
|
||||
|
|
|
@ -3,12 +3,13 @@ module ActionController #:nodoc:
|
|||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
# NOTE: Can't use alias_method_chain here because +render_without_layout+ is already
|
||||
# defined as a publicly exposed method
|
||||
alias_method :render_with_no_layout, :render
|
||||
alias_method :render, :render_with_a_layout
|
||||
|
||||
class << self
|
||||
alias_method :inherited_without_layout, :inherited
|
||||
alias_method :inherited, :inherited_with_layout
|
||||
alias_method_chain :inherited, :layout
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -26,9 +27,9 @@ module ActionController #:nodoc:
|
|||
# With layouts, you can flip it around and have the common structure know where to insert changing content. This means
|
||||
# that the header and footer are only mentioned in one place, like this:
|
||||
#
|
||||
# <!-- The header part of this layout -->
|
||||
# // The header part of this layout
|
||||
# <%= yield %>
|
||||
# <!-- The footer part of this layout -->
|
||||
# // The footer part of this layout -->
|
||||
#
|
||||
# And then you have content pages that look like this:
|
||||
#
|
||||
|
@ -37,9 +38,9 @@ module ActionController #:nodoc:
|
|||
# Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout,
|
||||
# like this:
|
||||
#
|
||||
# <!-- The header part of this layout -->
|
||||
# // The header part of this layout
|
||||
# hello world
|
||||
# <!-- The footer part of this layout -->
|
||||
# // The footer part of this layout -->
|
||||
#
|
||||
# == Accessing shared variables
|
||||
#
|
||||
|
@ -182,7 +183,6 @@ module ActionController #:nodoc:
|
|||
private
|
||||
def inherited_with_layout(child)
|
||||
inherited_without_layout(child)
|
||||
child.send :include, Reloadable
|
||||
layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '')
|
||||
child.layout(layout_match) unless layout_list.grep(%r{layouts/#{layout_match}\.[a-z][0-9a-z]*$}).empty?
|
||||
end
|
||||
|
@ -235,6 +235,8 @@ module ActionController #:nodoc:
|
|||
template_with_options = options.is_a?(Hash)
|
||||
|
||||
if apply_layout?(template_with_options, options) && (layout = pick_layout(template_with_options, options, deprecated_layout))
|
||||
assert_existence_of_template_file(layout)
|
||||
|
||||
options = options.merge :layout => false if template_with_options
|
||||
logger.info("Rendering #{options} within #{layout}") if logger
|
||||
|
||||
|
@ -248,6 +250,7 @@ module ActionController #:nodoc:
|
|||
erase_render_results
|
||||
add_variables_to_assigns
|
||||
@template.instance_variable_set("@content_for_layout", content_for_layout)
|
||||
response.layout = layout
|
||||
render_text(@template.render_file(layout, true), deprecated_status)
|
||||
else
|
||||
render_with_no_layout(options, deprecated_status, &block)
|
||||
|
@ -263,7 +266,7 @@ module ActionController #:nodoc:
|
|||
|
||||
def candidate_for_layout?(options)
|
||||
(options.has_key?(:layout) && options[:layout] != false) ||
|
||||
options.values_at(:text, :xml, :file, :inline, :partial, :nothing).compact.empty? &&
|
||||
options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing).compact.empty? &&
|
||||
!template_exempt_from_layout?(default_template_name(options[:action] || options[:template]))
|
||||
end
|
||||
|
||||
|
|
|
@ -4,11 +4,12 @@ module ActionController
|
|||
# backing.
|
||||
module Macros
|
||||
module AutoComplete #:nodoc:
|
||||
def self.append_features(base) #:nodoc:
|
||||
super
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# DEPRECATION WARNING: This method will become a separate plugin when Rails 2.0 ships.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# # Controller
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
module ActionController
|
||||
module Macros
|
||||
module InPlaceEditing #:nodoc:
|
||||
def self.append_features(base) #:nodoc:
|
||||
super
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# DEPRECATION WARNING: This method will become a separate plugin when Rails 2.0 ships.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# # Controller
|
||||
|
|
|
@ -8,18 +8,18 @@ module ActionController #:nodoc:
|
|||
# Without web-service support, an action which collects the data for displaying a list of people
|
||||
# might look something like this:
|
||||
#
|
||||
# def list
|
||||
# def index
|
||||
# @people = Person.find(:all)
|
||||
# end
|
||||
#
|
||||
# Here's the same action, with web-service support baked in:
|
||||
#
|
||||
# def list
|
||||
# def index
|
||||
# @people = Person.find(:all)
|
||||
#
|
||||
# respond_to do |wants|
|
||||
# wants.html
|
||||
# wants.xml { render :xml => @people.to_xml }
|
||||
# respond_to do |format|
|
||||
# format.html
|
||||
# format.xml { render :xml => @people.to_xml }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
|
@ -30,7 +30,7 @@ module ActionController #:nodoc:
|
|||
# Supposing you have an action that adds a new person, optionally creating their company
|
||||
# (by name) if it does not already exist, without web-services, it might look like this:
|
||||
#
|
||||
# def add
|
||||
# def create
|
||||
# @company = Company.find_or_create_by_name(params[:company][:name])
|
||||
# @person = @company.people.create(params[:person])
|
||||
#
|
||||
|
@ -39,15 +39,15 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# Here's the same action, with web-service support baked in:
|
||||
#
|
||||
# def add
|
||||
# def create
|
||||
# company = params[:person].delete(:company)
|
||||
# @company = Company.find_or_create_by_name(company[:name])
|
||||
# @person = @company.people.create(params[:person])
|
||||
#
|
||||
# respond_to do |wants|
|
||||
# wants.html { redirect_to(person_list_url) }
|
||||
# wants.js
|
||||
# wants.xml { render :xml => @person.to_xml(:include => @company) }
|
||||
# respond_to do |format|
|
||||
# format.html { redirect_to(person_list_url) }
|
||||
# format.js
|
||||
# format.xml { render :xml => @person.to_xml(:include => @company) }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
|
@ -97,9 +97,8 @@ module ActionController #:nodoc:
|
|||
# environment.rb as follows.
|
||||
#
|
||||
# Mime::Type.register "image/jpg", :jpg
|
||||
#
|
||||
def respond_to(*types, &block)
|
||||
raise ArgumentError, "respond_to takes either types or a block, never bot" unless types.any? ^ block
|
||||
raise ArgumentError, "respond_to takes either types or a block, never both" unless types.any? ^ block
|
||||
block ||= lambda { |responder| types.each { |type| responder.send(type) } }
|
||||
responder = Responder.new(block.binding)
|
||||
block.call(responder)
|
||||
|
@ -108,15 +107,19 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
class Responder #:nodoc:
|
||||
DEFAULT_BLOCKS = {
|
||||
:html => 'Proc.new { render }',
|
||||
:js => 'Proc.new { render :action => "#{action_name}.rjs" }',
|
||||
:xml => 'Proc.new { render :action => "#{action_name}.rxml" }'
|
||||
}
|
||||
DEFAULT_BLOCKS = [:html, :js, :xml].inject({}) do |blocks, ext|
|
||||
template_extension = (ext == :html ? '' : ".r#{ext}")
|
||||
blocks.update ext => %(Proc.new { render :action => "\#{action_name}#{template_extension}", :content_type => Mime::#{ext.to_s.upcase} })
|
||||
end
|
||||
|
||||
def initialize(block_binding)
|
||||
@block_binding = block_binding
|
||||
@mime_type_priority = eval("request.accepts", block_binding)
|
||||
@mime_type_priority = eval(
|
||||
"(params[:format] && Mime::EXTENSION_LOOKUP[params[:format]]) ? " +
|
||||
"[ Mime::EXTENSION_LOOKUP[params[:format]] ] : request.accepts",
|
||||
block_binding
|
||||
)
|
||||
|
||||
@order = []
|
||||
@responses = {}
|
||||
end
|
||||
|
@ -127,24 +130,33 @@ module ActionController #:nodoc:
|
|||
@order << mime_type
|
||||
|
||||
if block_given?
|
||||
@responses[mime_type] = block
|
||||
else
|
||||
@responses[mime_type] = eval(DEFAULT_BLOCKS[mime_type.to_sym], @block_binding)
|
||||
end
|
||||
end
|
||||
|
||||
for mime_type in %w( all html js xml rss atom yaml )
|
||||
eval <<-EOT
|
||||
def #{mime_type}(&block)
|
||||
custom(Mime::#{mime_type.upcase}, &block)
|
||||
@responses[mime_type] = Proc.new do
|
||||
eval "response.content_type = '#{mime_type.to_s}'", @block_binding
|
||||
block.call
|
||||
end
|
||||
EOT
|
||||
else
|
||||
if source = DEFAULT_BLOCKS[mime_type.to_sym]
|
||||
@responses[mime_type] = eval(source, @block_binding)
|
||||
else
|
||||
raise ActionController::RenderError, "Expected a block but none was given for custom mime handler #{mime_type}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def any(*args, &block)
|
||||
args.each { |type| send(type, &block) }
|
||||
end
|
||||
|
||||
def method_missing(symbol, &block)
|
||||
mime_constant = symbol.to_s.upcase
|
||||
|
||||
if Mime::SET.include?(Mime.const_get(mime_constant))
|
||||
custom(Mime.const_get(mime_constant), &block)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def respond
|
||||
for priority in @mime_type_priority
|
||||
if priority == Mime::ALL
|
||||
|
|
|
@ -1,5 +1,18 @@
|
|||
module Mime
|
||||
class Type #:nodoc:
|
||||
# Encapsulates the notion of a mime type. Can be used at render time, for example, with:
|
||||
#
|
||||
# class PostsController < ActionController::Base
|
||||
# def show
|
||||
# @post = Post.find(params[:id])
|
||||
#
|
||||
# respond_to do |format|
|
||||
# format.html
|
||||
# format.ics { render :text => post.to_ics, :mime_type => Mime::Type["text/calendar"] }
|
||||
# format.xml { render :xml => @people.to_xml }
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
class Type
|
||||
# A simple helper class used in parsing the accept header
|
||||
class AcceptItem #:nodoc:
|
||||
attr_accessor :order, :name, :q
|
||||
|
@ -31,14 +44,20 @@ module Mime
|
|||
LOOKUP[string]
|
||||
end
|
||||
|
||||
def register(string, symbol, synonyms = [])
|
||||
Mime.send :const_set, symbol.to_s.upcase, Type.new(string, symbol, synonyms)
|
||||
SET << Mime.send(:const_get, symbol.to_s.upcase)
|
||||
LOOKUP[string] = EXTENSION_LOOKUP[symbol.to_s] = SET.last
|
||||
end
|
||||
|
||||
def parse(accept_header)
|
||||
# keep track of creation order to keep the subsequent sort stable
|
||||
index = 0
|
||||
list = accept_header.split(/,/).
|
||||
map! { |i| AcceptItem.new(index += 1, *i.split(/;\s*q=/)) }.sort!
|
||||
list = accept_header.split(/,/).map! do |i|
|
||||
AcceptItem.new(index += 1, *i.split(/;\s*q=/))
|
||||
end.sort!
|
||||
|
||||
# Take care of the broken text/xml entry by renaming or deleting it
|
||||
|
||||
text_xml = list.index("text/xml")
|
||||
app_xml = list.index("application/xml")
|
||||
|
||||
|
@ -112,31 +131,70 @@ module Mime
|
|||
end
|
||||
|
||||
ALL = Type.new "*/*", :all
|
||||
TEXT = Type.new "text/plain", :text
|
||||
HTML = Type.new "text/html", :html, %w( application/xhtml+xml )
|
||||
JS = Type.new "text/javascript", :js, %w( application/javascript application/x-javascript )
|
||||
ICS = Type.new "text/calendar", :ics
|
||||
CSV = Type.new "text/csv", :csv
|
||||
XML = Type.new "application/xml", :xml, %w( text/xml application/x-xml )
|
||||
RSS = Type.new "application/rss+xml", :rss
|
||||
ATOM = Type.new "application/atom+xml", :atom
|
||||
YAML = Type.new "application/x-yaml", :yaml, %w( text/yaml )
|
||||
JSON = Type.new "application/json", :json, %w( text/x-json )
|
||||
|
||||
LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) }
|
||||
SET = [ ALL, TEXT, HTML, JS, ICS, XML, RSS, ATOM, YAML, JSON ]
|
||||
|
||||
LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k == "" }
|
||||
|
||||
LOOKUP["*/*"] = ALL
|
||||
|
||||
LOOKUP["text/plain"] = TEXT
|
||||
|
||||
LOOKUP["text/html"] = HTML
|
||||
LOOKUP["application/xhtml+xml"] = HTML
|
||||
|
||||
LOOKUP["application/xml"] = XML
|
||||
LOOKUP["text/xml"] = XML
|
||||
LOOKUP["application/x-xml"] = XML
|
||||
|
||||
LOOKUP["text/javascript"] = JS
|
||||
LOOKUP["application/javascript"] = JS
|
||||
LOOKUP["application/x-javascript"] = JS
|
||||
|
||||
LOOKUP["text/calendar"] = ICS
|
||||
|
||||
LOOKUP["text/csv"] = CSV
|
||||
|
||||
LOOKUP["application/xml"] = XML
|
||||
LOOKUP["text/xml"] = XML
|
||||
LOOKUP["application/x-xml"] = XML
|
||||
|
||||
LOOKUP["text/yaml"] = YAML
|
||||
LOOKUP["application/x-yaml"] = YAML
|
||||
|
||||
LOOKUP["application/rss+xml"] = RSS
|
||||
LOOKUP["application/atom+xml"] = ATOM
|
||||
end
|
||||
|
||||
LOOKUP["application/json"] = JSON
|
||||
LOOKUP["text/x-json"] = JSON
|
||||
|
||||
|
||||
EXTENSION_LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k == "" }
|
||||
|
||||
EXTENSION_LOOKUP["html"] = HTML
|
||||
EXTENSION_LOOKUP["xhtml"] = HTML
|
||||
|
||||
EXTENSION_LOOKUP["txt"] = TEXT
|
||||
|
||||
EXTENSION_LOOKUP["xml"] = XML
|
||||
|
||||
EXTENSION_LOOKUP["js"] = JS
|
||||
|
||||
EXTENSION_LOOKUP["ics"] = ICS
|
||||
|
||||
EXTENSION_LOOKUP["csv"] = CSV
|
||||
|
||||
EXTENSION_LOOKUP["yml"] = YAML
|
||||
EXTENSION_LOOKUP["yaml"] = YAML
|
||||
|
||||
EXTENSION_LOOKUP["rss"] = RSS
|
||||
EXTENSION_LOOKUP["atom"] = ATOM
|
||||
|
||||
EXTENSION_LOOKUP["json"] = JSON
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
module ActionController
|
||||
# === Action Pack pagination for Active Record collections
|
||||
#
|
||||
# DEPRECATION WARNING: Pagination will be separated into its own plugin with Rails 2.0.
|
||||
#
|
||||
# The Pagination module aids in the process of paging large collections of
|
||||
# Active Record objects. It offers macro-style automatic fetching of your
|
||||
# model for multiple views, or explicit fetching for single actions. And if
|
||||
|
@ -104,8 +106,7 @@ module ActionController
|
|||
# ClassMethods#paginate.
|
||||
#
|
||||
# +options+ are:
|
||||
# <tt>:singular_name</tt>:: the singular name to use, if it can't be inferred by
|
||||
# singularizing the collection name
|
||||
# <tt>:singular_name</tt>:: the singular name to use, if it can't be inferred by singularizing the collection name
|
||||
# <tt>:class_name</tt>:: the class name to use, if it can't be inferred by
|
||||
# camelizing the singular name
|
||||
# <tt>:per_page</tt>:: the maximum number of items to include in a
|
||||
|
@ -192,7 +193,7 @@ module ActionController
|
|||
|
||||
def paginator_and_collection_for(collection_id, options) #:nodoc:
|
||||
klass = options[:class_name].constantize
|
||||
page = @params[options[:parameter]]
|
||||
page = params[options[:parameter]]
|
||||
count = count_collection_for_pagination(klass, options)
|
||||
paginator = Paginator.new(self, count, options[:per_page], page)
|
||||
collection = find_collection_for_pagination(klass, options, paginator)
|
||||
|
|
|
@ -13,12 +13,18 @@ module ActionController
|
|||
@parameters ||= request_parameters.update(query_parameters).update(path_parameters).with_indifferent_access
|
||||
end
|
||||
|
||||
# Returns the HTTP request method as a lowercase symbol (:get, for example)
|
||||
# Returns the HTTP request method as a lowercase symbol (:get, for example). Note, HEAD is returned as :get
|
||||
# since the two are supposedly to be functionaly equivilent for all purposes except that HEAD won't return a response
|
||||
# body (which Rails also takes care of elsewhere).
|
||||
def method
|
||||
@request_method ||= @env['REQUEST_METHOD'].downcase.to_sym
|
||||
@request_method ||= (!parameters[:_method].blank? && @env['REQUEST_METHOD'] == 'POST') ?
|
||||
parameters[:_method].to_s.downcase.to_sym :
|
||||
@env['REQUEST_METHOD'].downcase.to_sym
|
||||
|
||||
@request_method == :head ? :get : @request_method
|
||||
end
|
||||
|
||||
# Is this a GET request? Equivalent to request.method == :get
|
||||
# Is this a GET (or HEAD) request? Equivalent to request.method == :get
|
||||
def get?
|
||||
method == :get
|
||||
end
|
||||
|
@ -38,9 +44,10 @@ module ActionController
|
|||
method == :delete
|
||||
end
|
||||
|
||||
# Is this a HEAD request? Equivalent to request.method == :head
|
||||
# Is this a HEAD request? HEAD is mapped as :get for request.method, so here we ask the
|
||||
# REQUEST_METHOD header directly. Thus, for head, both get? and head? will return true.
|
||||
def head?
|
||||
method == :head
|
||||
@env['REQUEST_METHOD'].downcase.to_sym == :head
|
||||
end
|
||||
|
||||
# Determine whether the body of a HTTP call is URL-encoded (default)
|
||||
|
@ -128,19 +135,21 @@ module ActionController
|
|||
@env['RAW_POST_DATA']
|
||||
end
|
||||
|
||||
# Returns the request URI correctly, taking into account the idiosyncracies
|
||||
# of the various servers.
|
||||
# Return the request URI, accounting for server idiosyncracies.
|
||||
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
|
||||
def request_uri
|
||||
if uri = @env['REQUEST_URI']
|
||||
(%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri # Remove domain, which webrick puts into the request_uri.
|
||||
else # REQUEST_URI is blank under IIS - get this from PATH_INFO and SCRIPT_NAME
|
||||
# Remove domain, which webrick puts into the request_uri.
|
||||
(%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
|
||||
else
|
||||
# Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
|
||||
script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
|
||||
uri = @env['PATH_INFO']
|
||||
uri = uri.sub(/#{script_filename}\//, '') unless script_filename.nil?
|
||||
unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty?
|
||||
uri << '?' << env_qs
|
||||
end
|
||||
uri
|
||||
@env['REQUEST_URI'] = uri
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -159,11 +168,10 @@ module ActionController
|
|||
path = (uri = request_uri) ? uri.split('?').first : ''
|
||||
|
||||
# Cut off the path to the installation directory if given
|
||||
root = relative_url_root
|
||||
path[0, root.length] = '' if root
|
||||
path || ''
|
||||
path.sub!(%r/^#{relative_url_root}/, '')
|
||||
path || ''
|
||||
end
|
||||
|
||||
|
||||
# Returns the path minus the web server relative installation directory.
|
||||
# This can be set with the environment variable RAILS_RELATIVE_URL_ROOT.
|
||||
# It can be automatically extracted for Apache setups. If the server is not
|
||||
|
|
|
@ -6,12 +6,10 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# You can tailor the rescuing behavior and appearance by overwriting the following two stub methods.
|
||||
module Rescue
|
||||
def self.append_features(base) #:nodoc:
|
||||
super
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
alias_method :perform_action_without_rescue, :perform_action
|
||||
alias_method :perform_action, :perform_action_with_rescue
|
||||
alias_method_chain :perform_action, :rescue
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -36,23 +34,26 @@ module ActionController #:nodoc:
|
|||
|
||||
# Overwrite to implement custom logging of errors. By default logs as fatal.
|
||||
def log_error(exception) #:doc:
|
||||
if ActionView::TemplateError === exception
|
||||
logger.fatal(exception.to_s)
|
||||
else
|
||||
logger.fatal(
|
||||
"\n\n#{exception.class} (#{exception.message}):\n " +
|
||||
clean_backtrace(exception).join("\n ") +
|
||||
"\n\n"
|
||||
)
|
||||
ActiveSupport::Deprecation.silence do
|
||||
if ActionView::TemplateError === exception
|
||||
logger.fatal(exception.to_s)
|
||||
else
|
||||
logger.fatal(
|
||||
"\n\n#{exception.class} (#{exception.message}):\n " +
|
||||
clean_backtrace(exception).join("\n ") +
|
||||
"\n\n"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>).
|
||||
def rescue_action_in_public(exception) #:doc:
|
||||
case exception
|
||||
when RoutingError, UnknownAction then
|
||||
when RoutingError, UnknownAction
|
||||
render_text(IO.read(File.join(RAILS_ROOT, 'public', '404.html')), "404 Not Found")
|
||||
else render_text "<html><body><h1>Application error (Rails)</h1></body></html>"
|
||||
else
|
||||
render_text(IO.read(File.join(RAILS_ROOT, 'public', '500.html')), "500 Internal Error")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -60,19 +61,19 @@ module ActionController #:nodoc:
|
|||
# the remote IP being 127.0.0.1. For example, this could include the IP of the developer machine when debugging
|
||||
# remotely.
|
||||
def local_request? #:doc:
|
||||
[@request.remote_addr, @request.remote_ip] == ["127.0.0.1"] * 2
|
||||
[request.remote_addr, request.remote_ip] == ["127.0.0.1"] * 2
|
||||
end
|
||||
|
||||
# Renders a detailed diagnostics screen on action exceptions.
|
||||
def rescue_action_locally(exception)
|
||||
add_variables_to_assigns
|
||||
@template.instance_variable_set("@exception", exception)
|
||||
@template.instance_variable_set("@rescues_path", File.dirname(__FILE__) + "/templates/rescues/")
|
||||
@template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
|
||||
@template.send(:assign_variables_from_controller)
|
||||
|
||||
@template.instance_variable_set("@contents", @template.render_file(template_path_for_local_rescue(exception), false))
|
||||
|
||||
@headers["Content-Type"] = "text/html"
|
||||
response.content_type = Mime::HTML
|
||||
render_file(rescues_path("layout"), response_code_for_rescue(exception))
|
||||
end
|
||||
|
||||
|
@ -80,8 +81,8 @@ module ActionController #:nodoc:
|
|||
def perform_action_with_rescue #:nodoc:
|
||||
begin
|
||||
perform_action_without_rescue
|
||||
rescue Object => exception
|
||||
if defined?(Breakpoint) && @params["BP-RETRY"]
|
||||
rescue Exception => exception # errors from action performed
|
||||
if defined?(Breakpoint) && params["BP-RETRY"]
|
||||
msg = exception.backtrace.first
|
||||
if md = /^(.+?):(\d+)(?::in `(.+)')?$/.match(msg) then
|
||||
origin_file, origin_line = md[1], md[2].to_i
|
||||
|
@ -89,7 +90,7 @@ module ActionController #:nodoc:
|
|||
set_trace_func(lambda do |type, file, line, method, context, klass|
|
||||
if file == origin_file and line == origin_line then
|
||||
set_trace_func(nil)
|
||||
@params["BP-RETRY"] = false
|
||||
params["BP-RETRY"] = false
|
||||
|
||||
callstack = caller
|
||||
callstack.slice!(0) if callstack.first["rescue.rb"]
|
||||
|
@ -127,8 +128,10 @@ module ActionController #:nodoc:
|
|||
|
||||
def response_code_for_rescue(exception)
|
||||
case exception
|
||||
when UnknownAction, RoutingError then "404 Page Not Found"
|
||||
else "500 Internal Error"
|
||||
when UnknownAction, RoutingError
|
||||
"404 Page Not Found"
|
||||
else
|
||||
"500 Internal Error"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,15 +1,33 @@
|
|||
module ActionController
|
||||
class AbstractResponse #:nodoc:
|
||||
DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
|
||||
attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params
|
||||
attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout
|
||||
|
||||
def initialize
|
||||
@body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
|
||||
end
|
||||
|
||||
def content_type=(mime_type)
|
||||
@headers["Content-Type"] = charset ? "#{mime_type}; charset=#{charset}" : mime_type
|
||||
end
|
||||
|
||||
def content_type
|
||||
content_type = String(@headers["Content-Type"]).split(";")[0]
|
||||
content_type.blank? ? nil : content_type
|
||||
end
|
||||
|
||||
def charset=(encoding)
|
||||
@headers["Content-Type"] = "#{content_type || "text/html"}; charset=#{encoding}"
|
||||
end
|
||||
|
||||
def charset
|
||||
charset = String(@headers["Content-Type"]).split(";")[1]
|
||||
charset.blank? ? nil : charset.strip.split("=")[1]
|
||||
end
|
||||
|
||||
def redirect(to_url, permanently = false)
|
||||
@headers["Status"] = "302 Found" unless @headers["Status"] == "301 Moved Permanently"
|
||||
@headers["location"] = to_url
|
||||
@headers["Location"] = to_url
|
||||
|
||||
@body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
|
||||
end
|
||||
|
|
1824
vendor/rails/actionpack/lib/action_controller/routing.rb
vendored
1824
vendor/rails/actionpack/lib/action_controller/routing.rb
vendored
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,6 @@
|
|||
module ActionController
|
||||
module Scaffolding # :nodoc:
|
||||
def self.append_features(base)
|
||||
super
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
|
@ -25,25 +24,25 @@ module ActionController
|
|||
# end
|
||||
#
|
||||
# def list
|
||||
# @entries = Entry.find_all
|
||||
# @entries = Entry.find(:all)
|
||||
# render_scaffold "list"
|
||||
# end
|
||||
#
|
||||
#
|
||||
# def show
|
||||
# @entry = Entry.find(params[:id])
|
||||
# render_scaffold
|
||||
# end
|
||||
#
|
||||
#
|
||||
# def destroy
|
||||
# Entry.find(params[:id]).destroy
|
||||
# redirect_to :action => "list"
|
||||
# end
|
||||
#
|
||||
#
|
||||
# def new
|
||||
# @entry = Entry.new
|
||||
# render_scaffold
|
||||
# end
|
||||
#
|
||||
#
|
||||
# def create
|
||||
# @entry = Entry.new(params[:entry])
|
||||
# if @entry.save
|
||||
|
@ -53,16 +52,16 @@ module ActionController
|
|||
# render_scaffold('new')
|
||||
# end
|
||||
# end
|
||||
#
|
||||
#
|
||||
# def edit
|
||||
# @entry = Entry.find(params[:id])
|
||||
# render_scaffold
|
||||
# end
|
||||
#
|
||||
#
|
||||
# def update
|
||||
# @entry = Entry.find(params[:id])
|
||||
# @entry.attributes = params[:entry]
|
||||
#
|
||||
#
|
||||
# if @entry.save
|
||||
# flash[:notice] = "Entry was successfully updated"
|
||||
# redirect_to :action => "show", :id => @entry
|
||||
|
@ -72,17 +71,17 @@ module ActionController
|
|||
# end
|
||||
# end
|
||||
#
|
||||
# The <tt>render_scaffold</tt> method will first check to see if you've made your own template (like "weblog/show.rhtml" for
|
||||
# the show action) and if not, then render the generic template for that action. This gives you the possibility of using the
|
||||
# scaffold while you're building your specific application. Start out with a totally generic setup, then replace one template
|
||||
# The <tt>render_scaffold</tt> method will first check to see if you've made your own template (like "weblog/show.rhtml" for
|
||||
# the show action) and if not, then render the generic template for that action. This gives you the possibility of using the
|
||||
# scaffold while you're building your specific application. Start out with a totally generic setup, then replace one template
|
||||
# and one action at a time while relying on the rest of the scaffolded templates and actions.
|
||||
module ClassMethods
|
||||
# Adds a swath of generic CRUD actions to the controller. The +model_id+ is automatically converted into a class name unless
|
||||
# one is specifically provide through <tt>options[:class_name]</tt>. So <tt>scaffold :post</tt> would use Post as the class
|
||||
# and @post/@posts for the instance variables.
|
||||
#
|
||||
#
|
||||
# It's possible to use more than one scaffold in a single controller by specifying <tt>options[:suffix] = true</tt>. This will
|
||||
# make <tt>scaffold :post, :suffix => true</tt> use method names like list_post, show_post, and create_post
|
||||
# make <tt>scaffold :post, :suffix => true</tt> use method names like list_post, show_post, and create_post
|
||||
# instead of just list, show, and post. If suffix is used, then no index method is added.
|
||||
def scaffold(model_id, options = {})
|
||||
options.assert_valid_keys(:class_name, :suffix)
|
||||
|
@ -99,13 +98,13 @@ module ActionController
|
|||
end
|
||||
end_eval
|
||||
end
|
||||
|
||||
|
||||
module_eval <<-"end_eval", __FILE__, __LINE__
|
||||
|
||||
|
||||
verify :method => :post, :only => [ :destroy#{suffix}, :create#{suffix}, :update#{suffix} ],
|
||||
:redirect_to => { :action => :list#{suffix} }
|
||||
|
||||
|
||||
|
||||
|
||||
def list#{suffix}
|
||||
@#{singular_name}_pages, @#{plural_name} = paginate :#{plural_name}, :per_page => 10
|
||||
render#{suffix}_scaffold "list#{suffix}"
|
||||
|
@ -115,17 +114,17 @@ module ActionController
|
|||
@#{singular_name} = #{class_name}.find(params[:id])
|
||||
render#{suffix}_scaffold
|
||||
end
|
||||
|
||||
|
||||
def destroy#{suffix}
|
||||
#{class_name}.find(params[:id]).destroy
|
||||
redirect_to :action => "list#{suffix}"
|
||||
end
|
||||
|
||||
|
||||
def new#{suffix}
|
||||
@#{singular_name} = #{class_name}.new
|
||||
render#{suffix}_scaffold
|
||||
end
|
||||
|
||||
|
||||
def create#{suffix}
|
||||
@#{singular_name} = #{class_name}.new(params[:#{singular_name}])
|
||||
if @#{singular_name}.save
|
||||
|
@ -135,12 +134,12 @@ module ActionController
|
|||
render#{suffix}_scaffold('new')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def edit#{suffix}
|
||||
@#{singular_name} = #{class_name}.find(params[:id])
|
||||
render#{suffix}_scaffold
|
||||
end
|
||||
|
||||
|
||||
def update#{suffix}
|
||||
@#{singular_name} = #{class_name}.find(params[:id])
|
||||
@#{singular_name}.attributes = params[:#{singular_name}]
|
||||
|
@ -152,14 +151,14 @@ module ActionController
|
|||
render#{suffix}_scaffold('edit')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def render#{suffix}_scaffold(action=nil)
|
||||
action ||= caller_method_name(caller)
|
||||
# logger.info ("testing template:" + "\#{self.class.controller_path}/\#{action}") if logger
|
||||
|
||||
|
||||
if template_exists?("\#{self.class.controller_path}/\#{action}")
|
||||
render_action(action)
|
||||
render :action => action
|
||||
else
|
||||
@scaffold_class = #{class_name}
|
||||
@scaffold_singular_name, @scaffold_plural_name = "#{singular_name}", "#{plural_name}"
|
||||
|
@ -169,9 +168,9 @@ module ActionController
|
|||
@template.instance_variable_set("@content_for_layout", @template.render_file(scaffold_path(action.sub(/#{suffix}$/, "")), false))
|
||||
|
||||
if !active_layout.nil?
|
||||
render_file(active_layout, nil, true)
|
||||
render :file => active_layout, :use_full_path => true
|
||||
else
|
||||
render_file(scaffold_path("layout"))
|
||||
render :file => scaffold_path('layout')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -179,12 +178,12 @@ module ActionController
|
|||
def scaffold_path(template_name)
|
||||
File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml"
|
||||
end
|
||||
|
||||
|
||||
def caller_method_name(caller)
|
||||
caller.first.scan(/`(.*)'/).first.first # ' ruby-mode
|
||||
end
|
||||
end_eval
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,6 +5,8 @@ require 'base64'
|
|||
|
||||
class CGI
|
||||
class Session
|
||||
attr_reader :data
|
||||
|
||||
# Return this session's underlying Session instance. Useful for the DB-backed session stores.
|
||||
def model
|
||||
@dbman.model if @dbman
|
||||
|
|
|
@ -26,6 +26,10 @@ class CGI #:nodoc:all
|
|||
def delete
|
||||
@@session_data.delete(@session_id)
|
||||
end
|
||||
|
||||
def data
|
||||
@@session_data[@session_id]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -93,6 +93,10 @@ begin
|
|||
end
|
||||
@session_data = {}
|
||||
end
|
||||
|
||||
def data
|
||||
@session_data
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,12 +8,9 @@ module ActionController #:nodoc:
|
|||
module SessionManagement #:nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
|
||||
base.send :alias_method, :process_without_session_management_support, :process
|
||||
base.send :alias_method, :process, :process_with_session_management_support
|
||||
|
||||
base.send :alias_method, :process_cleanup_without_session_management_support, :process_cleanup
|
||||
base.send :alias_method, :process_cleanup, :process_cleanup_with_session_management_support
|
||||
|
||||
base.send :alias_method_chain, :process, :session_management_support
|
||||
base.send :alias_method_chain, :process_cleanup, :session_management_support
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
@ -123,16 +120,16 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def process_cleanup_with_session_management_support
|
||||
process_cleanup_without_session_management_support
|
||||
clear_persistent_model_associations
|
||||
process_cleanup_without_session_management_support
|
||||
end
|
||||
|
||||
# Clear cached associations in session data so they don't overflow
|
||||
# the database field. Only applies to ActiveRecordStore since there
|
||||
# is not a standard way to iterate over session data.
|
||||
def clear_persistent_model_associations #:doc:
|
||||
if defined?(@session) && @session.instance_variables.include?('@data')
|
||||
session_data = @session.instance_variable_get('@data')
|
||||
if defined?(@_session) && @_session.respond_to?(:data)
|
||||
session_data = @_session.data
|
||||
|
||||
if session_data && session_data.respond_to?(:each_value)
|
||||
session_data.each_value do |obj|
|
||||
|
|
|
@ -69,17 +69,8 @@ module ActionController #:nodoc:
|
|||
logger.info "Streaming file #{path}" unless logger.nil?
|
||||
len = options[:buffer_size] || 4096
|
||||
File.open(path, 'rb') do |file|
|
||||
if output.respond_to?(:syswrite)
|
||||
begin
|
||||
while true
|
||||
output.syswrite(file.sysread(len))
|
||||
end
|
||||
rescue EOFError
|
||||
end
|
||||
else
|
||||
while buf = file.read(len)
|
||||
output.write(buf)
|
||||
end
|
||||
while buf = file.read(len)
|
||||
output.write(buf)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
@ -97,8 +88,8 @@ module ActionController #:nodoc:
|
|||
# * <tt>:type</tt> - specifies an HTTP content type.
|
||||
# Defaults to 'application/octet-stream'.
|
||||
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
||||
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
|
||||
# Valid values are 'inline' and 'attachment' (default).
|
||||
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
|
||||
#
|
||||
# Generic data download:
|
||||
# send_data buffer
|
||||
|
@ -125,10 +116,10 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
disposition = options[:disposition].dup || 'attachment'
|
||||
|
||||
|
||||
disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
|
||||
|
||||
@headers.update(
|
||||
headers.update(
|
||||
'Content-Length' => options[:length],
|
||||
'Content-Type' => options[:type].strip, # fixes a problem with extra '\r' with some browsers
|
||||
'Content-Disposition' => disposition,
|
||||
|
@ -141,7 +132,7 @@ module ActionController #:nodoc:
|
|||
# after it displays the "open/save" dialog, which means that if you
|
||||
# hit "open" the file isn't there anymore when the application that
|
||||
# is called for handling the download is run, so let's workaround that
|
||||
@headers['Cache-Control'] = 'private' if @headers['Cache-Control'] == 'no-cache'
|
||||
headers['Cache-Control'] = 'private' if headers['Cache-Control'] == 'no-cache'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
<% if false %>
|
||||
<br /><br />
|
||||
<% begin %>
|
||||
<%= form_tag(@request.request_uri, "method" => @request.method) %>
|
||||
<%= form_tag(request.request_uri, "method" => request.method) %>
|
||||
<input type="hidden" name="BP-RETRY" value="1" />
|
||||
|
||||
<% for key, values in @params %>
|
||||
<% for key, values in params %>
|
||||
<% next if key == "BP-RETRY" %>
|
||||
<% for value in Array(values) %>
|
||||
<input type="hidden" name="<%= key %>" value="<%= value %>" />
|
||||
|
@ -26,7 +26,7 @@
|
|||
<% end %>
|
||||
|
||||
<%
|
||||
request_parameters_without_action = @request.parameters.clone
|
||||
request_parameters_without_action = request.parameters.clone
|
||||
request_parameters_without_action.delete("action")
|
||||
request_parameters_without_action.delete("controller")
|
||||
|
||||
|
@ -37,8 +37,8 @@
|
|||
<p><b>Parameters</b>: <%=h request_dump == "{}" ? "None" : request_dump %></p>
|
||||
|
||||
<p><a href="#" onclick="document.getElementById('session_dump').style.display='block'; return false;">Show session dump</a></p>
|
||||
<div id="session_dump" style="display:none"><%= debug(@request.session.instance_variable_get("@data")) %></div>
|
||||
<div id="session_dump" style="display:none"><%= debug(request.session.instance_variable_get("@data")) %></div>
|
||||
|
||||
|
||||
<h2 style="margin-top: 30px">Response</h2>
|
||||
<b>Headers</b>: <%=h @response.headers.inspect.gsub(/,/, ",\n") %><br/>
|
||||
<b>Headers</b>: <%=h response ? response.headers.inspect.gsub(/,/, ",\n") : "None" %><br/>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<h1>
|
||||
<%=h @exception.class.to_s %>
|
||||
<% if @request.parameters['controller'] %>
|
||||
in <%=h @request.parameters['controller'].humanize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
|
||||
<% if request.parameters['controller'] %>
|
||||
in <%=h request.parameters['controller'].humanize %>Controller<% if request.parameters['action'] %>#<%=h request.parameters['action'] %><% end %>
|
||||
<% end %>
|
||||
</h1>
|
||||
<pre><%=h @exception.clean_message %></pre>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<h1>Routing Error</h1>
|
||||
<p><pre><%=h @exception.message %></pre></p>
|
||||
<% unless @exception.failures.empty? %><p>
|
||||
<h2>Failure reasons:</h2>
|
||||
<ol>
|
||||
<% @exception.failures.each do |route, reason| %>
|
||||
<li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
|
||||
<h2>Failure reasons:</h2>
|
||||
<ol>
|
||||
<% @exception.failures.each do |route, reason| %>
|
||||
<li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
|
||||
<% end %>
|
||||
</ol>
|
||||
</p><% end %>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<h1>
|
||||
<%=h @exception.original_exception.class.to_s %> in
|
||||
<%=h @request.parameters["controller"].capitalize if @request.parameters["controller"]%>#<%=h @request.parameters["action"] %>
|
||||
<%=h request.parameters["controller"].capitalize if request.parameters["controller"]%>#<%=h request.parameters["action"] %>
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<% end %>
|
||||
<td><%= link_to "Show", :action => "show#{@scaffold_suffix}", :id => entry %></td>
|
||||
<td><%= link_to "Edit", :action => "edit#{@scaffold_suffix}", :id => entry %></td>
|
||||
<td><%= link_to "Destroy", {:action => "destroy#{@scaffold_suffix}", :id => entry}, { :confirm => "Are you sure?", :post => true} %></td>
|
||||
<td><%= link_to "Destroy", {:action => "destroy#{@scaffold_suffix}", :id => entry}, { :confirm => "Are you sure?", :method => :post } %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
require File.dirname(__FILE__) + '/assertions'
|
||||
require File.dirname(__FILE__) + '/deprecated_assertions'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
class Base
|
||||
|
@ -18,8 +17,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
alias_method :process_without_test, :process
|
||||
alias_method :process, :process_with_test
|
||||
alias_method_chain :process, :test
|
||||
end
|
||||
|
||||
class TestRequest < AbstractRequest #:nodoc:
|
||||
|
@ -39,8 +37,8 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def reset_session
|
||||
@session = {}
|
||||
end
|
||||
@session = TestSession.new
|
||||
end
|
||||
|
||||
def raw_post
|
||||
if raw_post = env['RAW_POST_DATA']
|
||||
|
@ -79,6 +77,10 @@ module ActionController #:nodoc:
|
|||
@path = uri.split("?").first
|
||||
end
|
||||
|
||||
def accept=(mime_types)
|
||||
@env["HTTP_ACCEPT"] = Array(mime_types).collect { |mime_types| mime_types.to_s }.join(",")
|
||||
end
|
||||
|
||||
def remote_addr=(addr)
|
||||
@env['REMOTE_ADDR'] = addr
|
||||
end
|
||||
|
@ -103,7 +105,7 @@ module ActionController #:nodoc:
|
|||
if value.is_a? Fixnum
|
||||
value = value.to_s
|
||||
elsif value.is_a? Array
|
||||
value = ActionController::Routing::PathComponent::Result.new(value)
|
||||
value = ActionController::Routing::PathSegment::Result.new(value)
|
||||
end
|
||||
|
||||
if extra_keys.include?(key.to_sym)
|
||||
|
@ -112,6 +114,7 @@ module ActionController #:nodoc:
|
|||
path_parameters[key.to_s] = value
|
||||
end
|
||||
end
|
||||
@parameters = nil # reset TestRequest#parameters to use the new path_parameters
|
||||
end
|
||||
|
||||
def recycle!
|
||||
|
@ -176,7 +179,7 @@ module ActionController #:nodoc:
|
|||
|
||||
# returns the redirection location or nil
|
||||
def redirect_url
|
||||
redirect? ? headers['location'] : nil
|
||||
headers['Location']
|
||||
end
|
||||
|
||||
# does the redirect location match this regexp pattern?
|
||||
|
@ -272,27 +275,40 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
class TestSession #:nodoc:
|
||||
def initialize(attributes = {})
|
||||
attr_accessor :session_id
|
||||
|
||||
def initialize(attributes = nil)
|
||||
@session_id = ''
|
||||
@attributes = attributes
|
||||
@saved_attributes = nil
|
||||
end
|
||||
|
||||
def data
|
||||
@attributes ||= @saved_attributes || {}
|
||||
end
|
||||
|
||||
def [](key)
|
||||
@attributes[key]
|
||||
data[key.to_s]
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
@attributes[key] = value
|
||||
data[key.to_s] = value
|
||||
end
|
||||
|
||||
def session_id
|
||||
""
|
||||
def update
|
||||
@saved_attributes = @attributes
|
||||
end
|
||||
|
||||
def update() end
|
||||
def close() end
|
||||
def delete() @attributes = {} end
|
||||
def delete
|
||||
@attributes = nil
|
||||
end
|
||||
|
||||
def close
|
||||
update
|
||||
delete
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Essentially generates a modified Tempfile object similar to the object
|
||||
# you'd get from the standard library CGI module in a multipart
|
||||
# request. This means you can use an ActionController::TestUploadedFile
|
||||
|
@ -301,6 +317,7 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# Usage example, within a functional test:
|
||||
# post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png')
|
||||
require 'tempfile'
|
||||
class TestUploadedFile
|
||||
# The filename, *not* including the path, of the "uploaded" file
|
||||
attr_reader :original_filename
|
||||
|
@ -309,7 +326,7 @@ module ActionController #:nodoc:
|
|||
attr_reader :content_type
|
||||
|
||||
def initialize(path, content_type = 'text/plain')
|
||||
raise "file does not exist" unless File.exist?(path)
|
||||
raise "#{path} file does not exist" unless File.exist?(path)
|
||||
@content_type = content_type
|
||||
@original_filename = path.sub(/^.*#{File::SEPARATOR}([^#{File::SEPARATOR}]+)$/) { $1 }
|
||||
@tempfile = Tempfile.new(@original_filename)
|
||||
|
@ -333,7 +350,7 @@ module ActionController #:nodoc:
|
|||
%w( get post put delete head ).each do |method|
|
||||
base.class_eval <<-EOV, __FILE__, __LINE__
|
||||
def #{method}(action, parameters = nil, session = nil, flash = nil)
|
||||
@request.env['REQUEST_METHOD'] = "#{method.upcase}" if @request
|
||||
@request.env['REQUEST_METHOD'] = "#{method.upcase}" if defined?(@request)
|
||||
process(action, parameters, session, flash)
|
||||
end
|
||||
EOV
|
||||
|
@ -344,8 +361,10 @@ module ActionController #:nodoc:
|
|||
def process(action, parameters = nil, session = nil, flash = nil)
|
||||
# Sanity check for required instance variables so we can give an
|
||||
# understandable error message.
|
||||
%w(controller request response).each do |iv_name|
|
||||
raise "@#{iv_name} is nil: make sure you set it in your test's setup method." if instance_variable_get("@#{iv_name}").nil?
|
||||
%w(@controller @request @response).each do |iv_name|
|
||||
if !instance_variables.include?(iv_name) || instance_variable_get(iv_name).nil?
|
||||
raise "#{iv_name} is nil: make sure you set it in your test's setup method."
|
||||
end
|
||||
end
|
||||
|
||||
@request.recycle!
|
||||
|
@ -374,8 +393,9 @@ module ActionController #:nodoc:
|
|||
alias xhr :xml_http_request
|
||||
|
||||
def follow_redirect
|
||||
if @response.redirected_to[:controller]
|
||||
raise "Can't follow redirects outside of current controller (#{@response.redirected_to[:controller]})"
|
||||
redirected_controller = @response.redirected_to[:controller]
|
||||
if redirected_controller && redirected_controller != @controller.controller_name
|
||||
raise "Can't follow redirects outside of current controller (from #{@controller.controller_name} to #{redirected_controller})"
|
||||
end
|
||||
|
||||
get(@response.redirected_to.delete(:action), @response.redirected_to.stringify_keys)
|
||||
|
@ -428,7 +448,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def method_missing(selector, *args)
|
||||
return @controller.send(selector, *args) if ActionController::Routing::NamedRoutes::Helpers.include?(selector)
|
||||
return @controller.send(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
|
||||
return super
|
||||
end
|
||||
|
||||
|
@ -448,13 +468,15 @@ module ActionController #:nodoc:
|
|||
# The new instance is yielded to the passed block. Typically the block
|
||||
# will create some routes using map.draw { map.connect ... }:
|
||||
#
|
||||
# with_routing do |set|
|
||||
# set.draw { set.connect ':controller/:id/:action' }
|
||||
# assert_equal(
|
||||
# ['/content/10/show', {}],
|
||||
# set.generate(:controller => 'content', :id => 10, :action => 'show')
|
||||
# )
|
||||
# end
|
||||
# with_routing do |set|
|
||||
# set.draw do |map|
|
||||
# map.connect ':controller/:action/:id'
|
||||
# assert_equal(
|
||||
# ['/content/10/show', {}],
|
||||
# map.generate(:controller => 'content', :id => 10, :action => 'show')
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def with_routing
|
||||
real_routes = ActionController::Routing::Routes
|
||||
|
|
|
@ -1,13 +1,71 @@
|
|||
module ActionController
|
||||
|
||||
# Write URLs from arbitrary places in your codebase, such as your mailers.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# class MyMailer
|
||||
# include ActionController::UrlWriter
|
||||
# default_url_options[:host] = 'www.basecamphq.com'
|
||||
#
|
||||
# def signup_url(token)
|
||||
# url_for(:controller => 'signup', action => 'index', :token => token)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# In addition to providing +url_for+, named routes are also accessible after
|
||||
# including UrlWriter.
|
||||
#
|
||||
module UrlWriter
|
||||
|
||||
# The default options for urls written by this writer. Typically a :host pair
|
||||
# is provided.
|
||||
mattr_accessor :default_url_options
|
||||
self.default_url_options = {}
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
ActionController::Routing::Routes.named_routes.install base
|
||||
base.mattr_accessor :default_url_options
|
||||
base.default_url_options ||= default_url_options
|
||||
end
|
||||
|
||||
# Generate a url with the provided options. The following special options may
|
||||
# effect the constructed url:
|
||||
#
|
||||
# * :host Specifies the host the link should be targetted at. This option
|
||||
# must be provided either explicitly, or via default_url_options.
|
||||
# * :protocol The protocol to connect to. Defaults to 'http'
|
||||
# * :port Optionally specify the port to connect to.
|
||||
#
|
||||
def url_for(options)
|
||||
options = self.class.default_url_options.merge(options)
|
||||
|
||||
url = ''
|
||||
unless options.delete :only_path
|
||||
url << (options.delete(:protocol) || 'http')
|
||||
url << '://'
|
||||
|
||||
raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
|
||||
url << options.delete(:host)
|
||||
url << ":#{options.delete(:port)}" if options.key?(:port)
|
||||
else
|
||||
# Delete the unused options to prevent their appearance in the query string
|
||||
[:protocol, :host, :port].each { |k| options.delete k }
|
||||
end
|
||||
url << Routing::Routes.generate(options, {})
|
||||
return url
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Rewrites URLs for Base.redirect_to and Base.url_for in the controller.
|
||||
|
||||
class UrlRewriter #:nodoc:
|
||||
RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :trailing_slash, :skip_relative_url_root]
|
||||
def initialize(request, parameters)
|
||||
@request, @parameters = request, parameters
|
||||
end
|
||||
|
||||
def rewrite(options = {})
|
||||
def rewrite(options = {})
|
||||
rewrite_url(rewrite_path(options), options)
|
||||
end
|
||||
|
||||
|
@ -41,34 +99,10 @@ module ActionController
|
|||
options.update(overwrite)
|
||||
end
|
||||
RESERVED_OPTIONS.each {|k| options.delete k}
|
||||
path, extra_keys = Routing::Routes.generate(options.dup, @request) # Warning: Routes will mutate and violate the options hash
|
||||
|
||||
path << build_query_string(options, extra_keys) unless extra_keys.empty?
|
||||
|
||||
path
|
||||
end
|
||||
|
||||
# Returns a query string with escaped keys and values from the passed hash. If the passed hash contains an "id" it'll
|
||||
# be added as a path element instead of a regular parameter pair.
|
||||
def build_query_string(hash, only_keys = nil)
|
||||
elements = []
|
||||
query_string = ""
|
||||
|
||||
only_keys ||= hash.keys
|
||||
|
||||
only_keys.each do |key|
|
||||
value = hash[key]
|
||||
key = CGI.escape key.to_s
|
||||
if value.class == Array
|
||||
key << '[]'
|
||||
else
|
||||
value = [ value ]
|
||||
end
|
||||
value.each { |val| elements << "#{key}=#{Routing.extract_parameter_value(val)}" }
|
||||
end
|
||||
|
||||
query_string << ("?" + elements.join("&")) unless elements.empty?
|
||||
query_string
|
||||
# Generates the query string, too
|
||||
Routing::Routes.generate(options, @request.symbolized_path_parameters)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require File.dirname(__FILE__) + '/tokenizer'
|
||||
require File.dirname(__FILE__) + '/node'
|
||||
require File.dirname(__FILE__) + '/selector'
|
||||
|
||||
module HTML #:nodoc:
|
||||
|
||||
|
|
|
@ -92,7 +92,6 @@ module HTML #:nodoc:
|
|||
# returns non +nil+. Returns the result of the #find call that succeeded.
|
||||
def find(conditions)
|
||||
conditions = validate_conditions(conditions)
|
||||
|
||||
@children.each do |child|
|
||||
node = child.find(conditions)
|
||||
return node if node
|
||||
|
@ -152,11 +151,11 @@ module HTML #:nodoc:
|
|||
|
||||
if scanner.skip(/!\[CDATA\[/)
|
||||
scanner.scan_until(/\]\]>/)
|
||||
return CDATA.new(parent, line, pos, scanner.pre_match)
|
||||
return CDATA.new(parent, line, pos, scanner.pre_match.gsub(/<!\[CDATA\[/, ''))
|
||||
end
|
||||
|
||||
closing = ( scanner.scan(/\//) ? :close : nil )
|
||||
return Text.new(parent, line, pos, content) unless name = scanner.scan(/[\w:]+/)
|
||||
return Text.new(parent, line, pos, content) unless name = scanner.scan(/[\w:-]+/)
|
||||
name.downcase!
|
||||
|
||||
unless closing
|
||||
|
@ -239,7 +238,7 @@ module HTML #:nodoc:
|
|||
def match(conditions)
|
||||
case conditions
|
||||
when String
|
||||
@content.index(conditions)
|
||||
@content == conditions
|
||||
when Regexp
|
||||
@content =~ conditions
|
||||
when Hash
|
||||
|
@ -316,7 +315,7 @@ module HTML #:nodoc:
|
|||
s = "<#{@name}"
|
||||
@attributes.each do |k,v|
|
||||
s << " #{k}"
|
||||
s << "='#{v.gsub(/'/,"\\\\'")}'" if String === v
|
||||
s << "=\"#{v}\"" if String === v
|
||||
end
|
||||
s << " /" if @closing == :self
|
||||
s << ">"
|
||||
|
@ -410,7 +409,6 @@ module HTML #:nodoc:
|
|||
# :child => /hello world/ }
|
||||
def match(conditions)
|
||||
conditions = validate_conditions(conditions)
|
||||
|
||||
# check content of child nodes
|
||||
if conditions[:content]
|
||||
if children.empty?
|
||||
|
@ -455,7 +453,6 @@ module HTML #:nodoc:
|
|||
# count children
|
||||
if opts = conditions[:children]
|
||||
matches = children.select do |c|
|
||||
c.match(/./) or
|
||||
(c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?))
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
module ActionController #:nodoc:
|
||||
module Verification #:nodoc:
|
||||
def self.append_features(base) #:nodoc:
|
||||
super
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
|
@ -18,19 +17,26 @@ module ActionController #:nodoc:
|
|||
# Usage:
|
||||
#
|
||||
# class GlobalController < ActionController::Base
|
||||
# # prevent the #update_settings action from being invoked unless
|
||||
# # the 'admin_privileges' request parameter exists.
|
||||
# # Prevent the #update_settings action from being invoked unless
|
||||
# # the 'admin_privileges' request parameter exists. The
|
||||
# # settings action will be redirected to in current controller
|
||||
# # if verification fails.
|
||||
# verify :params => "admin_privileges", :only => :update_post,
|
||||
# :redirect_to => { :action => "settings" }
|
||||
#
|
||||
# # disallow a post from being updated if there was no information
|
||||
# # Disallow a post from being updated if there was no information
|
||||
# # submitted with the post, and if there is no active post in the
|
||||
# # session, and if there is no "note" key in the flash.
|
||||
# # session, and if there is no "note" key in the flash. The route
|
||||
# # named category_url will be redirected to if verification fails.
|
||||
#
|
||||
# verify :params => "post", :session => "post", "flash" => "note",
|
||||
# :only => :update_post,
|
||||
# :add_flash => { "alert" => "Failed to create your message" },
|
||||
# :redirect_to => :category_url
|
||||
#
|
||||
# Note that these prerequisites are not business rules. They do not examine
|
||||
# the content of the session or the parameters. That level of validation should
|
||||
# be encapsulated by your domain model or helper methods in the controller.
|
||||
module ClassMethods
|
||||
# Verify the given actions so that if certain prerequisites are not met,
|
||||
# the user is redirected to a different action. The +options+ parameter
|
||||
|
@ -40,7 +46,7 @@ module ActionController #:nodoc:
|
|||
# be in the <tt>params</tt> hash in order for the action(s) to be safely
|
||||
# called.
|
||||
# * <tt>:session</tt>: a single key or an array of keys that must
|
||||
# be in the @session in order for the action(s) to be safely called.
|
||||
# be in the <tt>session</tt> in order for the action(s) to be safely called.
|
||||
# * <tt>:flash</tt>: a single key or an array of keys that must
|
||||
# be in the flash in order for the action(s) to be safely called.
|
||||
# * <tt>:method</tt>: a single key or an array of keys--any one of which
|
||||
|
@ -51,8 +57,12 @@ module ActionController #:nodoc:
|
|||
# from an Ajax call or not.
|
||||
# * <tt>:add_flash</tt>: a hash of name/value pairs that should be merged
|
||||
# into the session's flash if the prerequisites cannot be satisfied.
|
||||
# * <tt>:add_headers</tt>: a hash of name/value pairs that should be
|
||||
# merged into the response's headers hash if the prerequisites cannot
|
||||
# be satisfied.
|
||||
# * <tt>:redirect_to</tt>: the redirection parameters to be used when
|
||||
# redirecting if the prerequisites cannot be satisfied.
|
||||
# redirecting if the prerequisites cannot be satisfied. You can
|
||||
# redirect either to named route or to the action in some controller.
|
||||
# * <tt>:render</tt>: the render parameters to be used when
|
||||
# the prerequisites cannot be satisfied.
|
||||
# * <tt>:only</tt>: only apply this verification to the actions specified
|
||||
|
@ -69,19 +79,20 @@ module ActionController #:nodoc:
|
|||
|
||||
def verify_action(options) #:nodoc:
|
||||
prereqs_invalid =
|
||||
[*options[:params] ].find { |v| @params[v].nil? } ||
|
||||
[*options[:session]].find { |v| @session[v].nil? } ||
|
||||
[*options[:params] ].find { |v| params[v].nil? } ||
|
||||
[*options[:session]].find { |v| session[v].nil? } ||
|
||||
[*options[:flash] ].find { |v| flash[v].nil? }
|
||||
|
||||
if !prereqs_invalid && options[:method]
|
||||
prereqs_invalid ||=
|
||||
[*options[:method]].all? { |v| @request.method != v.to_sym }
|
||||
[*options[:method]].all? { |v| request.method != v.to_sym }
|
||||
end
|
||||
|
||||
prereqs_invalid ||= (request.xhr? != options[:xhr]) unless options[:xhr].nil?
|
||||
|
||||
if prereqs_invalid
|
||||
flash.update(options[:add_flash]) if options[:add_flash]
|
||||
response.headers.update(options[:add_headers]) if options[:add_headers]
|
||||
unless performed?
|
||||
render(options[:render]) if options[:render]
|
||||
redirect_to(options[:redirect_to]) if options[:redirect_to]
|
||||
|
|
2
vendor/rails/actionpack/lib/action_pack.rb
vendored
2
vendor/rails/actionpack/lib/action_pack.rb
vendored
|
@ -1,5 +1,5 @@
|
|||
#--
|
||||
# Copyright (c) 2004 David Heinemeier Hansson
|
||||
# Copyright (c) 2004-2006 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
module ActionPack #:nodoc:
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 1
|
||||
MINOR = 12
|
||||
TINY = 5
|
||||
MINOR = 13
|
||||
TINY = 2
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
|
2
vendor/rails/actionpack/lib/action_view.rb
vendored
2
vendor/rails/actionpack/lib/action_view.rb
vendored
|
@ -1,5 +1,5 @@
|
|||
#--
|
||||
# Copyright (c) 2004 David Heinemeier Hansson
|
||||
# Copyright (c) 2004-2006 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
|
|
89
vendor/rails/actionpack/lib/action_view/base.rb
vendored
89
vendor/rails/actionpack/lib/action_view/base.rb
vendored
|
@ -1,7 +1,6 @@
|
|||
require 'erb'
|
||||
|
||||
module ActionView #:nodoc:
|
||||
|
||||
class ActionViewError < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
|
@ -54,13 +53,22 @@ module ActionView #:nodoc:
|
|||
#
|
||||
# You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values:
|
||||
#
|
||||
# <%= render "shared/header", { "headline" => "Welcome", "person" => person } %>
|
||||
# <%= render "shared/header", { :headline => "Welcome", :person => person } %>
|
||||
#
|
||||
# These can now be accessed in shared/header with:
|
||||
#
|
||||
# Headline: <%= headline %>
|
||||
# First name: <%= person.first_name %>
|
||||
#
|
||||
# If you need to find out whether a certain local variable has been assigned a value in a particular render call,
|
||||
# you need to use the following pattern:
|
||||
#
|
||||
# <% if local_assigns.has_key? :headline %>
|
||||
# Headline: <%= headline %>
|
||||
# <% end %>
|
||||
#
|
||||
# Testing using <tt>defined? headline</tt> will not work. This is an implementation restriction.
|
||||
#
|
||||
# == Template caching
|
||||
#
|
||||
# By default, Rails will compile each template to a method in order to render it. When you alter a template, Rails will
|
||||
|
@ -148,7 +156,8 @@ module ActionView #:nodoc:
|
|||
attr_accessor :base_path, :assigns, :template_extension
|
||||
attr_accessor :controller
|
||||
|
||||
attr_reader :logger, :params, :request, :response, :session, :headers, :flash
|
||||
attr_reader :logger, :response, :headers
|
||||
attr_internal *ActionController::Base::DEPRECATED_INSTANCE_VARIABLES
|
||||
|
||||
# Specify trim mode for the ERB compiler. Defaults to '-'.
|
||||
# See ERB documentation for suitable values.
|
||||
|
@ -199,7 +208,7 @@ module ActionView #:nodoc:
|
|||
end
|
||||
|
||||
def self.load_helpers(helper_dir)#:nodoc:
|
||||
Dir.foreach(helper_dir) do |helper_file|
|
||||
Dir.entries(helper_dir).sort.each do |helper_file|
|
||||
next unless helper_file =~ /^([a-z][a-z_]*_helper).rb$/
|
||||
require File.join(helper_dir, $1)
|
||||
helper_module_name = $1.camelize
|
||||
|
@ -301,6 +310,9 @@ module ActionView #:nodoc:
|
|||
# will only be read if it has to be compiled.
|
||||
#
|
||||
def compile_and_render_template(extension, template = nil, file_path = nil, local_assigns = {}) #:nodoc:
|
||||
# convert string keys to symbols if requested
|
||||
local_assigns = local_assigns.symbolize_keys if @@local_assigns_support_string_keys
|
||||
|
||||
# compile the given template, if necessary
|
||||
if compile_template?(template, file_path, local_assigns)
|
||||
template ||= read_template_file(file_path, extension)
|
||||
|
@ -309,9 +321,7 @@ module ActionView #:nodoc:
|
|||
|
||||
# Get the method name for this template and run it
|
||||
method_name = @@method_names[file_path || template]
|
||||
evaluate_assigns
|
||||
|
||||
local_assigns = local_assigns.symbolize_keys if @@local_assigns_support_string_keys
|
||||
evaluate_assigns
|
||||
|
||||
send(method_name, local_assigns) do |*name|
|
||||
instance_variable_get "@content_for_#{name.first || 'layout'}"
|
||||
|
@ -337,14 +347,14 @@ module ActionView #:nodoc:
|
|||
def builder_template_exists?(template_path)#:nodoc:
|
||||
template_exists?(template_path, :rxml)
|
||||
end
|
||||
|
||||
|
||||
def javascript_template_exists?(template_path)#:nodoc:
|
||||
template_exists?(template_path, :rjs)
|
||||
end
|
||||
|
||||
def file_exists?(template_path)#:nodoc:
|
||||
template_file_name, template_file_extension = path_and_extension(template_path)
|
||||
|
||||
|
||||
if template_file_extension
|
||||
template_exists?(template_file_name, template_file_extension)
|
||||
else
|
||||
|
@ -374,11 +384,11 @@ module ActionView #:nodoc:
|
|||
template_path_without_extension = template_path.sub(/\.(\w+)$/, '')
|
||||
[ template_path_without_extension, $1 ]
|
||||
end
|
||||
|
||||
|
||||
def cached_template_extension(template_path)
|
||||
@@cache_template_extensions && @@cached_template_extension[template_path]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def find_template_extension_for(template_path)
|
||||
if match = delegate_template_exists?(template_path)
|
||||
match.first.to_sym
|
||||
|
@ -386,7 +396,7 @@ module ActionView #:nodoc:
|
|||
elsif builder_template_exists?(template_path): :rxml
|
||||
elsif javascript_template_exists?(template_path): :rjs
|
||||
else
|
||||
raise ActionViewError, "No rhtml, rxml, rjs or delegate template found for #{template_path}"
|
||||
raise ActionViewError, "No rhtml, rxml, rjs or delegate template found for #{template_path} in #{@base_path}"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -416,7 +426,7 @@ module ActionView #:nodoc:
|
|||
local_assigns.empty? ||
|
||||
((args = @@template_args[render_symbol]) && local_assigns.all? { |k,_| args.has_key?(k) })
|
||||
end
|
||||
|
||||
|
||||
# Check whether compilation is necessary.
|
||||
# Compile if the inline template or file has not been compiled yet.
|
||||
# Or if local_assigns has a new key, which isn't supported by the compiled code yet.
|
||||
|
@ -427,8 +437,8 @@ module ActionView #:nodoc:
|
|||
|
||||
if @@compile_time[render_symbol] && supports_local_assigns?(render_symbol, local_assigns)
|
||||
if file_name && !@@cache_template_loading
|
||||
@@compile_time[render_symbol] < File.mtime(file_name) || (File.symlink?(file_name) ?
|
||||
@@compile_time[render_symbol] < File.lstat(file_name).mtime : false)
|
||||
@@compile_time[render_symbol] < File.mtime(file_name) ||
|
||||
(File.symlink?(file_name) && (@@compile_time[render_symbol] < File.lstat(file_name).mtime))
|
||||
end
|
||||
else
|
||||
true
|
||||
|
@ -440,11 +450,11 @@ module ActionView #:nodoc:
|
|||
if template_requires_setup?(extension)
|
||||
body = case extension.to_sym
|
||||
when :rxml
|
||||
"controller.response.content_type ||= 'application/xml'\n" +
|
||||
"xml = Builder::XmlMarkup.new(:indent => 2)\n" +
|
||||
"@controller.headers['Content-Type'] ||= 'application/xml'\n" +
|
||||
template
|
||||
when :rjs
|
||||
"@controller.headers['Content-Type'] ||= 'text/javascript'\n" +
|
||||
"controller.response.content_type ||= 'text/javascript'\n" +
|
||||
"update_page do |page|\n#{template}\nend"
|
||||
end
|
||||
else
|
||||
|
@ -457,7 +467,7 @@ module ActionView #:nodoc:
|
|||
|
||||
locals_code = ""
|
||||
locals_keys.each do |key|
|
||||
locals_code << "#{key} = local_assigns[:#{key}] if local_assigns.has_key?(:#{key})\n"
|
||||
locals_code << "#{key} = local_assigns[:#{key}]\n"
|
||||
end
|
||||
|
||||
"def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend"
|
||||
|
@ -472,34 +482,27 @@ module ActionView #:nodoc:
|
|||
end
|
||||
|
||||
def assign_method_name(extension, template, file_name)
|
||||
method_name = '_run_'
|
||||
method_name << "#{extension}_" if extension
|
||||
method_key = file_name || template
|
||||
@@method_names[method_key] ||= compiled_method_name(extension, template, file_name)
|
||||
end
|
||||
|
||||
def compiled_method_name(extension, template, file_name)
|
||||
['_run', extension, compiled_method_name_file_path_segment(file_name)].compact.join('_').to_sym
|
||||
end
|
||||
|
||||
def compiled_method_name_file_path_segment(file_name)
|
||||
if file_name
|
||||
file_path = File.expand_path(file_name)
|
||||
base_path = File.expand_path(@base_path)
|
||||
|
||||
i = file_path.index(base_path)
|
||||
l = base_path.length
|
||||
|
||||
method_name_file_part = i ? file_path[i+l+1,file_path.length-l-1] : file_path.clone
|
||||
method_name_file_part.sub!(/\.r(html|xml|js)$/,'')
|
||||
method_name_file_part.tr!('/:-', '_')
|
||||
method_name_file_part.gsub!(/[^a-zA-Z0-9_]/){|s| s[0].to_s}
|
||||
|
||||
method_name += method_name_file_part
|
||||
s = File.expand_path(file_name)
|
||||
s.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT)
|
||||
s.gsub!(/([^a-zA-Z0-9_])/) { $1[0].to_s }
|
||||
s
|
||||
else
|
||||
@@inline_template_count += 1
|
||||
method_name << @@inline_template_count.to_s
|
||||
(@@inline_template_count += 1).to_s
|
||||
end
|
||||
|
||||
@@method_names[file_name || template] = method_name.intern
|
||||
end
|
||||
|
||||
def compile_template(extension, template, file_name, local_assigns)
|
||||
method_key = file_name || template
|
||||
|
||||
render_symbol = @@method_names[method_key] || assign_method_name(extension, template, file_name)
|
||||
render_symbol = assign_method_name(extension, template, file_name)
|
||||
render_source = create_template_source(extension, template, render_symbol, local_assigns.keys)
|
||||
|
||||
line_offset = @@template_args[render_symbol].size
|
||||
|
@ -516,18 +519,18 @@ module ActionView #:nodoc:
|
|||
else
|
||||
CompiledTemplates.module_eval(render_source, 'compiled-template', -line_offset)
|
||||
end
|
||||
rescue Object => e
|
||||
rescue Exception => e # errors from template code
|
||||
if logger
|
||||
logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
|
||||
logger.debug "Function body: #{render_source}"
|
||||
logger.debug "Backtrace: #{e.backtrace.join("\n")}"
|
||||
end
|
||||
|
||||
raise TemplateError.new(@base_path, method_key, @assigns, template, e)
|
||||
raise TemplateError.new(@base_path, file_name || template, @assigns, template, e)
|
||||
end
|
||||
|
||||
@@compile_time[render_symbol] = Time.now
|
||||
# logger.debug "Compiled template #{method_key}\n ==> #{render_symbol}" if logger
|
||||
# logger.debug "Compiled template #{file_name || template}\n ==> #{render_symbol}" if logger
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -55,7 +55,7 @@ module ActionView
|
|||
begin
|
||||
module_eval(method_def, fake_file_name, initial_line_number)
|
||||
@mtimes[full_key(identifier, arg_names)] = Time.now
|
||||
rescue Object => e
|
||||
rescue Exception => e # errors from compiled source
|
||||
e.blame_file! identifier
|
||||
raise
|
||||
end
|
||||
|
|
|
@ -85,36 +85,62 @@ module ActionView
|
|||
# <%= error_message_on "post", "title", "Title simply ", " (or it won't work)", "inputError" %> =>
|
||||
# <div class="inputError">Title simply can't be empty (or it won't work)</div>
|
||||
def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError")
|
||||
if errors = instance_variable_get("@#{object}").errors.on(method)
|
||||
if (obj = instance_variable_get("@#{object}")) && (errors = obj.errors.on(method))
|
||||
content_tag("div", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", :class => css_class)
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a string with a div containing all the error messages for the object located as an instance variable by the name
|
||||
# of <tt>object_name</tt>. This div can be tailored by the following options:
|
||||
# Returns a string with a div containing all of the error messages for the objects located as instance variables by the names
|
||||
# given. If more than one object is specified, the errors for the objects are displayed in the order that the object names are
|
||||
# provided.
|
||||
#
|
||||
# This div can be tailored by the following options:
|
||||
#
|
||||
# * <tt>header_tag</tt> - Used for the header of the error div (default: h2)
|
||||
# * <tt>id</tt> - The id of the error div (default: errorExplanation)
|
||||
# * <tt>class</tt> - The class of the error div (default: errorExplanation)
|
||||
# * <tt>object_name</tt> - The object name to use in the header, or
|
||||
# any text that you prefer. If <tt>object_name</tt> is not set, the name of
|
||||
# the first object will be used.
|
||||
#
|
||||
# Specifying one object:
|
||||
#
|
||||
# error_messages_for 'user'
|
||||
#
|
||||
# Specifying more than one object (and using the name 'user' in the
|
||||
# header as the <tt>object_name</tt> instead of 'user_common'):
|
||||
#
|
||||
# error_messages_for 'user_common', 'user', :object_name => 'user'
|
||||
#
|
||||
# NOTE: This is a pre-packaged presentation of the errors with embedded strings and a certain HTML structure. If what
|
||||
# you need is significantly different from the default presentation, it makes plenty of sense to access the object.errors
|
||||
# instance yourself and set it up. View the source of this method to see how easy it is.
|
||||
def error_messages_for(object_name, options = {})
|
||||
options = options.symbolize_keys
|
||||
object = instance_variable_get("@#{object_name}")
|
||||
if object && !object.errors.empty?
|
||||
content_tag("div",
|
||||
content_tag(
|
||||
options[:header_tag] || "h2",
|
||||
"#{pluralize(object.errors.count, "error")} prohibited this #{object_name.to_s.gsub("_", " ")} from being saved"
|
||||
) +
|
||||
content_tag("p", "There were problems with the following fields:") +
|
||||
content_tag("ul", object.errors.full_messages.collect { |msg| content_tag("li", msg) }),
|
||||
"id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
|
||||
def error_messages_for(*params)
|
||||
options = params.last.is_a?(Hash) ? params.pop.symbolize_keys : {}
|
||||
objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
|
||||
count = objects.inject(0) {|sum, object| sum + object.errors.count }
|
||||
unless count.zero?
|
||||
html = {}
|
||||
[:id, :class].each do |key|
|
||||
if options.include?(key)
|
||||
value = options[key]
|
||||
html[key] = value unless value.blank?
|
||||
else
|
||||
html[key] = 'errorExplanation'
|
||||
end
|
||||
end
|
||||
header_message = "#{pluralize(count, 'error')} prohibited this #{(options[:object_name] || params.first).to_s.gsub('_', ' ')} from being saved"
|
||||
error_messages = objects.map {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }
|
||||
content_tag(:div,
|
||||
content_tag(options[:header_tag] || :h2, header_message) <<
|
||||
content_tag(:p, 'There were problems with the following fields:') <<
|
||||
content_tag(:ul, error_messages),
|
||||
html
|
||||
)
|
||||
else
|
||||
""
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -137,12 +163,14 @@ module ActionView
|
|||
to_input_field_tag(field_type, options)
|
||||
when :text
|
||||
to_text_area_tag(options)
|
||||
when :integer, :float
|
||||
when :integer, :float, :decimal
|
||||
to_input_field_tag("text", options)
|
||||
when :date
|
||||
to_date_select_tag(options)
|
||||
when :datetime, :timestamp
|
||||
to_datetime_select_tag(options)
|
||||
when :time
|
||||
to_time_select_tag(options)
|
||||
when :boolean
|
||||
to_boolean_select_tag(options)
|
||||
end
|
||||
|
@ -184,6 +212,15 @@ module ActionView
|
|||
end
|
||||
end
|
||||
|
||||
alias_method :to_time_select_tag_without_error_wrapping, :to_time_select_tag
|
||||
def to_time_select_tag(options = {})
|
||||
if object.respond_to?("errors") && object.errors.respond_to?("on")
|
||||
error_wrapping(to_time_select_tag_without_error_wrapping(options), object.errors.on(@method_name))
|
||||
else
|
||||
to_time_select_tag_without_error_wrapping(options)
|
||||
end
|
||||
end
|
||||
|
||||
def error_wrapping(html_tag, has_error)
|
||||
has_error ? Base.field_error_proc.call(html_tag, self) : html_tag
|
||||
end
|
||||
|
|
|
@ -3,20 +3,36 @@ require File.dirname(__FILE__) + '/url_helper'
|
|||
require File.dirname(__FILE__) + '/tag_helper'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
# Provides methods for linking a HTML page together with other assets, such as javascripts, stylesheets, and feeds.
|
||||
module Helpers #:nodoc:
|
||||
# Provides methods for linking an HTML page together with other assets such
|
||||
# as images, javascripts, stylesheets, and feeds. You can direct Rails to
|
||||
# link to assets from a dedicated assets server by setting ActionController::Base.asset_host
|
||||
# in your environment.rb. These methods do not verify the assets exist before
|
||||
# linking to them.
|
||||
#
|
||||
# ActionController::Base.asset_host = "http://assets.example.com"
|
||||
# image_tag("rails.png")
|
||||
# => <img src="http://assets.example.com/images/rails.png" alt="Rails" />
|
||||
# stylesheet_include_tag("application")
|
||||
# => <link href="http://assets.example.com/stylesheets/application.css" media="screen" rel="Stylesheet" type="text/css" />
|
||||
module AssetTagHelper
|
||||
# Returns a link tag that browsers and news readers can use to auto-detect a RSS or ATOM feed for this page. The +type+ can
|
||||
# either be <tt>:rss</tt> (default) or <tt>:atom</tt> and the +options+ follow the url_for style of declaring a link target.
|
||||
# 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
|
||||
# <tt>:atom</tt>. Control the link options in url_for format using the
|
||||
# +url_options+. You can modify the LINK tag itself in +tag_options+.
|
||||
#
|
||||
# Examples:
|
||||
# auto_discovery_link_tag # =>
|
||||
# Tag Options:
|
||||
# * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate"
|
||||
# * <tt>:type</tt> - Override the auto-generated mime type
|
||||
# * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
|
||||
#
|
||||
# auto_discovery_link_tag # =>
|
||||
# <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.curenthost.com/controller/action" />
|
||||
# auto_discovery_link_tag(:atom) # =>
|
||||
# auto_discovery_link_tag(:atom) # =>
|
||||
# <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.curenthost.com/controller/action" />
|
||||
# auto_discovery_link_tag(:rss, {:action => "feed"}) # =>
|
||||
# auto_discovery_link_tag(:rss, {:action => "feed"}) # =>
|
||||
# <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.curenthost.com/controller/feed" />
|
||||
# auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # =>
|
||||
# auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # =>
|
||||
# <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.curenthost.com/controller/feed" />
|
||||
def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
|
||||
tag(
|
||||
|
@ -28,9 +44,14 @@ module ActionView
|
|||
)
|
||||
end
|
||||
|
||||
# Returns path to a javascript asset. Example:
|
||||
# Computes the path to a javascript asset in the public javascripts directory.
|
||||
# If the +source+ filename has no extension, .js will be appended.
|
||||
# Full paths from the document root will be passed through.
|
||||
# Used internally by javascript_include_tag to build the script path.
|
||||
#
|
||||
# javascript_path "xmlhr" # => /javascripts/xmlhr.js
|
||||
# javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
|
||||
# javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
|
||||
def javascript_path(source)
|
||||
compute_public_path(source, 'javascripts', 'js')
|
||||
end
|
||||
|
@ -38,7 +59,15 @@ module ActionView
|
|||
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
|
||||
@@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
|
||||
|
||||
# Returns a script include tag per source given as argument. Examples:
|
||||
# Returns an html script tag for each of the +sources+ provided. You
|
||||
# can pass in the filename (.js extension is optional) of javascript files
|
||||
# that exist in your public/javascripts directory for inclusion into the
|
||||
# current page or you can pass the full path relative to your document
|
||||
# root. To include the Prototype and Scriptaculous javascript libraries in
|
||||
# your application, pass <tt>:defaults</tt> as the source. When using
|
||||
# :defaults, if an <tt>application.js</tt> file exists in your public
|
||||
# javascripts directory, it will be included as well. You can modify the
|
||||
# html attributes of the script tag by passing a hash as the last argument.
|
||||
#
|
||||
# javascript_include_tag "xmlhr" # =>
|
||||
# <script type="text/javascript" src="/javascripts/xmlhr.js"></script>
|
||||
|
@ -52,11 +81,6 @@ module ActionView
|
|||
# <script type="text/javascript" src="/javascripts/effects.js"></script>
|
||||
# ...
|
||||
# <script type="text/javascript" src="/javascripts/application.js"></script> *see below
|
||||
#
|
||||
# If there's an <tt>application.js</tt> file in your <tt>public/javascripts</tt> directory,
|
||||
# <tt>javascript_include_tag :defaults</tt> will automatically include it. This file
|
||||
# facilitates the inclusion of small snippets of JavaScript code, along the lines of
|
||||
# <tt>controllers/application.rb</tt> and <tt>helpers/application_helper.rb</tt>.
|
||||
def javascript_include_tag(*sources)
|
||||
options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
|
||||
|
||||
|
@ -69,18 +93,16 @@ module ActionView
|
|||
sources << "application" if defined?(RAILS_ROOT) && File.exists?("#{RAILS_ROOT}/public/javascripts/application.js")
|
||||
end
|
||||
|
||||
sources.collect { |source|
|
||||
sources.collect do |source|
|
||||
source = javascript_path(source)
|
||||
content_tag("script", "", { "type" => "text/javascript", "src" => source }.merge(options))
|
||||
}.join("\n")
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
# Register one or more additional JavaScript files to be included when
|
||||
#
|
||||
# javascript_include_tag :defaults
|
||||
#
|
||||
# is called. This method is intended to be called only from plugin initialization
|
||||
# to register extra .js files the plugin installed in <tt>public/javascripts</tt>.
|
||||
# <tt>javascript_include_tag :defaults</tt> is called. This method is
|
||||
# only intended to be called from plugin initialization to register additional
|
||||
# .js files that the plugin installed in <tt>public/javascripts</tt>.
|
||||
def self.register_javascript_include_default(*sources)
|
||||
@@javascript_default_sources.concat(sources)
|
||||
end
|
||||
|
@ -89,14 +111,21 @@ module ActionView
|
|||
@@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
|
||||
end
|
||||
|
||||
# Returns path to a stylesheet asset. Example:
|
||||
# Computes the path to a stylesheet asset in the public stylesheets directory.
|
||||
# If the +source+ filename has no extension, .css will be appended.
|
||||
# Full paths from the document root will be passed through.
|
||||
# Used internally by stylesheet_link_tag to build the stylesheet path.
|
||||
#
|
||||
# stylesheet_path "style" # => /stylesheets/style.css
|
||||
# stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
|
||||
# stylesheet_path "/dir/style.css" # => /dir/style.css
|
||||
def stylesheet_path(source)
|
||||
compute_public_path(source, 'stylesheets', 'css')
|
||||
end
|
||||
|
||||
# Returns a css link tag per source given as argument. Examples:
|
||||
# Returns a stylesheet link tag for the sources specified as arguments. If
|
||||
# you don't specify an extension, .css will be appended automatically.
|
||||
# You can modify the link attributes by passing a hash as the last argument.
|
||||
#
|
||||
# stylesheet_link_tag "style" # =>
|
||||
# <link href="/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />
|
||||
|
@ -109,31 +138,50 @@ module ActionView
|
|||
# <link href="/css/stylish.css" media="screen" rel="Stylesheet" type="text/css" />
|
||||
def stylesheet_link_tag(*sources)
|
||||
options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
|
||||
sources.collect { |source|
|
||||
sources.collect do |source|
|
||||
source = stylesheet_path(source)
|
||||
tag("link", { "rel" => "Stylesheet", "type" => "text/css", "media" => "screen", "href" => source }.merge(options))
|
||||
}.join("\n")
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
# Returns path to an image asset. Example:
|
||||
# Computes the path to an image asset in the public images directory.
|
||||
# Full paths from the document root will be passed through.
|
||||
# Used internally by image_tag to build the image path. Passing
|
||||
# a filename without an extension is deprecated.
|
||||
#
|
||||
# The +src+ can be supplied as a...
|
||||
# * full path, like "/my_images/image.gif"
|
||||
# * file name, like "rss.gif", that gets expanded to "/images/rss.gif"
|
||||
# * file name without extension, like "logo", that gets expanded to "/images/logo.png"
|
||||
# image_path("edit.png") # => /images/edit.png
|
||||
# image_path("icons/edit.png") # => /images/icons/edit.png
|
||||
# image_path("/icons/edit.png") # => /icons/edit.png
|
||||
def image_path(source)
|
||||
unless (source.split("/").last || source).include?(".") || source.blank?
|
||||
ActiveSupport::Deprecation.warn(
|
||||
"You've called image_path with a source that doesn't include an extension. " +
|
||||
"In Rails 2.0, that will not result in .png automatically being appended. " +
|
||||
"So you should call image_path('#{source}.png') instead", caller
|
||||
)
|
||||
end
|
||||
|
||||
compute_public_path(source, 'images', 'png')
|
||||
end
|
||||
|
||||
# Returns an image tag converting the +options+ into html options on the tag, but with these special cases:
|
||||
# Returns an html image tag for the +source+. The +source+ can be a full
|
||||
# path or a file that exists in your public images directory. Note that
|
||||
# specifying a filename without the extension is now deprecated in Rails.
|
||||
# You can add html attributes using the +options+. The +options+ supports
|
||||
# two additional keys for convienence and conformance:
|
||||
#
|
||||
# * <tt>:alt</tt> - If no alt text is given, the file name part of the +src+ is used (capitalized and without the extension)
|
||||
# * <tt>:size</tt> - Supplied as "XxY", so "30x45" becomes width="30" and height="45"
|
||||
# * <tt>:alt</tt> - If no alt text is given, the file name part of the
|
||||
# +source+ is used (capitalized and without the extension)
|
||||
# * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
|
||||
# width="30" and height="45". <tt>:size</tt> will be ignored if the
|
||||
# value is not in the correct format.
|
||||
#
|
||||
# The +src+ can be supplied as a...
|
||||
# * full path, like "/my_images/image.gif"
|
||||
# * file name, like "rss.gif", that gets expanded to "/images/rss.gif"
|
||||
# * file name without extension, like "logo", that gets expanded to "/images/logo.png"
|
||||
# image_tag("icon.png") # =>
|
||||
# <img src="/images/icon.png" alt="Icon" />
|
||||
# image_tag("icon.png", :size => "16x10", :alt => "Edit Entry") # =>
|
||||
# <img src="/images/icon.png" width="16" height="10" alt="Edit Entry" />
|
||||
# image_tag("/icons/icon.gif", :size => "16x16") # =>
|
||||
# <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
|
||||
def image_tag(source, options = {})
|
||||
options.symbolize_keys!
|
||||
|
||||
|
@ -141,8 +189,8 @@ module ActionView
|
|||
options[:alt] ||= File.basename(options[:src], '.*').split('.').first.capitalize
|
||||
|
||||
if options[:size]
|
||||
options[:width], options[:height] = options[:size].split("x")
|
||||
options.delete :size
|
||||
options[:width], options[:height] = options[:size].split("x") if options[:size] =~ %r{^\d+x\d+$}
|
||||
options.delete(:size)
|
||||
end
|
||||
|
||||
tag("img", options)
|
||||
|
@ -150,11 +198,14 @@ module ActionView
|
|||
|
||||
private
|
||||
def compute_public_path(source, dir, ext)
|
||||
source = "/#{dir}/#{source}" unless source.first == "/" || source.include?(":")
|
||||
source << ".#{ext}" unless source.split("/").last.include?(".")
|
||||
source << '?' + rails_asset_id(source) if defined?(RAILS_ROOT) && %r{^[-a-z]+://} !~ source
|
||||
source = "#{@controller.request.relative_url_root}#{source}" unless %r{^[-a-z]+://} =~ source
|
||||
source = ActionController::Base.asset_host + source unless source.include?(":")
|
||||
source = source.dup
|
||||
source << ".#{ext}" if File.extname(source).blank?
|
||||
unless source =~ %r{^[-a-z]+://}
|
||||
source = "/#{dir}/#{source}" unless source[0] == ?/
|
||||
asset_id = rails_asset_id(source)
|
||||
source << '?' + asset_id if defined?(RAILS_ROOT) && !asset_id.blank?
|
||||
source = "#{ActionController::Base.asset_host}#{@controller.request.relative_url_root}#{source}"
|
||||
end
|
||||
source
|
||||
end
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ module ActionView
|
|||
# named @@content_for_#{name_of_the_content_block}@. So <tt><%= content_for('footer') %></tt>
|
||||
# would be avaiable as <tt><%= @content_for_footer %></tt>. The preferred notation now is
|
||||
# <tt><%= yield :footer %></tt>.
|
||||
def content_for(name, &block)
|
||||
def content_for(name, content = nil, &block)
|
||||
eval "@content_for_#{name} = (@content_for_#{name} || '') + capture(&block)"
|
||||
end
|
||||
|
||||
|
|
|
@ -13,14 +13,38 @@ module ActionView
|
|||
module DateHelper
|
||||
DEFAULT_PREFIX = 'date' unless const_defined?('DEFAULT_PREFIX')
|
||||
|
||||
# Reports the approximate distance in time between two Time objects or integers.
|
||||
# For example, if the distance is 47 minutes, it'll return
|
||||
# "about 1 hour". See the source for the complete wording list.
|
||||
# Reports the approximate distance in time between two Time or Date objects or integers as seconds.
|
||||
# Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs
|
||||
# Distances are reported base on the following table:
|
||||
#
|
||||
# Integers are interpreted as seconds. So,
|
||||
# <tt>distance_of_time_in_words(50)</tt> returns "less than a minute".
|
||||
# 0 <-> 29 secs # => less than a minute
|
||||
# 30 secs <-> 1 min, 29 secs # => 1 minute
|
||||
# 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
|
||||
# 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour
|
||||
# 89 mins, 29 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
|
||||
# 23 hrs, 59 mins, 29 secs <-> 47 hrs, 59 mins, 29 secs # => 1 day
|
||||
# 47 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
|
||||
# 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month
|
||||
# 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 31 secs # => [2..12] months
|
||||
# 1 yr minus 30 secs <-> 2 yrs minus 31 secs # => about 1 year
|
||||
# 2 yrs minus 30 secs <-> max time or date # => over [2..X] years
|
||||
#
|
||||
# Set <tt>include_seconds</tt> to true if you want more detailed approximations if distance < 1 minute
|
||||
# With include_seconds = true and the difference < 1 minute 29 seconds
|
||||
# 0-4 secs # => less than 5 seconds
|
||||
# 5-9 secs # => less than 10 seconds
|
||||
# 10-19 secs # => less than 20 seconds
|
||||
# 20-39 secs # => half a minute
|
||||
# 40-59 secs # => less than a minute
|
||||
# 60-89 secs # => 1 minute
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# from_time = Time.now
|
||||
# distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
|
||||
# distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
|
||||
# distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds
|
||||
#
|
||||
# Note: Rails calculates one year as 365.25 days.
|
||||
def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
|
||||
from_time = from_time.to_time if from_time.respond_to?(:to_time)
|
||||
to_time = to_time.to_time if to_time.respond_to?(:to_time)
|
||||
|
@ -29,29 +53,33 @@ module ActionView
|
|||
|
||||
case distance_in_minutes
|
||||
when 0..1
|
||||
return (distance_in_minutes==0) ? 'less than a minute' : '1 minute' unless include_seconds
|
||||
return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
|
||||
case distance_in_seconds
|
||||
when 0..5 then 'less than 5 seconds'
|
||||
when 6..10 then 'less than 10 seconds'
|
||||
when 11..20 then 'less than 20 seconds'
|
||||
when 21..40 then 'half a minute'
|
||||
when 41..59 then 'less than a minute'
|
||||
when 0..4 then 'less than 5 seconds'
|
||||
when 5..9 then 'less than 10 seconds'
|
||||
when 10..19 then 'less than 20 seconds'
|
||||
when 20..39 then 'half a minute'
|
||||
when 40..59 then 'less than a minute'
|
||||
else '1 minute'
|
||||
end
|
||||
|
||||
when 2..45 then "#{distance_in_minutes} minutes"
|
||||
when 46..90 then 'about 1 hour'
|
||||
when 90..1440 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
|
||||
when 1441..2880 then '1 day'
|
||||
else "#{(distance_in_minutes / 1440).round} days"
|
||||
|
||||
when 2..44 then "#{distance_in_minutes} minutes"
|
||||
when 45..89 then 'about 1 hour'
|
||||
when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
|
||||
when 1440..2879 then '1 day'
|
||||
when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
|
||||
when 43200..86399 then 'about 1 month'
|
||||
when 86400..525959 then "#{(distance_in_minutes / 43200).round} months"
|
||||
when 525960..1051919 then 'about 1 year'
|
||||
else "over #{(distance_in_minutes / 525960).round} years"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Like distance_of_time_in_words, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
|
||||
def time_ago_in_words(from_time, include_seconds = false)
|
||||
distance_of_time_in_words(from_time, Time.now, include_seconds)
|
||||
end
|
||||
|
||||
|
||||
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
|
||||
|
||||
# Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute (identified by
|
||||
|
@ -80,6 +108,19 @@ module ActionView
|
|||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_date_select_tag(options)
|
||||
end
|
||||
|
||||
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified
|
||||
# time-based attribute (identified by +method+) on an object assigned to the template (identified by +object+).
|
||||
# You can include the seconds with <tt>:include_seconds</tt>.
|
||||
# Examples:
|
||||
#
|
||||
# time_select("post", "sunrise")
|
||||
# time_select("post", "start_time", :include_seconds => true)
|
||||
#
|
||||
# The selects are prepared for multi-parameter assignment to an Active Record object.
|
||||
def time_select(object_name, method, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_time_select_tag(options)
|
||||
end
|
||||
|
||||
# Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based
|
||||
# attribute (identified by +method+) on an object assigned to the template (identified by +object+). Examples:
|
||||
#
|
||||
|
@ -91,36 +132,55 @@ module ActionView
|
|||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_datetime_select_tag(options)
|
||||
end
|
||||
|
||||
# Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
|
||||
def select_date(date = Date.today, options = {})
|
||||
select_year(date, options) + select_month(date, options) + select_day(date, options)
|
||||
end
|
||||
|
||||
# Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
|
||||
def select_datetime(datetime = Time.now, options = {})
|
||||
select_year(datetime, options) + select_month(datetime, options) + select_day(datetime, options) +
|
||||
select_hour(datetime, options) + select_minute(datetime, options)
|
||||
# It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
|
||||
# symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
|
||||
# will be appened onto the <tt>:order</tt> passed in. You can also add <tt>:date_separator</tt> and <tt>:time_separator</tt>
|
||||
# keys to the +options+ to control visual display of the elements.
|
||||
def select_datetime(datetime = Time.now, options = {})
|
||||
separator = options[:datetime_separator] || ''
|
||||
select_date(datetime, options) + separator + select_time(datetime, options)
|
||||
end
|
||||
|
||||
# Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
|
||||
# It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
|
||||
# symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
|
||||
# will be appened onto the <tt>:order</tt> passed in.
|
||||
def select_date(date = Date.today, options = {})
|
||||
options[:order] ||= []
|
||||
[:year, :month, :day].each { |o| options[:order].push(o) unless options[:order].include?(o) }
|
||||
|
||||
select_date = ''
|
||||
options[:order].each do |o|
|
||||
select_date << self.send("select_#{o}", date, options)
|
||||
end
|
||||
select_date
|
||||
end
|
||||
|
||||
# Returns a set of html select-tags (one for hour and minute)
|
||||
# You can set <tt>:add_separator</tt> key to format the output.
|
||||
def select_time(datetime = Time.now, options = {})
|
||||
h = select_hour(datetime, options) + select_minute(datetime, options) + (options[:include_seconds] ? select_second(datetime, options) : '')
|
||||
separator = options[:time_separator] || ''
|
||||
select_hour(datetime, options) + separator + select_minute(datetime, options) + (options[:include_seconds] ? separator + select_second(datetime, options) : '')
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
|
||||
# The <tt>second</tt> can also be substituted for a second number.
|
||||
# Override the field name using the <tt>:field_name</tt> option, 'second' by default.
|
||||
def select_second(datetime, options = {})
|
||||
second_options = []
|
||||
|
||||
0.upto(59) do |second|
|
||||
second_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) == second) ?
|
||||
%(<option value="#{leading_zero_on_single_digits(second)}" selected="selected">#{leading_zero_on_single_digits(second)}</option>\n) :
|
||||
%(<option value="#{leading_zero_on_single_digits(second)}">#{leading_zero_on_single_digits(second)}</option>\n)
|
||||
)
|
||||
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) : ''
|
||||
if options[:use_hidden]
|
||||
options[:include_seconds] ? hidden_html(options[:field_name] || 'second', val, options) : ''
|
||||
else
|
||||
second_options = []
|
||||
0.upto(59) do |second|
|
||||
second_options << ((val == second) ?
|
||||
%(<option value="#{leading_zero_on_single_digits(second)}" selected="selected">#{leading_zero_on_single_digits(second)}</option>\n) :
|
||||
%(<option value="#{leading_zero_on_single_digits(second)}">#{leading_zero_on_single_digits(second)}</option>\n)
|
||||
)
|
||||
end
|
||||
select_html(options[:field_name] || 'second', second_options, options)
|
||||
end
|
||||
|
||||
select_html(options[:field_name] || 'second', second_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
|
||||
|
@ -128,84 +188,100 @@ module ActionView
|
|||
# The <tt>minute</tt> can also be substituted for a minute number.
|
||||
# Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
|
||||
def select_minute(datetime, options = {})
|
||||
minute_options = []
|
||||
|
||||
0.step(59, options[:minute_step] || 1) do |minute|
|
||||
minute_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.min) == minute) ?
|
||||
%(<option value="#{leading_zero_on_single_digits(minute)}" selected="selected">#{leading_zero_on_single_digits(minute)}</option>\n) :
|
||||
%(<option value="#{leading_zero_on_single_digits(minute)}">#{leading_zero_on_single_digits(minute)}</option>\n)
|
||||
)
|
||||
end
|
||||
|
||||
select_html(options[:field_name] || 'minute', minute_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
|
||||
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.min) : ''
|
||||
if options[:use_hidden]
|
||||
hidden_html(options[:field_name] || 'minute', val, options)
|
||||
else
|
||||
minute_options = []
|
||||
0.step(59, options[:minute_step] || 1) do |minute|
|
||||
minute_options << ((val == minute) ?
|
||||
%(<option value="#{leading_zero_on_single_digits(minute)}" selected="selected">#{leading_zero_on_single_digits(minute)}</option>\n) :
|
||||
%(<option value="#{leading_zero_on_single_digits(minute)}">#{leading_zero_on_single_digits(minute)}</option>\n)
|
||||
)
|
||||
end
|
||||
select_html(options[:field_name] || 'minute', minute_options, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
|
||||
# The <tt>hour</tt> can also be substituted for a hour number.
|
||||
# Override the field name using the <tt>:field_name</tt> option, 'hour' by default.
|
||||
def select_hour(datetime, options = {})
|
||||
hour_options = []
|
||||
|
||||
0.upto(23) do |hour|
|
||||
hour_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) == hour) ?
|
||||
%(<option value="#{leading_zero_on_single_digits(hour)}" selected="selected">#{leading_zero_on_single_digits(hour)}</option>\n) :
|
||||
%(<option value="#{leading_zero_on_single_digits(hour)}">#{leading_zero_on_single_digits(hour)}</option>\n)
|
||||
)
|
||||
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) : ''
|
||||
if options[:use_hidden]
|
||||
hidden_html(options[:field_name] || 'hour', val, options)
|
||||
else
|
||||
hour_options = []
|
||||
0.upto(23) do |hour|
|
||||
hour_options << ((val == hour) ?
|
||||
%(<option value="#{leading_zero_on_single_digits(hour)}" selected="selected">#{leading_zero_on_single_digits(hour)}</option>\n) :
|
||||
%(<option value="#{leading_zero_on_single_digits(hour)}">#{leading_zero_on_single_digits(hour)}</option>\n)
|
||||
)
|
||||
end
|
||||
select_html(options[:field_name] || 'hour', hour_options, options)
|
||||
end
|
||||
|
||||
select_html(options[:field_name] || 'hour', hour_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the days 1 through 31 with the current day selected.
|
||||
# The <tt>date</tt> can also be substituted for a hour number.
|
||||
# Override the field name using the <tt>:field_name</tt> option, 'day' by default.
|
||||
def select_day(date, options = {})
|
||||
day_options = []
|
||||
|
||||
1.upto(31) do |day|
|
||||
day_options << ((date && (date.kind_of?(Fixnum) ? date : date.day) == day) ?
|
||||
%(<option value="#{day}" selected="selected">#{day}</option>\n) :
|
||||
%(<option value="#{day}">#{day}</option>\n)
|
||||
)
|
||||
val = date ? (date.kind_of?(Fixnum) ? date : date.day) : ''
|
||||
if options[:use_hidden]
|
||||
hidden_html(options[:field_name] || 'day', val, options)
|
||||
else
|
||||
day_options = []
|
||||
1.upto(31) do |day|
|
||||
day_options << ((val == day) ?
|
||||
%(<option value="#{day}" selected="selected">#{day}</option>\n) :
|
||||
%(<option value="#{day}">#{day}</option>\n)
|
||||
)
|
||||
end
|
||||
select_html(options[:field_name] || 'day', day_options, options)
|
||||
end
|
||||
|
||||
select_html(options[:field_name] || 'day', day_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the months January through December with the current month selected.
|
||||
# The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are used as values
|
||||
# (what's submitted to the server). It's also possible to use month numbers for the presentation instead of names --
|
||||
# set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you want both numbers and names,
|
||||
# set the <tt>:add_month_numbers</tt> key in +options+ to true. Examples:
|
||||
# set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer to show month names as abbreviations,
|
||||
# set the <tt>:use_short_month</tt> key in +options+ to true. If you want to use your own month names, set the
|
||||
# <tt>:use_month_names</tt> key in +options+ to an array of 12 month names.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# select_month(Date.today) # Will use keys like "January", "March"
|
||||
# select_month(Date.today, :use_month_numbers => true) # Will use keys like "1", "3"
|
||||
# select_month(Date.today, :add_month_numbers => true) # Will use keys like "1 - January", "3 - March"
|
||||
# select_month(Date.today, :use_short_month => true) # Will use keys like "Jan", "Mar"
|
||||
# select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...)) # Will use keys like "Januar", "Marts"
|
||||
#
|
||||
# Override the field name using the <tt>:field_name</tt> option, 'month' by default.
|
||||
#
|
||||
# If you would prefer to show month names as abbreviations, set the
|
||||
# <tt>:use_short_month</tt> key in +options+ to true.
|
||||
def select_month(date, options = {})
|
||||
month_options = []
|
||||
month_names = options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES
|
||||
val = date ? (date.kind_of?(Fixnum) ? date : date.month) : ''
|
||||
if options[:use_hidden]
|
||||
hidden_html(options[:field_name] || 'month', val, options)
|
||||
else
|
||||
month_options = []
|
||||
month_names = options[:use_month_names] || (options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES)
|
||||
month_names.unshift(nil) if month_names.size < 13
|
||||
1.upto(12) do |month_number|
|
||||
month_name = if options[:use_month_numbers]
|
||||
month_number
|
||||
elsif options[:add_month_numbers]
|
||||
month_number.to_s + ' - ' + month_names[month_number]
|
||||
else
|
||||
month_names[month_number]
|
||||
end
|
||||
|
||||
1.upto(12) do |month_number|
|
||||
month_name = if options[:use_month_numbers]
|
||||
month_number
|
||||
elsif options[:add_month_numbers]
|
||||
month_number.to_s + ' - ' + month_names[month_number]
|
||||
else
|
||||
month_names[month_number]
|
||||
month_options << ((val == month_number) ?
|
||||
%(<option value="#{month_number}" selected="selected">#{month_name}</option>\n) :
|
||||
%(<option value="#{month_number}">#{month_name}</option>\n)
|
||||
)
|
||||
end
|
||||
|
||||
month_options << ((date && (date.kind_of?(Fixnum) ? date : date.month) == month_number) ?
|
||||
%(<option value="#{month_number}" selected="selected">#{month_name}</option>\n) :
|
||||
%(<option value="#{month_number}">#{month_name}</option>\n)
|
||||
)
|
||||
select_html(options[:field_name] || 'month', month_options, options)
|
||||
end
|
||||
|
||||
select_html(options[:field_name] || 'month', month_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius
|
||||
|
@ -215,37 +291,51 @@ module ActionView
|
|||
#
|
||||
# select_year(Date.today, :start_year => 1992, :end_year => 2007) # ascending year values
|
||||
# select_year(Date.today, :start_year => 2005, :end_year => 1900) # descending year values
|
||||
# select_year(2006, :start_year => 2000, :end_year => 2010)
|
||||
#
|
||||
# Override the field name using the <tt>:field_name</tt> option, 'year' by default.
|
||||
def select_year(date, options = {})
|
||||
year_options = []
|
||||
y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year
|
||||
val = date ? (date.kind_of?(Fixnum) ? date : date.year) : ''
|
||||
if options[:use_hidden]
|
||||
hidden_html(options[:field_name] || 'year', val, options)
|
||||
else
|
||||
year_options = []
|
||||
y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year
|
||||
|
||||
start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
|
||||
step_val = start_year < end_year ? 1 : -1
|
||||
|
||||
start_year.step(end_year, step_val) do |year|
|
||||
year_options << ((date && (date.kind_of?(Fixnum) ? date : date.year) == year) ?
|
||||
%(<option value="#{year}" selected="selected">#{year}</option>\n) :
|
||||
%(<option value="#{year}">#{year}</option>\n)
|
||||
)
|
||||
start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
|
||||
step_val = start_year < end_year ? 1 : -1
|
||||
start_year.step(end_year, step_val) do |year|
|
||||
year_options << ((val == year) ?
|
||||
%(<option value="#{year}" selected="selected">#{year}</option>\n) :
|
||||
%(<option value="#{year}">#{year}</option>\n)
|
||||
)
|
||||
end
|
||||
select_html(options[:field_name] || 'year', year_options, options)
|
||||
end
|
||||
|
||||
select_html(options[:field_name] || 'year', year_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
|
||||
end
|
||||
|
||||
private
|
||||
def select_html(type, options, prefix = nil, include_blank = false, discard_type = false, disabled = false)
|
||||
select_html = %(<select name="#{prefix || DEFAULT_PREFIX})
|
||||
select_html << "[#{type}]" unless discard_type
|
||||
select_html << %(")
|
||||
select_html << %( disabled="disabled") if disabled
|
||||
|
||||
def select_html(type, html_options, options)
|
||||
name_and_id_from_options(options, type)
|
||||
select_html = %(<select id="#{options[:id]}" name="#{options[:name]}")
|
||||
select_html << %( disabled="disabled") if options[:disabled]
|
||||
select_html << %(>\n)
|
||||
select_html << %(<option value=""></option>\n) if include_blank
|
||||
select_html << options.to_s
|
||||
select_html << %(<option value=""></option>\n) if options[:include_blank]
|
||||
select_html << html_options.to_s
|
||||
select_html << "</select>\n"
|
||||
end
|
||||
|
||||
def hidden_html(type, value, options)
|
||||
name_and_id_from_options(options, type)
|
||||
hidden_html = %(<input type="hidden" id="#{options[:id]}" name="#{options[:name]}" value="#{value}" />\n)
|
||||
end
|
||||
|
||||
def name_and_id_from_options(options, type)
|
||||
options[:name] = (options[:prefix] || DEFAULT_PREFIX) + (options[:discard_type] ? '' : "[#{type}]")
|
||||
options[:id] = options[:name].gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
|
||||
end
|
||||
|
||||
def leading_zero_on_single_digits(number)
|
||||
number > 9 ? number : "0#{number}"
|
||||
end
|
||||
|
@ -255,43 +345,71 @@ module ActionView
|
|||
include DateHelper
|
||||
|
||||
def to_date_select_tag(options = {})
|
||||
defaults = { :discard_type => true }
|
||||
options = defaults.merge(options)
|
||||
options_with_prefix = Proc.new { |position| options.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
|
||||
date = options[:include_blank] ? (value || 0) : (value || Date.today)
|
||||
date_or_time_select options.merge(:discard_hour => true)
|
||||
end
|
||||
|
||||
date_select = ''
|
||||
options[:order] = [:month, :year, :day] if options[:month_before_year] # For backwards compatibility
|
||||
options[:order] ||= [:year, :month, :day]
|
||||
|
||||
position = {:year => 1, :month => 2, :day => 3}
|
||||
|
||||
discard = {}
|
||||
discard[:year] = true if options[:discard_year]
|
||||
discard[:month] = true if options[:discard_month]
|
||||
discard[:day] = true if options[:discard_day] or options[:discard_month]
|
||||
|
||||
options[:order].each do |param|
|
||||
date_select << self.send("select_#{param}", date, options_with_prefix.call(position[param])) unless discard[param]
|
||||
end
|
||||
|
||||
date_select
|
||||
def to_time_select_tag(options = {})
|
||||
date_or_time_select options.merge(:discard_year => true, :discard_month => true)
|
||||
end
|
||||
|
||||
def to_datetime_select_tag(options = {})
|
||||
defaults = { :discard_type => true }
|
||||
options = defaults.merge(options)
|
||||
options_with_prefix = Proc.new { |position| options.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
|
||||
datetime = options[:include_blank] ? (value || nil) : (value || Time.now)
|
||||
|
||||
datetime_select = select_year(datetime, options_with_prefix.call(1))
|
||||
datetime_select << select_month(datetime, options_with_prefix.call(2)) unless options[:discard_month]
|
||||
datetime_select << select_day(datetime, options_with_prefix.call(3)) unless options[:discard_day] || options[:discard_month]
|
||||
datetime_select << ' — ' + select_hour(datetime, options_with_prefix.call(4)) unless options[:discard_hour]
|
||||
datetime_select << ' : ' + select_minute(datetime, options_with_prefix.call(5)) unless options[:discard_minute] || options[:discard_hour]
|
||||
|
||||
datetime_select
|
||||
date_or_time_select options
|
||||
end
|
||||
|
||||
private
|
||||
def date_or_time_select(options)
|
||||
defaults = { :discard_type => true }
|
||||
options = defaults.merge(options)
|
||||
datetime = value(object)
|
||||
datetime ||= Time.now unless options[:include_blank]
|
||||
|
||||
position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
|
||||
|
||||
order = (options[:order] ||= [:year, :month, :day])
|
||||
|
||||
# Discard explicit and implicit by not being included in the :order
|
||||
discard = {}
|
||||
discard[:year] = true if options[:discard_year] or !order.include?(:year)
|
||||
discard[:month] = true if options[:discard_month] or !order.include?(:month)
|
||||
discard[:day] = true if options[:discard_day] or discard[:month] or !order.include?(:day)
|
||||
discard[:hour] = true if options[:discard_hour]
|
||||
discard[:minute] = true if options[:discard_minute] or discard[:hour]
|
||||
discard[:second] = true unless options[:include_seconds] && !discard[:minute]
|
||||
|
||||
# Maintain valid dates by including hidden fields for discarded elements
|
||||
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
|
||||
# Ensure proper ordering of :hour, :minute and :second
|
||||
[:hour, :minute, :second].each { |o| order.delete(o); order.push(o) }
|
||||
|
||||
date_or_time_select = ''
|
||||
order.reverse.each do |param|
|
||||
# Send hidden fields for discarded elements once output has started
|
||||
# This ensures AR can reconstruct valid dates using ParseDate
|
||||
next if discard[param] && date_or_time_select.empty?
|
||||
|
||||
date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param]))))
|
||||
date_or_time_select.insert(0,
|
||||
case param
|
||||
when :hour then (discard[:year] && discard[:day] ? "" : " — ")
|
||||
when :minute then " : "
|
||||
when :second then options[:include_seconds] ? " : " : ""
|
||||
else ""
|
||||
end)
|
||||
|
||||
end
|
||||
|
||||
date_or_time_select
|
||||
end
|
||||
|
||||
def options_with_prefix(position, options)
|
||||
prefix = "#{@object_name}"
|
||||
if options[:index]
|
||||
prefix << "[#{options[:index]}]"
|
||||
elsif @auto_index
|
||||
prefix << "[#{@auto_index}]"
|
||||
end
|
||||
options.merge(:prefix => "#{prefix}[#{@method_name}(#{position}i)]")
|
||||
end
|
||||
end
|
||||
|
||||
class FormBuilder
|
||||
|
@ -299,6 +417,10 @@ module ActionView
|
|||
@template.date_select(@object_name, method, options.merge(:object => @object))
|
||||
end
|
||||
|
||||
def time_select(method, options = {})
|
||||
@template.time_select(@object_name, method, options.merge(:object => @object))
|
||||
end
|
||||
|
||||
def datetime_select(method, options = {})
|
||||
@template.datetime_select(@object_name, method, options.merge(:object => @object))
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ module ActionView
|
|||
begin
|
||||
Marshal::dump(object)
|
||||
"<pre class='debug_dump'>#{h(object.to_yaml).gsub(" ", " ")}</pre>"
|
||||
rescue Object => e
|
||||
rescue Exception => e # errors from Marshal or YAML
|
||||
# Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
|
||||
"<code class='debug_dump'>#{h(object.inspect)}</code>"
|
||||
end
|
||||
|
|
|
@ -142,11 +142,13 @@ module ActionView
|
|||
#
|
||||
# Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base.
|
||||
# Like collection_select and datetime_select.
|
||||
def fields_for(object_name, *args, &proc)
|
||||
def fields_for(object_name, *args, &block)
|
||||
raise ArgumentError, "Missing block" unless block_given?
|
||||
options = args.last.is_a?(Hash) ? args.pop : {}
|
||||
object = args.first
|
||||
yield((options[:builder] || FormBuilder).new(object_name, object, self, options, proc))
|
||||
|
||||
builder = options[:builder] || ActionView::Base.default_form_builder
|
||||
yield builder.new(object_name, object, self, options, block)
|
||||
end
|
||||
|
||||
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
|
||||
|
@ -238,7 +240,11 @@ module ActionView
|
|||
@template_object, @local_binding = template_object, local_binding
|
||||
@object = object
|
||||
if @object_name.sub!(/\[\]$/,"")
|
||||
@auto_index = @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}").id_before_type_cast
|
||||
if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:id_before_type_cast)
|
||||
@auto_index = object.id_before_type_cast
|
||||
else
|
||||
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to id_before_type_cast: #{object.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -250,7 +256,7 @@ module ActionView
|
|||
options.delete("size")
|
||||
end
|
||||
options["type"] = field_type
|
||||
options["value"] ||= value_before_type_cast unless field_type == "file"
|
||||
options["value"] ||= value_before_type_cast(object) unless field_type == "file"
|
||||
add_default_name_and_id(options)
|
||||
tag("input", options)
|
||||
end
|
||||
|
@ -259,9 +265,15 @@ module ActionView
|
|||
options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
|
||||
options["type"] = "radio"
|
||||
options["value"] = tag_value
|
||||
options["checked"] = "checked" if value.to_s == tag_value.to_s
|
||||
if options.has_key?("checked")
|
||||
cv = options.delete "checked"
|
||||
checked = cv == true || cv == "checked"
|
||||
else
|
||||
checked = self.class.radio_button_checked?(value(object), tag_value)
|
||||
end
|
||||
options["checked"] = "checked" if checked
|
||||
pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
|
||||
options["id"] = @auto_index ?
|
||||
options["id"] ||= defined?(@auto_index) ?
|
||||
"#{@object_name}_#{@auto_index}_#{@method_name}_#{pretty_tag_value}" :
|
||||
"#{@object_name}_#{@method_name}_#{pretty_tag_value}"
|
||||
add_default_name_and_id(options)
|
||||
|
@ -271,14 +283,82 @@ module ActionView
|
|||
def to_text_area_tag(options = {})
|
||||
options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
|
||||
add_default_name_and_id(options)
|
||||
content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast), options)
|
||||
|
||||
if size = options.delete("size")
|
||||
options["cols"], options["rows"] = size.split("x")
|
||||
end
|
||||
|
||||
content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast(object)), options)
|
||||
end
|
||||
|
||||
def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
|
||||
options = options.stringify_keys
|
||||
options["type"] = "checkbox"
|
||||
options["value"] = checked_value
|
||||
checked = case value
|
||||
if options.has_key?("checked")
|
||||
cv = options.delete "checked"
|
||||
checked = cv == true || cv == "checked"
|
||||
else
|
||||
checked = self.class.check_box_checked?(value(object), checked_value)
|
||||
end
|
||||
options["checked"] = "checked" if checked
|
||||
add_default_name_and_id(options)
|
||||
tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value)
|
||||
end
|
||||
|
||||
def to_date_tag()
|
||||
defaults = DEFAULT_DATE_OPTIONS.dup
|
||||
date = value(object) || Date.today
|
||||
options = Proc.new { |position| defaults.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
|
||||
html_day_select(date, options.call(3)) +
|
||||
html_month_select(date, options.call(2)) +
|
||||
html_year_select(date, options.call(1))
|
||||
end
|
||||
|
||||
def to_boolean_select_tag(options = {})
|
||||
options = options.stringify_keys
|
||||
add_default_name_and_id(options)
|
||||
value = value(object)
|
||||
tag_text = "<select"
|
||||
tag_text << tag_options(options)
|
||||
tag_text << "><option value=\"false\""
|
||||
tag_text << " selected" if value == false
|
||||
tag_text << ">False</option><option value=\"true\""
|
||||
tag_text << " selected" if value
|
||||
tag_text << ">True</option></select>"
|
||||
end
|
||||
|
||||
def to_content_tag(tag_name, options = {})
|
||||
content_tag(tag_name, value(object), options)
|
||||
end
|
||||
|
||||
def object
|
||||
@object || @template_object.instance_variable_get("@#{@object_name}")
|
||||
end
|
||||
|
||||
def value(object)
|
||||
self.class.value(object, @method_name)
|
||||
end
|
||||
|
||||
def value_before_type_cast(object)
|
||||
self.class.value_before_type_cast(object, @method_name)
|
||||
end
|
||||
|
||||
class << self
|
||||
def value(object, method_name)
|
||||
object.send method_name unless object.nil?
|
||||
end
|
||||
|
||||
def value_before_type_cast(object, method_name)
|
||||
unless object.nil?
|
||||
object.respond_to?(method_name + "_before_type_cast") ?
|
||||
object.send(method_name + "_before_type_cast") :
|
||||
object.send(method_name)
|
||||
end
|
||||
end
|
||||
|
||||
def check_box_checked?(value, checked_value)
|
||||
case value
|
||||
when TrueClass, FalseClass
|
||||
value
|
||||
when NilClass
|
||||
|
@ -290,55 +370,10 @@ module ActionView
|
|||
else
|
||||
value.to_i != 0
|
||||
end
|
||||
if checked || options["checked"] == "checked"
|
||||
options["checked"] = "checked"
|
||||
else
|
||||
options.delete("checked")
|
||||
end
|
||||
add_default_name_and_id(options)
|
||||
tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value)
|
||||
end
|
||||
|
||||
def to_date_tag()
|
||||
defaults = DEFAULT_DATE_OPTIONS.dup
|
||||
date = value || Date.today
|
||||
options = Proc.new { |position| defaults.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
|
||||
html_day_select(date, options.call(3)) +
|
||||
html_month_select(date, options.call(2)) +
|
||||
html_year_select(date, options.call(1))
|
||||
end
|
||||
|
||||
def to_boolean_select_tag(options = {})
|
||||
options = options.stringify_keys
|
||||
add_default_name_and_id(options)
|
||||
tag_text = "<select"
|
||||
tag_text << tag_options(options)
|
||||
tag_text << "><option value=\"false\""
|
||||
tag_text << " selected" if value == false
|
||||
tag_text << ">False</option><option value=\"true\""
|
||||
tag_text << " selected" if value
|
||||
tag_text << ">True</option></select>"
|
||||
end
|
||||
|
||||
def to_content_tag(tag_name, options = {})
|
||||
content_tag(tag_name, value, options)
|
||||
end
|
||||
|
||||
def object
|
||||
@object || @template_object.instance_variable_get("@#{@object_name}")
|
||||
end
|
||||
|
||||
def value
|
||||
unless object.nil?
|
||||
object.send(@method_name)
|
||||
end
|
||||
end
|
||||
|
||||
def value_before_type_cast
|
||||
unless object.nil?
|
||||
object.respond_to?(@method_name + "_before_type_cast") ?
|
||||
object.send(@method_name + "_before_type_cast") :
|
||||
object.send(@method_name)
|
||||
|
||||
def radio_button_checked?(value, checked_value)
|
||||
value.to_s == checked_value.to_s
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -348,11 +383,11 @@ module ActionView
|
|||
options["name"] ||= tag_name_with_index(options["index"])
|
||||
options["id"] ||= tag_id_with_index(options["index"])
|
||||
options.delete("index")
|
||||
elsif @auto_index
|
||||
elsif defined?(@auto_index)
|
||||
options["name"] ||= tag_name_with_index(@auto_index)
|
||||
options["id"] ||= tag_id_with_index(@auto_index)
|
||||
else
|
||||
options["name"] ||= tag_name
|
||||
options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
|
||||
options["id"] ||= tag_id
|
||||
end
|
||||
end
|
||||
|
@ -379,7 +414,7 @@ module ActionView
|
|||
class_inheritable_accessor :field_helpers
|
||||
self.field_helpers = (FormHelper.instance_methods - ['form_for'])
|
||||
|
||||
attr_accessor :object_name, :object
|
||||
attr_accessor :object_name, :object, :options
|
||||
|
||||
def initialize(object_name, object, template, options, proc)
|
||||
@object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
|
||||
|
@ -403,4 +438,9 @@ module ActionView
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Base
|
||||
cattr_accessor :default_form_builder
|
||||
self.default_form_builder = ::ActionView::Helpers::FormBuilder
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,7 +26,7 @@ module ActionView
|
|||
#
|
||||
# Another common case is a select tag for an <tt>belongs_to</tt>-associated object. For example,
|
||||
#
|
||||
# select("post", "person_id", Person.find_all.collect {|p| [ p.name, p.id ] })
|
||||
# select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] })
|
||||
#
|
||||
# could become:
|
||||
#
|
||||
|
@ -43,7 +43,7 @@ module ActionView
|
|||
# See options_for_select for the required format of the choices parameter.
|
||||
#
|
||||
# Example with @post.person_id => 1:
|
||||
# select("post", "person_id", Person.find_all.collect {|p| [ p.name, p.id ] }, { :include_blank => true })
|
||||
# select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, { :include_blank => true })
|
||||
#
|
||||
# could become:
|
||||
#
|
||||
|
@ -113,7 +113,6 @@ module ActionView
|
|||
|
||||
options_for_select = container.inject([]) do |options, element|
|
||||
if !element.is_a?(String) and element.respond_to?(:first) and element.respond_to?(:last)
|
||||
is_selected = ( (selected.respond_to?(:include?) ? selected.include?(element.last) : element.last == selected) )
|
||||
is_selected = ( (selected.respond_to?(:include?) && !selected.is_a?(String) ? selected.include?(element.last) : element.last == selected) )
|
||||
if is_selected
|
||||
options << "<option value=\"#{html_escape(element.last.to_s)}\" selected=\"selected\">#{html_escape(element.first.to_s)}</option>"
|
||||
|
@ -121,7 +120,6 @@ module ActionView
|
|||
options << "<option value=\"#{html_escape(element.last.to_s)}\">#{html_escape(element.first.to_s)}</option>"
|
||||
end
|
||||
else
|
||||
is_selected = ( (selected.respond_to?(:include?) ? selected.include?(element) : element == selected) )
|
||||
is_selected = ( (selected.respond_to?(:include?) && !selected.is_a?(String) ? selected.include?(element) : element == selected) )
|
||||
options << ((is_selected) ? "<option value=\"#{html_escape(element.to_s)}\" selected=\"selected\">#{html_escape(element.to_s)}</option>" : "<option value=\"#{html_escape(element.to_s)}\">#{html_escape(element.to_s)}</option>")
|
||||
end
|
||||
|
@ -299,13 +297,15 @@ module ActionView
|
|||
def to_select_tag(choices, options, html_options)
|
||||
html_options = html_options.stringify_keys
|
||||
add_default_name_and_id(html_options)
|
||||
value = value(object)
|
||||
selected_value = options.has_key?(:selected) ? options[:selected] : value
|
||||
content_tag("select", add_options(options_for_select(choices, selected_value), options, value), html_options)
|
||||
content_tag("select", add_options(options_for_select(choices, selected_value), options, selected_value), html_options)
|
||||
end
|
||||
|
||||
def to_collection_select_tag(collection, value_method, text_method, options, html_options)
|
||||
html_options = html_options.stringify_keys
|
||||
add_default_name_and_id(html_options)
|
||||
value = value(object)
|
||||
content_tag(
|
||||
"select", add_options(options_from_collection_for_select(collection, value_method, text_method, value), options, value), html_options
|
||||
)
|
||||
|
@ -314,12 +314,14 @@ module ActionView
|
|||
def to_country_select_tag(priority_countries, options, html_options)
|
||||
html_options = html_options.stringify_keys
|
||||
add_default_name_and_id(html_options)
|
||||
value = value(object)
|
||||
content_tag("select", add_options(country_options_for_select(value, priority_countries), options, value), html_options)
|
||||
end
|
||||
|
||||
def to_time_zone_select_tag(priority_zones, options, html_options)
|
||||
html_options = html_options.stringify_keys
|
||||
add_default_name_and_id(html_options)
|
||||
value = value(object)
|
||||
content_tag("select",
|
||||
add_options(
|
||||
time_zone_options_for_select(value, priority_zones, options[:model] || TimeZone),
|
||||
|
|
|
@ -12,14 +12,49 @@ module ActionView
|
|||
# Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like
|
||||
# ActionController::Base#url_for. The method for the form defaults to POST.
|
||||
#
|
||||
# Examples:
|
||||
# * <tt>form_tag('/posts') => <form action="/posts" method="post"></tt>
|
||||
# * <tt>form_tag('/posts/1', :method => :put) => <form action="/posts/1" method="put"></tt>
|
||||
# * <tt>form_tag('/upload', :multipart => true) => <form action="/upload" method="post" enctype="multipart/form-data"></tt>
|
||||
#
|
||||
# ERb example:
|
||||
# <% form_tag '/posts' do -%>
|
||||
# <div><%= submit_tag 'Save' %></div>
|
||||
# <% end -%>
|
||||
#
|
||||
# Will output:
|
||||
# <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:multipart</tt> - If set to true, the enctype is set to "multipart/form-data".
|
||||
# * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post".
|
||||
def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &proc)
|
||||
html_options = { "method" => "post" }.merge(options.stringify_keys)
|
||||
# * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post".
|
||||
# If "put", "delete", or another verb is used, a hidden input with name _method
|
||||
# is added to simulate the verb over post.
|
||||
def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
|
||||
html_options = options.stringify_keys
|
||||
html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
|
||||
html_options["action"] = url_for(url_for_options, *parameters_for_url)
|
||||
tag :form, html_options, true
|
||||
html_options["action"] = url_for(url_for_options, *parameters_for_url)
|
||||
|
||||
method_tag = ""
|
||||
|
||||
case method = html_options.delete("method").to_s
|
||||
when /^get$/i # must be case-insentive, but can't use downcase as might be nil
|
||||
html_options["method"] = "get"
|
||||
when /^post$/i, "", nil
|
||||
html_options["method"] = "post"
|
||||
else
|
||||
html_options["method"] = "post"
|
||||
method_tag = content_tag(:div, tag(:input, :type => "hidden", :name => "_method", :value => method), :style => 'margin:0;padding:0')
|
||||
end
|
||||
|
||||
if block_given?
|
||||
content = capture(&block)
|
||||
concat(tag(:form, html_options, true) + method_tag, block.binding)
|
||||
concat(content, block.binding)
|
||||
concat("</form>", block.binding)
|
||||
else
|
||||
tag(:form, html_options, true) + method_tag
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :start_form_tag, :form_tag
|
||||
|
@ -28,6 +63,8 @@ module ActionView
|
|||
def end_form_tag
|
||||
"</form>"
|
||||
end
|
||||
|
||||
deprecate :end_form_tag, :start_form_tag => :form_tag
|
||||
|
||||
# Creates a dropdown selection box, or if the <tt>:multiple</tt> option is set to true, a multiple
|
||||
# choice selection box.
|
||||
|
@ -110,7 +147,8 @@ module ActionView
|
|||
|
||||
# Creates a radio button.
|
||||
def radio_button_tag(name, value, checked = false, options = {})
|
||||
html_options = { "type" => "radio", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys)
|
||||
pretty_tag_value = value.to_s.gsub(/\s/, "_").gsub(/(?!-)\W/, "").downcase
|
||||
html_options = { "type" => "radio", "name" => name, "id" => "#{name}_#{pretty_tag_value}", "value" => value }.update(options.stringify_keys)
|
||||
html_options["checked"] = "checked" if checked
|
||||
tag :input, html_options
|
||||
end
|
||||
|
|
|
@ -7,6 +7,8 @@ module ActionView
|
|||
# editing relies on ActionController::Base.in_place_edit_for and the autocompletion relies on
|
||||
# ActionController::Base.auto_complete_for.
|
||||
module JavaScriptMacrosHelper
|
||||
# DEPRECATION WARNING: This method will become a separate plugin when Rails 2.0 ships.
|
||||
#
|
||||
# Makes an HTML element specified by the DOM ID +field_id+ become an in-place
|
||||
# editor of a property.
|
||||
#
|
||||
|
@ -27,20 +29,21 @@ module ActionView
|
|||
# <tt>:url</tt>:: Specifies the url where the updated value should
|
||||
# be sent after the user presses "ok".
|
||||
#
|
||||
#
|
||||
# Addtional +options+ are:
|
||||
# <tt>:rows</tt>:: Number of rows (more than 1 will use a TEXTAREA)
|
||||
# <tt>:cols</tt>:: Number of characters the text input should span (works for both INPUT and TEXTAREA)
|
||||
# <tt>:size</tt>:: Synonym for :cols when using a single line text input.
|
||||
# <tt>:cancel_text</tt>:: The text on the cancel link. (default: "cancel")
|
||||
# <tt>:save_text</tt>:: The text on the save link. (default: "ok")
|
||||
# <tt>:loading_text</tt>:: The text to display when submitting to the server (default: "Saving...")
|
||||
# <tt>:loading_text</tt>:: The text to display while the data is being loaded from the server (default: "Loading...")
|
||||
# <tt>:saving_text</tt>:: The text to display when submitting to the server (default: "Saving...")
|
||||
# <tt>:external_control</tt>:: The id of an external control used to enter edit mode.
|
||||
# <tt>:load_text_url</tt>:: URL where initial value of editor (content) is retrieved.
|
||||
# <tt>:options</tt>:: Pass through options to the AJAX call (see prototype's Ajax.Updater)
|
||||
# <tt>:with</tt>:: JavaScript snippet that should return what is to be sent
|
||||
# in the AJAX call, +form+ is an implicit parameter
|
||||
# <tt>:script</tt>:: Instructs the in-place editor to evaluate the remote JavaScript response (default: false)
|
||||
# <tt>:click_to_edit_text</tt>::The text shown during mouseover the editable text (default: "Click to edit")
|
||||
def in_place_editor(field_id, options = {})
|
||||
function = "new Ajax.InPlaceEditor("
|
||||
function << "'#{field_id}', "
|
||||
|
@ -50,6 +53,7 @@ module ActionView
|
|||
js_options['cancelText'] = %('#{options[:cancel_text]}') if options[:cancel_text]
|
||||
js_options['okText'] = %('#{options[:save_text]}') if options[:save_text]
|
||||
js_options['loadingText'] = %('#{options[:loading_text]}') if options[:loading_text]
|
||||
js_options['savingText'] = %('#{options[:saving_text]}') if options[:saving_text]
|
||||
js_options['rows'] = options[:rows] if options[:rows]
|
||||
js_options['cols'] = options[:cols] if options[:cols]
|
||||
js_options['size'] = options[:size] if options[:size]
|
||||
|
@ -58,6 +62,7 @@ module ActionView
|
|||
js_options['ajaxOptions'] = options[:options] if options[:options]
|
||||
js_options['evalScripts'] = options[:script] if options[:script]
|
||||
js_options['callback'] = "function(form) { return #{options[:with]} }" if options[:with]
|
||||
js_options['clickToEditText'] = %('#{options[:click_to_edit_text]}') if options[:click_to_edit_text]
|
||||
function << (', ' + options_for_javascript(js_options)) unless js_options.empty?
|
||||
|
||||
function << ')'
|
||||
|
@ -65,6 +70,8 @@ module ActionView
|
|||
javascript_tag(function)
|
||||
end
|
||||
|
||||
# DEPRECATION WARNING: This method will become a separate plugin when Rails 2.0 ships.
|
||||
#
|
||||
# Renders the value of the specified object and method with in-place editing capabilities.
|
||||
#
|
||||
# See the RDoc on ActionController::InPlaceEditing to learn more about this.
|
||||
|
@ -76,14 +83,16 @@ module ActionView
|
|||
in_place_editor(tag_options[:id], in_place_editor_options)
|
||||
end
|
||||
|
||||
# DEPRECATION WARNING: This method will become a separate plugin when Rails 2.0 ships.
|
||||
#
|
||||
# Adds AJAX autocomplete functionality to the text input field with the
|
||||
# DOM ID specified by +field_id+.
|
||||
#
|
||||
# This function expects that the called action returns a HTML <ul> list,
|
||||
# This function expects that the called action returns an HTML <ul> list,
|
||||
# or nothing if no entries should be displayed for autocompletion.
|
||||
#
|
||||
# You'll probably want to turn the browser's built-in autocompletion off,
|
||||
# so be sure to include a autocomplete="off" attribute with your text
|
||||
# so be sure to include an <tt>autocomplete="off"</tt> attribute with your text
|
||||
# input field.
|
||||
#
|
||||
# The autocompleter object is assigned to a Javascript variable named <tt>field_id</tt>_auto_completer.
|
||||
|
@ -91,45 +100,45 @@ module ActionView
|
|||
# other means than user input (for that specific case, call the <tt>activate</tt> method on that object).
|
||||
#
|
||||
# Required +options+ are:
|
||||
# <tt>:url</tt>:: URL to call for autocompletion results
|
||||
# in url_for format.
|
||||
# <tt>:url</tt>:: URL to call for autocompletion results
|
||||
# in url_for format.
|
||||
#
|
||||
# Addtional +options+ are:
|
||||
# <tt>:update</tt>:: Specifies the DOM ID of the element whose
|
||||
# innerHTML should be updated with the autocomplete
|
||||
# entries returned by the AJAX request.
|
||||
# Defaults to field_id + '_auto_complete'
|
||||
# <tt>:with</tt>:: A JavaScript expression specifying the
|
||||
# parameters for the XMLHttpRequest. This defaults
|
||||
# to 'fieldname=value'.
|
||||
# <tt>:frequency</tt>:: Determines the time to wait after the last keystroke
|
||||
# for the AJAX request to be initiated.
|
||||
# <tt>:indicator</tt>:: Specifies the DOM ID of an element which will be
|
||||
# displayed while autocomplete is running.
|
||||
# <tt>:tokens</tt>:: A string or an array of strings containing
|
||||
# separator tokens for tokenized incremental
|
||||
# autocompletion. Example: <tt>:tokens => ','</tt> would
|
||||
# allow multiple autocompletion entries, separated
|
||||
# by commas.
|
||||
# <tt>:min_chars</tt>:: The minimum number of characters that should be
|
||||
# in the input field before an Ajax call is made
|
||||
# to the server.
|
||||
# <tt>:on_hide</tt>:: A Javascript expression that is called when the
|
||||
# autocompletion div is hidden. The expression
|
||||
# should take two variables: element and update.
|
||||
# Element is a DOM element for the field, update
|
||||
# is a DOM element for the div from which the
|
||||
# innerHTML is replaced.
|
||||
# <tt>:on_show</tt>:: Like on_hide, only now the expression is called
|
||||
# then the div is shown.
|
||||
# <tt>:after_update_element</tt>:: A Javascript expression that is called when the
|
||||
# user has selected one of the proposed values.
|
||||
# The expression should take two variables: element and value.
|
||||
# Element is a DOM element for the field, value
|
||||
# is the value selected by the user.
|
||||
# <tt>:select</tt>:: Pick the class of the element from which the value for
|
||||
# insertion should be extracted. If this is not specified,
|
||||
# the entire element is used.
|
||||
# <tt>:update</tt>:: Specifies the DOM ID of the element whose
|
||||
# innerHTML should be updated with the autocomplete
|
||||
# entries returned by the AJAX request.
|
||||
# Defaults to <tt>field_id</tt> + '_auto_complete'
|
||||
# <tt>:with</tt>:: A JavaScript expression specifying the
|
||||
# parameters for the XMLHttpRequest. This defaults
|
||||
# to 'fieldname=value'.
|
||||
# <tt>:frequency</tt>:: Determines the time to wait after the last keystroke
|
||||
# for the AJAX request to be initiated.
|
||||
# <tt>:indicator</tt>:: Specifies the DOM ID of an element which will be
|
||||
# displayed while autocomplete is running.
|
||||
# <tt>:tokens</tt>:: A string or an array of strings containing
|
||||
# separator tokens for tokenized incremental
|
||||
# autocompletion. Example: <tt>:tokens => ','</tt> would
|
||||
# allow multiple autocompletion entries, separated
|
||||
# by commas.
|
||||
# <tt>:min_chars</tt>:: The minimum number of characters that should be
|
||||
# in the input field before an Ajax call is made
|
||||
# to the server.
|
||||
# <tt>:on_hide</tt>:: A Javascript expression that is called when the
|
||||
# autocompletion div is hidden. The expression
|
||||
# should take two variables: element and update.
|
||||
# Element is a DOM element for the field, update
|
||||
# is a DOM element for the div from which the
|
||||
# innerHTML is replaced.
|
||||
# <tt>:on_show</tt>:: Like on_hide, only now the expression is called
|
||||
# then the div is shown.
|
||||
# <tt>:after_update_element</tt>:: A Javascript expression that is called when the
|
||||
# user has selected one of the proposed values.
|
||||
# The expression should take two variables: element and value.
|
||||
# Element is a DOM element for the field, value
|
||||
# is the value selected by the user.
|
||||
# <tt>:select</tt>:: Pick the class of the element from which the value for
|
||||
# insertion should be extracted. If this is not specified,
|
||||
# the entire element is used.
|
||||
def auto_complete_field(field_id, options = {})
|
||||
function = "var #{field_id}_auto_completer = new Ajax.Autocompleter("
|
||||
function << "'#{field_id}', "
|
||||
|
@ -141,6 +150,7 @@ module ActionView
|
|||
js_options[:callback] = "function(element, value) { return #{options[:with]} }" if options[:with]
|
||||
js_options[:indicator] = "'#{options[:indicator]}'" if options[:indicator]
|
||||
js_options[:select] = "'#{options[:select]}'" if options[:select]
|
||||
js_options[:paramName] = "'#{options[:param_name]}'" if options[:param_name]
|
||||
js_options[:frequency] = "#{options[:frequency]}" if options[:frequency]
|
||||
|
||||
{ :after_update_element => :afterUpdateElement,
|
||||
|
@ -153,6 +163,8 @@ module ActionView
|
|||
javascript_tag(function)
|
||||
end
|
||||
|
||||
# DEPRECATION WARNING: This method will become a separate plugin when Rails 2.0 ships.
|
||||
#
|
||||
# Use this method in your view to generate a return for the AJAX autocomplete requests.
|
||||
#
|
||||
# Example action:
|
||||
|
@ -161,7 +173,7 @@ module ActionView
|
|||
# @items = Item.find(:all,
|
||||
# :conditions => [ 'LOWER(description) LIKE ?',
|
||||
# '%' + request.raw_post.downcase + '%' ])
|
||||
# render :inline => '<%= auto_complete_result(@items, 'description') %>'
|
||||
# render :inline => "<%= auto_complete_result(@items, 'description') %>"
|
||||
# end
|
||||
#
|
||||
# The auto_complete_result can of course also be called from a view belonging to the
|
||||
|
@ -172,12 +184,14 @@ module ActionView
|
|||
content_tag("ul", items.uniq)
|
||||
end
|
||||
|
||||
# DEPRECATION WARNING: This method will become a separate plugin when Rails 2.0 ships.
|
||||
#
|
||||
# Wrapper for text_field with added AJAX autocompletion functionality.
|
||||
#
|
||||
# In your controller, you'll need to define an action called
|
||||
# auto_complete_for_object_method to respond the AJAX calls,
|
||||
# auto_complete_for to respond the AJAX calls,
|
||||
#
|
||||
# See the RDoc on ActionController::AutoComplete to learn more about this.
|
||||
# See the RDoc on ActionController::Macros::AutoComplete to learn more about this.
|
||||
def text_field_with_auto_complete(object, method, tag_options = {}, completion_options = {})
|
||||
(completion_options[:skip_style] ? "" : auto_complete_stylesheet) +
|
||||
text_field(object, method, tag_options) +
|
||||
|
@ -187,7 +201,7 @@ module ActionView
|
|||
|
||||
private
|
||||
def auto_complete_stylesheet
|
||||
content_tag("style", <<-EOT
|
||||
content_tag('style', <<-EOT, :type => 'text/css')
|
||||
div.auto_complete {
|
||||
width: 350px;
|
||||
background: #fff;
|
||||
|
@ -212,7 +226,6 @@ module ActionView
|
|||
padding:0;
|
||||
}
|
||||
EOT
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require File.dirname(__FILE__) + '/tag_helper'
|
||||
require File.dirname(__FILE__) + '/prototype_helper'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
|
@ -40,15 +41,50 @@ module ActionView
|
|||
unless const_defined? :JAVASCRIPT_PATH
|
||||
JAVASCRIPT_PATH = File.join(File.dirname(__FILE__), 'javascripts')
|
||||
end
|
||||
|
||||
include PrototypeHelper
|
||||
|
||||
# Returns a link that'll trigger a JavaScript +function+ using the
|
||||
# Returns a link that will trigger a JavaScript +function+ using the
|
||||
# onclick handler and return false after the fact.
|
||||
#
|
||||
# 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).
|
||||
#
|
||||
# Examples:
|
||||
# link_to_function "Greeting", "alert('Hello world!')"
|
||||
# link_to_function(image_tag("delete"), "if confirm('Really?'){ do_delete(); }")
|
||||
def link_to_function(name, function, html_options = {})
|
||||
# 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 {
|
||||
# $("details").visualEffect("toggle_blind");
|
||||
# $("more_link").update("Show me less");
|
||||
# }
|
||||
# catch (e) {
|
||||
# alert('RJS error:\n\n' + e.toString());
|
||||
# alert('$(\"details\").visualEffect(\"toggle_blind\");
|
||||
# \n$(\"more_link\").update(\"Show me less\");');
|
||||
# throw e
|
||||
# };
|
||||
# return false;">Show me more</a>
|
||||
#
|
||||
def link_to_function(name, *args, &block)
|
||||
html_options = args.last.is_a?(Hash) ? args.pop : {}
|
||||
function = args[0] || ''
|
||||
|
||||
html_options.symbolize_keys!
|
||||
function = update_page(&block) if block_given?
|
||||
content_tag(
|
||||
"a", name,
|
||||
html_options.merge({
|
||||
|
@ -58,14 +94,28 @@ module ActionView
|
|||
)
|
||||
end
|
||||
|
||||
# Returns a link that'll trigger a JavaScript +function+ using the
|
||||
# Returns a button that'll trigger a JavaScript +function+ using the
|
||||
# onclick handler.
|
||||
#
|
||||
# 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).
|
||||
#
|
||||
# Examples:
|
||||
# button_to_function "Greeting", "alert('Hello world!')"
|
||||
# button_to_function "Delete", "if confirm('Really?'){ do_delete(); }")
|
||||
def button_to_function(name, function, html_options = {})
|
||||
# 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.last.is_a?(Hash) ? args.pop : {}
|
||||
function = args[0] || ''
|
||||
|
||||
html_options.symbolize_keys!
|
||||
function = update_page(&block) if block_given?
|
||||
tag(:input, html_options.merge({
|
||||
:type => "button", :value => name,
|
||||
:onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
|
||||
|
@ -99,13 +149,24 @@ module ActionView
|
|||
|
||||
# Escape carrier returns and single and double quotes for JavaScript segments.
|
||||
def escape_javascript(javascript)
|
||||
(javascript || '').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }
|
||||
(javascript || '').gsub('\\','\0\0').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }
|
||||
end
|
||||
|
||||
# Returns a JavaScript tag with the +content+ inside. Example:
|
||||
# javascript_tag "alert('All is good')" # => <script type="text/javascript">alert('All is good')</script>
|
||||
def javascript_tag(content)
|
||||
content_tag("script", javascript_cdata_section(content), :type => "text/javascript")
|
||||
# javascript_tag "alert('All is good')"
|
||||
#
|
||||
# Returns:
|
||||
#
|
||||
# <script type="text/javascript">
|
||||
# //<![CDATA[
|
||||
# alert('All is good')
|
||||
# //]]>
|
||||
# </script>
|
||||
#
|
||||
# +html_options+ may be a hash of attributes for the <script> tag. Example:
|
||||
# javascript_tag "alert('All is good')", :defer => 'true' # => <script defer="true" type="text/javascript">alert('All is good')</script>
|
||||
def javascript_tag(content, html_options = {})
|
||||
content_tag("script", javascript_cdata_section(content), html_options.merge(:type => "text/javascript"))
|
||||
end
|
||||
|
||||
def javascript_cdata_section(content) #:nodoc:
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
|
||||
// (c) 2005 Jon Tirsen (http://www.tirsen.com)
|
||||
// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
|
||||
// (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com)
|
||||
// Contributors:
|
||||
// Richard Livsey
|
||||
// Rahul Bhargava
|
||||
// Rob Wills
|
||||
//
|
||||
// See scriptaculous.js for full license.
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
// Autocompleter.Base handles all the autocompletion functionality
|
||||
// that's independent of the data source for autocompletion. This
|
||||
|
@ -33,6 +34,9 @@
|
|||
// useful when one of the tokens is \n (a newline), as it
|
||||
// allows smart autocompletion after linebreaks.
|
||||
|
||||
if(typeof Effect == 'undefined')
|
||||
throw("controls.js requires including script.aculo.us' effects.js library");
|
||||
|
||||
var Autocompleter = {}
|
||||
Autocompleter.Base = function() {};
|
||||
Autocompleter.Base.prototype = {
|
||||
|
@ -45,7 +49,7 @@ Autocompleter.Base.prototype = {
|
|||
this.index = 0;
|
||||
this.entryCount = 0;
|
||||
|
||||
if (this.setOptions)
|
||||
if(this.setOptions)
|
||||
this.setOptions(options);
|
||||
else
|
||||
this.options = options || {};
|
||||
|
@ -55,17 +59,20 @@ Autocompleter.Base.prototype = {
|
|||
this.options.frequency = this.options.frequency || 0.4;
|
||||
this.options.minChars = this.options.minChars || 1;
|
||||
this.options.onShow = this.options.onShow ||
|
||||
function(element, update){
|
||||
if(!update.style.position || update.style.position=='absolute') {
|
||||
update.style.position = 'absolute';
|
||||
Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
|
||||
}
|
||||
Effect.Appear(update,{duration:0.15});
|
||||
};
|
||||
function(element, update){
|
||||
if(!update.style.position || update.style.position=='absolute') {
|
||||
update.style.position = 'absolute';
|
||||
Position.clone(element, update, {
|
||||
setHeight: false,
|
||||
offsetTop: element.offsetHeight
|
||||
});
|
||||
}
|
||||
Effect.Appear(update,{duration:0.15});
|
||||
};
|
||||
this.options.onHide = this.options.onHide ||
|
||||
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
|
||||
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
|
||||
|
||||
if (typeof(this.options.tokens) == 'string')
|
||||
if(typeof(this.options.tokens) == 'string')
|
||||
this.options.tokens = new Array(this.options.tokens);
|
||||
|
||||
this.observer = null;
|
||||
|
@ -94,7 +101,7 @@ Autocompleter.Base.prototype = {
|
|||
},
|
||||
|
||||
fixIEOverlapping: function() {
|
||||
Position.clone(this.update, this.iefix);
|
||||
Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
|
||||
this.iefix.style.zIndex = 1;
|
||||
this.update.style.zIndex = 2;
|
||||
Element.show(this.iefix);
|
||||
|
@ -202,11 +209,13 @@ Autocompleter.Base.prototype = {
|
|||
markPrevious: function() {
|
||||
if(this.index > 0) this.index--
|
||||
else this.index = this.entryCount-1;
|
||||
this.getEntry(this.index).scrollIntoView(true);
|
||||
},
|
||||
|
||||
markNext: function() {
|
||||
if(this.index < this.entryCount-1) this.index++
|
||||
else this.index = 0;
|
||||
this.getEntry(this.index).scrollIntoView(false);
|
||||
},
|
||||
|
||||
getEntry: function(index) {
|
||||
|
@ -254,11 +263,11 @@ Autocompleter.Base.prototype = {
|
|||
if(!this.changed && this.hasFocus) {
|
||||
this.update.innerHTML = choices;
|
||||
Element.cleanWhitespace(this.update);
|
||||
Element.cleanWhitespace(this.update.firstChild);
|
||||
Element.cleanWhitespace(this.update.down());
|
||||
|
||||
if(this.update.firstChild && this.update.firstChild.childNodes) {
|
||||
if(this.update.firstChild && this.update.down().childNodes) {
|
||||
this.entryCount =
|
||||
this.update.firstChild.childNodes.length;
|
||||
this.update.down().childNodes.length;
|
||||
for (var i = 0; i < this.entryCount; i++) {
|
||||
var entry = this.getEntry(i);
|
||||
entry.autocompleteIndex = i;
|
||||
|
@ -269,9 +278,14 @@ Autocompleter.Base.prototype = {
|
|||
}
|
||||
|
||||
this.stopIndicator();
|
||||
|
||||
this.index = 0;
|
||||
this.render();
|
||||
|
||||
if(this.entryCount==1 && this.options.autoSelect) {
|
||||
this.selectEntry();
|
||||
this.hide();
|
||||
} else {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -459,6 +473,7 @@ Ajax.InPlaceEditor.prototype = {
|
|||
this.element = $(element);
|
||||
|
||||
this.options = Object.extend({
|
||||
paramName: "value",
|
||||
okButton: true,
|
||||
okText: "ok",
|
||||
cancelLink: true,
|
||||
|
@ -531,7 +546,7 @@ Ajax.InPlaceEditor.prototype = {
|
|||
Element.hide(this.element);
|
||||
this.createForm();
|
||||
this.element.parentNode.insertBefore(this.form, this.element);
|
||||
Field.scrollFreeActivate(this.editField);
|
||||
if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
|
||||
// stop the event to avoid a page refresh in Safari
|
||||
if (evt) {
|
||||
Event.stop(evt);
|
||||
|
@ -590,7 +605,7 @@ Ajax.InPlaceEditor.prototype = {
|
|||
var textField = document.createElement("input");
|
||||
textField.obj = this;
|
||||
textField.type = "text";
|
||||
textField.name = "value";
|
||||
textField.name = this.options.paramName;
|
||||
textField.value = text;
|
||||
textField.style.backgroundColor = this.options.highlightcolor;
|
||||
textField.className = 'editor_field';
|
||||
|
@ -603,7 +618,7 @@ Ajax.InPlaceEditor.prototype = {
|
|||
this.options.textarea = true;
|
||||
var textArea = document.createElement("textarea");
|
||||
textArea.obj = this;
|
||||
textArea.name = "value";
|
||||
textArea.name = this.options.paramName;
|
||||
textArea.value = this.convertHTMLLineBreaks(text);
|
||||
textArea.rows = this.options.rows;
|
||||
textArea.cols = this.options.cols || 40;
|
||||
|
@ -636,6 +651,7 @@ Ajax.InPlaceEditor.prototype = {
|
|||
Element.removeClassName(this.form, this.options.loadingClassName);
|
||||
this.editField.disabled = false;
|
||||
this.editField.value = transport.responseText.stripTags();
|
||||
Field.scrollFreeActivate(this.editField);
|
||||
},
|
||||
onclickCancel: function() {
|
||||
this.onComplete();
|
||||
|
@ -772,6 +788,8 @@ Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
|
|||
collection.each(function(e,i) {
|
||||
optionTag = document.createElement("option");
|
||||
optionTag.value = (e instanceof Array) ? e[0] : e;
|
||||
if((typeof this.options.value == 'undefined') &&
|
||||
((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
|
||||
if(this.options.value==optionTag.value) optionTag.selected = true;
|
||||
optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
|
||||
selectTag.appendChild(optionTag);
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
|
||||
// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
|
||||
//
|
||||
// See scriptaculous.js for full license.
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
if(typeof Effect == 'undefined')
|
||||
throw("dragdrop.js requires including script.aculo.us' effects.js library");
|
||||
|
||||
var Droppables = {
|
||||
drops: [],
|
||||
|
@ -145,8 +147,16 @@ var Draggables = {
|
|||
},
|
||||
|
||||
activate: function(draggable) {
|
||||
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
|
||||
this.activeDraggable = draggable;
|
||||
if(draggable.options.delay) {
|
||||
this._timeout = setTimeout(function() {
|
||||
Draggables._timeout = null;
|
||||
window.focus();
|
||||
Draggables.activeDraggable = draggable;
|
||||
}.bind(this), draggable.options.delay);
|
||||
} else {
|
||||
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
|
||||
this.activeDraggable = draggable;
|
||||
}
|
||||
},
|
||||
|
||||
deactivate: function() {
|
||||
|
@ -160,10 +170,15 @@ var Draggables = {
|
|||
// the same coordinates, prevent needless redrawing (moz bug?)
|
||||
if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
|
||||
this._lastPointer = pointer;
|
||||
|
||||
this.activeDraggable.updateDrag(event, pointer);
|
||||
},
|
||||
|
||||
endDrag: function(event) {
|
||||
if(this._timeout) {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = null;
|
||||
}
|
||||
if(!this.activeDraggable) return;
|
||||
this._lastPointer = null;
|
||||
this.activeDraggable.endDrag(event);
|
||||
|
@ -190,6 +205,7 @@ var Draggables = {
|
|||
this.observers.each( function(o) {
|
||||
if(o[eventName]) o[eventName](eventName, draggable, event);
|
||||
});
|
||||
if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
|
||||
},
|
||||
|
||||
_cacheObserverCallbacks: function() {
|
||||
|
@ -204,39 +220,59 @@ var Draggables = {
|
|||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var Draggable = Class.create();
|
||||
Draggable._dragging = {};
|
||||
|
||||
Draggable.prototype = {
|
||||
initialize: function(element) {
|
||||
var options = Object.extend({
|
||||
var defaults = {
|
||||
handle: false,
|
||||
starteffect: function(element) {
|
||||
new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
|
||||
},
|
||||
reverteffect: function(element, top_offset, left_offset) {
|
||||
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
|
||||
element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur});
|
||||
new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
|
||||
queue: {scope:'_draggable', position:'end'}
|
||||
});
|
||||
},
|
||||
endeffect: function(element) {
|
||||
new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
|
||||
endeffect: function(element) {
|
||||
var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0;
|
||||
new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
|
||||
queue: {scope:'_draggable', position:'end'},
|
||||
afterFinish: function(){
|
||||
Draggable._dragging[element] = false
|
||||
}
|
||||
});
|
||||
},
|
||||
zindex: 1000,
|
||||
revert: false,
|
||||
scroll: false,
|
||||
scrollSensitivity: 20,
|
||||
scrollSpeed: 15,
|
||||
snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] }
|
||||
}, arguments[1] || {});
|
||||
snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
|
||||
delay: 0
|
||||
};
|
||||
|
||||
if(!arguments[1] || typeof arguments[1].endeffect == 'undefined')
|
||||
Object.extend(defaults, {
|
||||
starteffect: function(element) {
|
||||
element._opacity = Element.getOpacity(element);
|
||||
Draggable._dragging[element] = true;
|
||||
new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
|
||||
}
|
||||
});
|
||||
|
||||
var options = Object.extend(defaults, arguments[1] || {});
|
||||
|
||||
this.element = $(element);
|
||||
|
||||
if(options.handle && (typeof options.handle == 'string')) {
|
||||
var h = Element.childrenWithClassName(this.element, options.handle, true);
|
||||
if(h.length>0) this.handle = h[0];
|
||||
}
|
||||
if(options.handle && (typeof options.handle == 'string'))
|
||||
this.handle = this.element.down('.'+options.handle, 0);
|
||||
|
||||
if(!this.handle) this.handle = $(options.handle);
|
||||
if(!this.handle) this.handle = this.element;
|
||||
|
||||
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML)
|
||||
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
|
||||
options.scroll = $(options.scroll);
|
||||
this._isScrollChild = Element.childOf(this.element, options.scroll);
|
||||
}
|
||||
|
||||
Element.makePositioned(this.element); // fix IE
|
||||
|
||||
|
@ -262,6 +298,8 @@ Draggable.prototype = {
|
|||
},
|
||||
|
||||
initDrag: function(event) {
|
||||
if(typeof Draggable._dragging[this.element] != 'undefined' &&
|
||||
Draggable._dragging[this.element]) return;
|
||||
if(Event.isLeftClick(event)) {
|
||||
// abort on form elements, fixes a Firefox issue
|
||||
var src = Event.element(event);
|
||||
|
@ -272,11 +310,6 @@ Draggable.prototype = {
|
|||
src.tagName=='BUTTON' ||
|
||||
src.tagName=='TEXTAREA')) return;
|
||||
|
||||
if(this.element._revert) {
|
||||
this.element._revert.cancel();
|
||||
this.element._revert = null;
|
||||
}
|
||||
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
var pos = Position.cumulativeOffset(this.element);
|
||||
this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
|
||||
|
@ -312,6 +345,7 @@ Draggable.prototype = {
|
|||
}
|
||||
|
||||
Draggables.notify('onStart', this, event);
|
||||
|
||||
if(this.options.starteffect) this.options.starteffect(this.element);
|
||||
},
|
||||
|
||||
|
@ -320,6 +354,7 @@ Draggable.prototype = {
|
|||
Position.prepare();
|
||||
Droppables.show(pointer, this.element);
|
||||
Draggables.notify('onDrag', this, event);
|
||||
|
||||
this.draw(pointer);
|
||||
if(this.options.change) this.options.change(this);
|
||||
|
||||
|
@ -331,8 +366,8 @@ Draggable.prototype = {
|
|||
with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
|
||||
} else {
|
||||
p = Position.page(this.options.scroll);
|
||||
p[0] += this.options.scroll.scrollLeft;
|
||||
p[1] += this.options.scroll.scrollTop;
|
||||
p[0] += this.options.scroll.scrollLeft + Position.deltaX;
|
||||
p[1] += this.options.scroll.scrollTop + Position.deltaY;
|
||||
p.push(p[0]+this.options.scroll.offsetWidth);
|
||||
p.push(p[1]+this.options.scroll.offsetHeight);
|
||||
}
|
||||
|
@ -378,7 +413,7 @@ Draggable.prototype = {
|
|||
|
||||
if(this.options.endeffect)
|
||||
this.options.endeffect(this.element);
|
||||
|
||||
|
||||
Draggables.deactivate(this);
|
||||
Droppables.reset();
|
||||
},
|
||||
|
@ -398,10 +433,15 @@ Draggable.prototype = {
|
|||
|
||||
draw: function(point) {
|
||||
var pos = Position.cumulativeOffset(this.element);
|
||||
if(this.options.ghosting) {
|
||||
var r = Position.realOffset(this.element);
|
||||
pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
|
||||
}
|
||||
|
||||
var d = this.currentDelta();
|
||||
pos[0] -= d[0]; pos[1] -= d[1];
|
||||
|
||||
if(this.options.scroll && (this.options.scroll != window)) {
|
||||
if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
|
||||
pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
|
||||
pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
|
||||
}
|
||||
|
@ -412,7 +452,7 @@ Draggable.prototype = {
|
|||
|
||||
if(this.options.snap) {
|
||||
if(typeof this.options.snap == 'function') {
|
||||
p = this.options.snap(p[0],p[1]);
|
||||
p = this.options.snap(p[0],p[1],this);
|
||||
} else {
|
||||
if(this.options.snap instanceof Array) {
|
||||
p = p.map( function(v, i) {
|
||||
|
@ -428,6 +468,7 @@ Draggable.prototype = {
|
|||
style.left = p[0] + "px";
|
||||
if((!this.options.constraint) || (this.options.constraint=='vertical'))
|
||||
style.top = p[1] + "px";
|
||||
|
||||
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
|
||||
},
|
||||
|
||||
|
@ -440,6 +481,7 @@ Draggable.prototype = {
|
|||
},
|
||||
|
||||
startScrolling: function(speed) {
|
||||
if(!(speed[0] || speed[1])) return;
|
||||
this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
|
||||
this.lastScrolled = new Date();
|
||||
this.scrollInterval = setInterval(this.scroll.bind(this), 10);
|
||||
|
@ -464,14 +506,16 @@ Draggable.prototype = {
|
|||
Position.prepare();
|
||||
Droppables.show(Draggables._lastPointer, this.element);
|
||||
Draggables.notify('onDrag', this);
|
||||
Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
|
||||
Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
|
||||
Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
|
||||
if (Draggables._lastScrollPointer[0] < 0)
|
||||
Draggables._lastScrollPointer[0] = 0;
|
||||
if (Draggables._lastScrollPointer[1] < 0)
|
||||
Draggables._lastScrollPointer[1] = 0;
|
||||
this.draw(Draggables._lastScrollPointer);
|
||||
if (this._isScrollChild) {
|
||||
Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
|
||||
Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
|
||||
Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
|
||||
if (Draggables._lastScrollPointer[0] < 0)
|
||||
Draggables._lastScrollPointer[0] = 0;
|
||||
if (Draggables._lastScrollPointer[1] < 0)
|
||||
Draggables._lastScrollPointer[1] = 0;
|
||||
this.draw(Draggables._lastScrollPointer);
|
||||
}
|
||||
|
||||
if(this.options.change) this.options.change(this);
|
||||
},
|
||||
|
@ -523,6 +567,8 @@ SortableObserver.prototype = {
|
|||
}
|
||||
|
||||
var Sortable = {
|
||||
SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
|
||||
|
||||
sortables: {},
|
||||
|
||||
_findRootElement: function(element) {
|
||||
|
@ -563,12 +609,13 @@ var Sortable = {
|
|||
containment: element, // also takes array of elements (or id's); or false
|
||||
handle: false, // or a CSS class
|
||||
only: false,
|
||||
delay: 0,
|
||||
hoverclass: null,
|
||||
ghosting: false,
|
||||
scroll: false,
|
||||
scrollSensitivity: 20,
|
||||
scrollSpeed: 15,
|
||||
format: /^[^_]*_(.*)$/,
|
||||
format: this.SERIALIZE_RULE,
|
||||
onChange: Prototype.emptyFunction,
|
||||
onUpdate: Prototype.emptyFunction
|
||||
}, arguments[1] || {});
|
||||
|
@ -582,6 +629,7 @@ var Sortable = {
|
|||
scroll: options.scroll,
|
||||
scrollSpeed: options.scrollSpeed,
|
||||
scrollSensitivity: options.scrollSensitivity,
|
||||
delay: options.delay,
|
||||
ghosting: options.ghosting,
|
||||
constraint: options.constraint,
|
||||
handle: options.handle };
|
||||
|
@ -610,7 +658,6 @@ var Sortable = {
|
|||
tree: options.tree,
|
||||
hoverclass: options.hoverclass,
|
||||
onHover: Sortable.onHover
|
||||
//greedy: !options.dropOnEmpty
|
||||
}
|
||||
|
||||
var options_for_tree = {
|
||||
|
@ -635,7 +682,7 @@ var Sortable = {
|
|||
(this.findElements(element, options) || []).each( function(e) {
|
||||
// handles are per-draggable
|
||||
var handle = options.handle ?
|
||||
Element.childrenWithClassName(e, options.handle)[0] : e;
|
||||
$(e).down('.'+options.handle,0) : e;
|
||||
options.draggables.push(
|
||||
new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
|
||||
Droppables.add(e, options_for_droppable);
|
||||
|
@ -706,7 +753,7 @@ var Sortable = {
|
|||
if(!Element.isParent(dropon, element)) {
|
||||
var index;
|
||||
|
||||
var children = Sortable.findElements(dropon, {tag: droponOptions.tag});
|
||||
var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
|
||||
var child = null;
|
||||
|
||||
if(children) {
|
||||
|
@ -733,7 +780,7 @@ var Sortable = {
|
|||
},
|
||||
|
||||
unmark: function() {
|
||||
if(Sortable._marker) Element.hide(Sortable._marker);
|
||||
if(Sortable._marker) Sortable._marker.hide();
|
||||
},
|
||||
|
||||
mark: function(dropon, position) {
|
||||
|
@ -742,23 +789,21 @@ var Sortable = {
|
|||
if(sortable && !sortable.ghosting) return;
|
||||
|
||||
if(!Sortable._marker) {
|
||||
Sortable._marker = $('dropmarker') || document.createElement('DIV');
|
||||
Element.hide(Sortable._marker);
|
||||
Element.addClassName(Sortable._marker, 'dropmarker');
|
||||
Sortable._marker.style.position = 'absolute';
|
||||
Sortable._marker =
|
||||
($('dropmarker') || Element.extend(document.createElement('DIV'))).
|
||||
hide().addClassName('dropmarker').setStyle({position:'absolute'});
|
||||
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
|
||||
}
|
||||
var offsets = Position.cumulativeOffset(dropon);
|
||||
Sortable._marker.style.left = offsets[0] + 'px';
|
||||
Sortable._marker.style.top = offsets[1] + 'px';
|
||||
Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
|
||||
|
||||
if(position=='after')
|
||||
if(sortable.overlap == 'horizontal')
|
||||
Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
|
||||
Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
|
||||
else
|
||||
Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
|
||||
Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
|
||||
|
||||
Element.show(Sortable._marker);
|
||||
Sortable._marker.show();
|
||||
},
|
||||
|
||||
_tree: function(element, options, parent) {
|
||||
|
@ -773,9 +818,9 @@ var Sortable = {
|
|||
id: encodeURIComponent(match ? match[1] : null),
|
||||
element: element,
|
||||
parent: parent,
|
||||
children: new Array,
|
||||
children: [],
|
||||
position: parent.children.length,
|
||||
container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase())
|
||||
container: $(children[i]).down(options.treeTag)
|
||||
}
|
||||
|
||||
/* Get the element containing the children and recurse over it */
|
||||
|
@ -788,17 +833,6 @@ var Sortable = {
|
|||
return parent;
|
||||
},
|
||||
|
||||
/* Finds the first element of the given tag type within a parent element.
|
||||
Used for finding the first LI[ST] within a L[IST]I[TEM].*/
|
||||
_findChildrenElement: function (element, containerTag) {
|
||||
if (element && element.hasChildNodes)
|
||||
for (var i = 0; i < element.childNodes.length; ++i)
|
||||
if (element.childNodes[i].tagName == containerTag)
|
||||
return element.childNodes[i];
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
tree: function(element) {
|
||||
element = $(element);
|
||||
var sortableOptions = this.options(element);
|
||||
|
@ -813,12 +847,12 @@ var Sortable = {
|
|||
var root = {
|
||||
id: null,
|
||||
parent: null,
|
||||
children: new Array,
|
||||
children: [],
|
||||
container: element,
|
||||
position: 0
|
||||
}
|
||||
|
||||
return Sortable._tree (element, options, root);
|
||||
return Sortable._tree(element, options, root);
|
||||
},
|
||||
|
||||
/* Construct a [i] index for a particular node */
|
||||
|
@ -867,7 +901,7 @@ var Sortable = {
|
|||
|
||||
if (options.tree) {
|
||||
return Sortable.tree(element, arguments[1]).children.map( function (item) {
|
||||
return [name + Sortable._constructIndex(item) + "=" +
|
||||
return [name + Sortable._constructIndex(item) + "[id]=" +
|
||||
encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
|
||||
}).flatten().join('&');
|
||||
} else {
|
||||
|
@ -878,12 +912,10 @@ var Sortable = {
|
|||
}
|
||||
}
|
||||
|
||||
/* Returns true if child is contained within element */
|
||||
// Returns true if child is contained within element
|
||||
Element.isParent = function(child, element) {
|
||||
if (!child.parentNode || child == element) return false;
|
||||
|
||||
if (child.parentNode == element) return true;
|
||||
|
||||
return Element.isParent(child.parentNode, element);
|
||||
}
|
||||
|
||||
|
@ -906,8 +938,5 @@ Element.findChildren = function(element, only, recursive, tagName) {
|
|||
}
|
||||
|
||||
Element.offsetSize = function (element, type) {
|
||||
if (type == 'vertical' || type == 'height')
|
||||
return element.offsetHeight;
|
||||
else
|
||||
return element.offsetWidth;
|
||||
}
|
||||
return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
|
||||
}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// Contributors:
|
||||
// Justin Palmer (http://encytemedia.com/)
|
||||
// Mark Pilgrim (http://diveintomark.org/)
|
||||
// Martin Bialasinki
|
||||
//
|
||||
// See scriptaculous.js for full license.
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
// converts rgb() and #xxx to #xxxxxx format,
|
||||
// returns self (or first argument) if not convertable
|
||||
String.prototype.parseColor = function() {
|
||||
var color = '#';
|
||||
var color = '#';
|
||||
if(this.slice(0,4) == 'rgb(') {
|
||||
var cols = this.slice(4,this.length-1).split(',');
|
||||
var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
|
||||
|
@ -41,15 +42,17 @@ Element.collectTextNodesIgnoreClass = function(element, className) {
|
|||
|
||||
Element.setContentZoom = function(element, percent) {
|
||||
element = $(element);
|
||||
Element.setStyle(element, {fontSize: (percent/100) + 'em'});
|
||||
element.setStyle({fontSize: (percent/100) + 'em'});
|
||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
|
||||
return element;
|
||||
}
|
||||
|
||||
Element.getOpacity = function(element){
|
||||
Element.getOpacity = function(element){
|
||||
element = $(element);
|
||||
var opacity;
|
||||
if (opacity = Element.getStyle(element, 'opacity'))
|
||||
if (opacity = element.getStyle('opacity'))
|
||||
return parseFloat(opacity);
|
||||
if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))
|
||||
if (opacity = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
|
||||
if(opacity[1]) return parseFloat(opacity[1]) / 100;
|
||||
return 1.0;
|
||||
}
|
||||
|
@ -57,34 +60,26 @@ Element.getOpacity = function(element){
|
|||
Element.setOpacity = function(element, value){
|
||||
element= $(element);
|
||||
if (value == 1){
|
||||
Element.setStyle(element, { opacity:
|
||||
element.setStyle({ opacity:
|
||||
(/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ?
|
||||
0.999999 : null });
|
||||
if(/MSIE/.test(navigator.userAgent))
|
||||
Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});
|
||||
0.999999 : 1.0 });
|
||||
if(/MSIE/.test(navigator.userAgent) && !window.opera)
|
||||
element.setStyle({filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});
|
||||
} else {
|
||||
if(value < 0.00001) value = 0;
|
||||
Element.setStyle(element, {opacity: value});
|
||||
if(/MSIE/.test(navigator.userAgent))
|
||||
Element.setStyle(element,
|
||||
{ filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
|
||||
'alpha(opacity='+value*100+')' });
|
||||
element.setStyle({opacity: value});
|
||||
if(/MSIE/.test(navigator.userAgent) && !window.opera)
|
||||
element.setStyle(
|
||||
{ filter: element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') +
|
||||
'alpha(opacity='+value*100+')' });
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
Element.getInlineOpacity = function(element){
|
||||
return $(element).style.opacity || '';
|
||||
}
|
||||
|
||||
Element.childrenWithClassName = function(element, className, findFirst) {
|
||||
var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)");
|
||||
var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) {
|
||||
return (c.className && c.className.match(classNameRegExp));
|
||||
});
|
||||
if(!results) results = [];
|
||||
return results;
|
||||
}
|
||||
|
||||
Element.forceRerendering = function(element) {
|
||||
try {
|
||||
element = $(element);
|
||||
|
@ -104,9 +99,17 @@ Array.prototype.call = function() {
|
|||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var Effect = {
|
||||
_elementDoesNotExistError: {
|
||||
name: 'ElementDoesNotExistError',
|
||||
message: 'The specified DOM element does not exist, but is required for this effect to operate'
|
||||
},
|
||||
tagifyText: function(element) {
|
||||
if(typeof Builder == 'undefined')
|
||||
throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
|
||||
|
||||
var tagifyStyle = 'position:relative';
|
||||
if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1';
|
||||
if(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1';
|
||||
|
||||
element = $(element);
|
||||
$A(element.childNodes).each( function(child) {
|
||||
if(child.nodeType==3) {
|
||||
|
@ -159,33 +162,35 @@ var Effect2 = Effect; // deprecated
|
|||
|
||||
/* ------------- transitions ------------- */
|
||||
|
||||
Effect.Transitions = {}
|
||||
|
||||
Effect.Transitions.linear = function(pos) {
|
||||
return pos;
|
||||
}
|
||||
Effect.Transitions.sinoidal = function(pos) {
|
||||
return (-Math.cos(pos*Math.PI)/2) + 0.5;
|
||||
}
|
||||
Effect.Transitions.reverse = function(pos) {
|
||||
return 1-pos;
|
||||
}
|
||||
Effect.Transitions.flicker = function(pos) {
|
||||
return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
|
||||
}
|
||||
Effect.Transitions.wobble = function(pos) {
|
||||
return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
|
||||
}
|
||||
Effect.Transitions.pulse = function(pos) {
|
||||
return (Math.floor(pos*10) % 2 == 0 ?
|
||||
(pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
|
||||
}
|
||||
Effect.Transitions.none = function(pos) {
|
||||
return 0;
|
||||
}
|
||||
Effect.Transitions.full = function(pos) {
|
||||
return 1;
|
||||
}
|
||||
Effect.Transitions = {
|
||||
linear: Prototype.K,
|
||||
sinoidal: function(pos) {
|
||||
return (-Math.cos(pos*Math.PI)/2) + 0.5;
|
||||
},
|
||||
reverse: function(pos) {
|
||||
return 1-pos;
|
||||
},
|
||||
flicker: function(pos) {
|
||||
return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
|
||||
},
|
||||
wobble: function(pos) {
|
||||
return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
|
||||
},
|
||||
pulse: function(pos, pulses) {
|
||||
pulses = pulses || 5;
|
||||
return (
|
||||
Math.round((pos % (1/pulses)) * pulses) == 0 ?
|
||||
((pos * pulses * 2) - Math.floor(pos * pulses * 2)) :
|
||||
1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2))
|
||||
);
|
||||
},
|
||||
none: function(pos) {
|
||||
return 0;
|
||||
},
|
||||
full: function(pos) {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
/* ------------- core effects ------------- */
|
||||
|
||||
|
@ -212,6 +217,9 @@ Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
|
|||
e.finishOn += effect.finishOn;
|
||||
});
|
||||
break;
|
||||
case 'with-last':
|
||||
timestamp = this.effects.pluck('startOn').max() || timestamp;
|
||||
break;
|
||||
case 'end':
|
||||
// start effect after last queued effect has finished
|
||||
timestamp = this.effects.pluck('finishOn').max() || timestamp;
|
||||
|
@ -348,12 +356,24 @@ Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
|
|||
}
|
||||
});
|
||||
|
||||
Effect.Event = Class.create();
|
||||
Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), {
|
||||
initialize: function() {
|
||||
var options = Object.extend({
|
||||
duration: 0
|
||||
}, arguments[0] || {});
|
||||
this.start(options);
|
||||
},
|
||||
update: Prototype.emptyFunction
|
||||
});
|
||||
|
||||
Effect.Opacity = Class.create();
|
||||
Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element) {
|
||||
this.element = $(element);
|
||||
if(!this.element) throw(Effect._elementDoesNotExistError);
|
||||
// make this work on IE on elements without 'layout'
|
||||
if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
|
||||
if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout))
|
||||
this.element.setStyle({zoom: 1});
|
||||
var options = Object.extend({
|
||||
from: this.element.getOpacity() || 0.0,
|
||||
|
@ -370,6 +390,7 @@ Effect.Move = Class.create();
|
|||
Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element) {
|
||||
this.element = $(element);
|
||||
if(!this.element) throw(Effect._elementDoesNotExistError);
|
||||
var options = Object.extend({
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
@ -393,8 +414,8 @@ Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
|
|||
},
|
||||
update: function(position) {
|
||||
this.element.setStyle({
|
||||
left: this.options.x * position + this.originalLeft + 'px',
|
||||
top: this.options.y * position + this.originalTop + 'px'
|
||||
left: Math.round(this.options.x * position + this.originalLeft) + 'px',
|
||||
top: Math.round(this.options.y * position + this.originalTop) + 'px'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -408,7 +429,8 @@ Effect.MoveBy = function(element, toTop, toLeft) {
|
|||
Effect.Scale = Class.create();
|
||||
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element, percent) {
|
||||
this.element = $(element)
|
||||
this.element = $(element);
|
||||
if(!this.element) throw(Effect._elementDoesNotExistError);
|
||||
var options = Object.extend({
|
||||
scaleX: true,
|
||||
scaleY: true,
|
||||
|
@ -433,7 +455,7 @@ Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
|
|||
this.originalLeft = this.element.offsetLeft;
|
||||
|
||||
var fontSize = this.element.getStyle('font-size') || '100%';
|
||||
['em','px','%'].each( function(fontSizeType) {
|
||||
['em','px','%','pt'].each( function(fontSizeType) {
|
||||
if(fontSize.indexOf(fontSizeType)>0) {
|
||||
this.fontSize = parseFloat(fontSize);
|
||||
this.fontSizeType = fontSizeType;
|
||||
|
@ -458,12 +480,12 @@ Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
|
|||
this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
|
||||
},
|
||||
finish: function(position) {
|
||||
if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
|
||||
if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
|
||||
},
|
||||
setDimensions: function(height, width) {
|
||||
var d = {};
|
||||
if(this.options.scaleX) d.width = width + 'px';
|
||||
if(this.options.scaleY) d.height = height + 'px';
|
||||
if(this.options.scaleX) d.width = Math.round(width) + 'px';
|
||||
if(this.options.scaleY) d.height = Math.round(height) + 'px';
|
||||
if(this.options.scaleFromCenter) {
|
||||
var topd = (height - this.dims[0])/2;
|
||||
var leftd = (width - this.dims[1])/2;
|
||||
|
@ -483,6 +505,7 @@ Effect.Highlight = Class.create();
|
|||
Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element) {
|
||||
this.element = $(element);
|
||||
if(!this.element) throw(Effect._elementDoesNotExistError);
|
||||
var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
|
||||
this.start(options);
|
||||
},
|
||||
|
@ -547,8 +570,7 @@ Effect.Fade = function(element) {
|
|||
to: 0.0,
|
||||
afterFinishInternal: function(effect) {
|
||||
if(effect.options.to!=0) return;
|
||||
effect.element.hide();
|
||||
effect.element.setStyle({opacity: oldOpacity});
|
||||
effect.element.hide().setStyle({opacity: oldOpacity});
|
||||
}}, arguments[1] || {});
|
||||
return new Effect.Opacity(element,options);
|
||||
}
|
||||
|
@ -563,25 +585,31 @@ Effect.Appear = function(element) {
|
|||
effect.element.forceRerendering();
|
||||
},
|
||||
beforeSetup: function(effect) {
|
||||
effect.element.setOpacity(effect.options.from);
|
||||
effect.element.show();
|
||||
effect.element.setOpacity(effect.options.from).show();
|
||||
}}, arguments[1] || {});
|
||||
return new Effect.Opacity(element,options);
|
||||
}
|
||||
|
||||
Effect.Puff = function(element) {
|
||||
element = $(element);
|
||||
var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position') };
|
||||
var oldStyle = {
|
||||
opacity: element.getInlineOpacity(),
|
||||
position: element.getStyle('position'),
|
||||
top: element.style.top,
|
||||
left: element.style.left,
|
||||
width: element.style.width,
|
||||
height: element.style.height
|
||||
};
|
||||
return new Effect.Parallel(
|
||||
[ new Effect.Scale(element, 200,
|
||||
{ sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
|
||||
new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
|
||||
Object.extend({ duration: 1.0,
|
||||
beforeSetupInternal: function(effect) {
|
||||
effect.effects[0].element.setStyle({position: 'absolute'}); },
|
||||
Position.absolutize(effect.effects[0].element)
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.effects[0].element.hide();
|
||||
effect.effects[0].element.setStyle(oldStyle); }
|
||||
effect.effects[0].element.hide().setStyle(oldStyle); }
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
@ -589,13 +617,12 @@ Effect.Puff = function(element) {
|
|||
Effect.BlindUp = function(element) {
|
||||
element = $(element);
|
||||
element.makeClipping();
|
||||
return new Effect.Scale(element, 0,
|
||||
return new Effect.Scale(element, 0,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
restoreAfterFinish: true,
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide();
|
||||
effect.element.undoClipping();
|
||||
effect.element.hide().undoClipping();
|
||||
}
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
|
@ -604,28 +631,25 @@ Effect.BlindUp = function(element) {
|
|||
Effect.BlindDown = function(element) {
|
||||
element = $(element);
|
||||
var elementDimensions = element.getDimensions();
|
||||
return new Effect.Scale(element, 100,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleFrom: 0,
|
||||
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
|
||||
restoreAfterFinish: true,
|
||||
afterSetup: function(effect) {
|
||||
effect.element.makeClipping();
|
||||
effect.element.setStyle({height: '0px'});
|
||||
effect.element.show();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.undoClipping();
|
||||
}
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
return new Effect.Scale(element, 100, Object.extend({
|
||||
scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleFrom: 0,
|
||||
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
|
||||
restoreAfterFinish: true,
|
||||
afterSetup: function(effect) {
|
||||
effect.element.makeClipping().setStyle({height: '0px'}).show();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.undoClipping();
|
||||
}
|
||||
}, arguments[1] || {}));
|
||||
}
|
||||
|
||||
Effect.SwitchOff = function(element) {
|
||||
element = $(element);
|
||||
var oldOpacity = element.getInlineOpacity();
|
||||
return new Effect.Appear(element, {
|
||||
return new Effect.Appear(element, Object.extend({
|
||||
duration: 0.4,
|
||||
from: 0,
|
||||
transition: Effect.Transitions.flicker,
|
||||
|
@ -634,18 +658,14 @@ Effect.SwitchOff = function(element) {
|
|||
duration: 0.3, scaleFromCenter: true,
|
||||
scaleX: false, scaleContent: false, restoreAfterFinish: true,
|
||||
beforeSetup: function(effect) {
|
||||
effect.element.makePositioned();
|
||||
effect.element.makeClipping();
|
||||
effect.element.makePositioned().makeClipping();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide();
|
||||
effect.element.undoClipping();
|
||||
effect.element.undoPositioned();
|
||||
effect.element.setStyle({opacity: oldOpacity});
|
||||
effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}, arguments[1] || {}));
|
||||
}
|
||||
|
||||
Effect.DropOut = function(element) {
|
||||
|
@ -663,9 +683,7 @@ Effect.DropOut = function(element) {
|
|||
effect.effects[0].element.makePositioned();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.effects[0].element.hide();
|
||||
effect.effects[0].element.undoPositioned();
|
||||
effect.effects[0].element.setStyle(oldStyle);
|
||||
effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
|
||||
}
|
||||
}, arguments[1] || {}));
|
||||
}
|
||||
|
@ -687,54 +705,42 @@ Effect.Shake = function(element) {
|
|||
{ x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
|
||||
new Effect.Move(effect.element,
|
||||
{ x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
|
||||
effect.element.undoPositioned();
|
||||
effect.element.setStyle(oldStyle);
|
||||
effect.element.undoPositioned().setStyle(oldStyle);
|
||||
}}) }}) }}) }}) }}) }});
|
||||
}
|
||||
|
||||
Effect.SlideDown = function(element) {
|
||||
element = $(element);
|
||||
element.cleanWhitespace();
|
||||
element = $(element).cleanWhitespace();
|
||||
// SlideDown need to have the content of the element wrapped in a container element with fixed height!
|
||||
var oldInnerBottom = $(element.firstChild).getStyle('bottom');
|
||||
var oldInnerBottom = element.down().getStyle('bottom');
|
||||
var elementDimensions = element.getDimensions();
|
||||
return new Effect.Scale(element, 100, Object.extend({
|
||||
scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleFrom: 0,
|
||||
scaleFrom: window.opera ? 0 : 1,
|
||||
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
|
||||
restoreAfterFinish: true,
|
||||
afterSetup: function(effect) {
|
||||
effect.element.makePositioned();
|
||||
effect.element.firstChild.makePositioned();
|
||||
effect.element.down().makePositioned();
|
||||
if(window.opera) effect.element.setStyle({top: ''});
|
||||
effect.element.makeClipping();
|
||||
effect.element.setStyle({height: '0px'});
|
||||
effect.element.show(); },
|
||||
effect.element.makeClipping().setStyle({height: '0px'}).show();
|
||||
},
|
||||
afterUpdateInternal: function(effect) {
|
||||
effect.element.firstChild.setStyle({bottom:
|
||||
effect.element.down().setStyle({bottom:
|
||||
(effect.dims[0] - effect.element.clientHeight) + 'px' });
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.undoClipping();
|
||||
// IE will crash if child is undoPositioned first
|
||||
if(/MSIE/.test(navigator.userAgent)){
|
||||
effect.element.undoPositioned();
|
||||
effect.element.firstChild.undoPositioned();
|
||||
}else{
|
||||
effect.element.firstChild.undoPositioned();
|
||||
effect.element.undoPositioned();
|
||||
}
|
||||
effect.element.firstChild.setStyle({bottom: oldInnerBottom}); }
|
||||
effect.element.undoClipping().undoPositioned();
|
||||
effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Effect.SlideUp = function(element) {
|
||||
element = $(element);
|
||||
element.cleanWhitespace();
|
||||
var oldInnerBottom = $(element.firstChild).getStyle('bottom');
|
||||
return new Effect.Scale(element, 0,
|
||||
element = $(element).cleanWhitespace();
|
||||
var oldInnerBottom = element.down().getStyle('bottom');
|
||||
return new Effect.Scale(element, window.opera ? 0 : 1,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleMode: 'box',
|
||||
|
@ -742,32 +748,32 @@ Effect.SlideUp = function(element) {
|
|||
restoreAfterFinish: true,
|
||||
beforeStartInternal: function(effect) {
|
||||
effect.element.makePositioned();
|
||||
effect.element.firstChild.makePositioned();
|
||||
effect.element.down().makePositioned();
|
||||
if(window.opera) effect.element.setStyle({top: ''});
|
||||
effect.element.makeClipping();
|
||||
effect.element.show(); },
|
||||
effect.element.makeClipping().show();
|
||||
},
|
||||
afterUpdateInternal: function(effect) {
|
||||
effect.element.firstChild.setStyle({bottom:
|
||||
(effect.dims[0] - effect.element.clientHeight) + 'px' }); },
|
||||
effect.element.down().setStyle({bottom:
|
||||
(effect.dims[0] - effect.element.clientHeight) + 'px' });
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide();
|
||||
effect.element.undoClipping();
|
||||
effect.element.firstChild.undoPositioned();
|
||||
effect.element.undoPositioned();
|
||||
effect.element.setStyle({bottom: oldInnerBottom}); }
|
||||
effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom});
|
||||
effect.element.down().undoPositioned();
|
||||
}
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
// Bug in opera makes the TD containing this element expand for a instance after finish
|
||||
Effect.Squish = function(element) {
|
||||
return new Effect.Scale(element, window.opera ? 1 : 0,
|
||||
{ restoreAfterFinish: true,
|
||||
beforeSetup: function(effect) {
|
||||
effect.element.makeClipping(effect.element); },
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide(effect.element);
|
||||
effect.element.undoClipping(effect.element); }
|
||||
return new Effect.Scale(element, window.opera ? 1 : 0, {
|
||||
restoreAfterFinish: true,
|
||||
beforeSetup: function(effect) {
|
||||
effect.element.makeClipping();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide().undoClipping();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -823,9 +829,7 @@ Effect.Grow = function(element) {
|
|||
y: initialMoveY,
|
||||
duration: 0.01,
|
||||
beforeSetup: function(effect) {
|
||||
effect.element.hide();
|
||||
effect.element.makeClipping();
|
||||
effect.element.makePositioned();
|
||||
effect.element.hide().makeClipping().makePositioned();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
new Effect.Parallel(
|
||||
|
@ -836,13 +840,10 @@ Effect.Grow = function(element) {
|
|||
sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
|
||||
], Object.extend({
|
||||
beforeSetup: function(effect) {
|
||||
effect.effects[0].element.setStyle({height: '0px'});
|
||||
effect.effects[0].element.show();
|
||||
effect.effects[0].element.setStyle({height: '0px'}).show();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.effects[0].element.undoClipping();
|
||||
effect.effects[0].element.undoPositioned();
|
||||
effect.effects[0].element.setStyle(oldStyle);
|
||||
effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
|
||||
}
|
||||
}, options)
|
||||
)
|
||||
|
@ -896,13 +897,10 @@ Effect.Shrink = function(element) {
|
|||
new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
|
||||
], Object.extend({
|
||||
beforeStartInternal: function(effect) {
|
||||
effect.effects[0].element.makePositioned();
|
||||
effect.effects[0].element.makeClipping(); },
|
||||
effect.effects[0].element.makePositioned().makeClipping();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.effects[0].element.hide();
|
||||
effect.effects[0].element.undoClipping();
|
||||
effect.effects[0].element.undoPositioned();
|
||||
effect.effects[0].element.setStyle(oldStyle); }
|
||||
effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
|
||||
}, options)
|
||||
);
|
||||
}
|
||||
|
@ -912,10 +910,10 @@ Effect.Pulsate = function(element) {
|
|||
var options = arguments[1] || {};
|
||||
var oldOpacity = element.getInlineOpacity();
|
||||
var transition = options.transition || Effect.Transitions.sinoidal;
|
||||
var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
|
||||
var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
|
||||
reverser.bind(transition);
|
||||
return new Effect.Opacity(element,
|
||||
Object.extend(Object.extend({ duration: 3.0, from: 0,
|
||||
Object.extend(Object.extend({ duration: 2.0, from: 0,
|
||||
afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
|
||||
}, options), {transition: reverser}));
|
||||
}
|
||||
|
@ -927,7 +925,7 @@ Effect.Fold = function(element) {
|
|||
left: element.style.left,
|
||||
width: element.style.width,
|
||||
height: element.style.height };
|
||||
Element.makeClipping(element);
|
||||
element.makeClipping();
|
||||
return new Effect.Scale(element, 5, Object.extend({
|
||||
scaleContent: false,
|
||||
scaleX: false,
|
||||
|
@ -936,15 +934,147 @@ Effect.Fold = function(element) {
|
|||
scaleContent: false,
|
||||
scaleY: false,
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide();
|
||||
effect.element.undoClipping();
|
||||
effect.element.setStyle(oldStyle);
|
||||
effect.element.hide().undoClipping().setStyle(oldStyle);
|
||||
} });
|
||||
}}, arguments[1] || {}));
|
||||
};
|
||||
|
||||
Effect.Morph = Class.create();
|
||||
Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element) {
|
||||
this.element = $(element);
|
||||
if(!this.element) throw(Effect._elementDoesNotExistError);
|
||||
var options = Object.extend({
|
||||
style: ''
|
||||
}, arguments[1] || {});
|
||||
this.start(options);
|
||||
},
|
||||
setup: function(){
|
||||
function parseColor(color){
|
||||
if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
|
||||
color = color.parseColor();
|
||||
return $R(0,2).map(function(i){
|
||||
return parseInt( color.slice(i*2+1,i*2+3), 16 )
|
||||
});
|
||||
}
|
||||
this.transforms = this.options.style.parseStyle().map(function(property){
|
||||
var originalValue = this.element.getStyle(property[0]);
|
||||
return $H({
|
||||
style: property[0],
|
||||
originalValue: property[1].unit=='color' ?
|
||||
parseColor(originalValue) : parseFloat(originalValue || 0),
|
||||
targetValue: property[1].unit=='color' ?
|
||||
parseColor(property[1].value) : property[1].value,
|
||||
unit: property[1].unit
|
||||
});
|
||||
}.bind(this)).reject(function(transform){
|
||||
return (
|
||||
(transform.originalValue == transform.targetValue) ||
|
||||
(
|
||||
transform.unit != 'color' &&
|
||||
(isNaN(transform.originalValue) || isNaN(transform.targetValue))
|
||||
)
|
||||
)
|
||||
});
|
||||
},
|
||||
update: function(position) {
|
||||
var style = $H(), value = null;
|
||||
this.transforms.each(function(transform){
|
||||
value = transform.unit=='color' ?
|
||||
$R(0,2).inject('#',function(m,v,i){
|
||||
return m+(Math.round(transform.originalValue[i]+
|
||||
(transform.targetValue[i] - transform.originalValue[i])*position)).toColorPart() }) :
|
||||
transform.originalValue + Math.round(
|
||||
((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
|
||||
style[transform.style] = value;
|
||||
});
|
||||
this.element.setStyle(style);
|
||||
}
|
||||
});
|
||||
|
||||
Effect.Transform = Class.create();
|
||||
Object.extend(Effect.Transform.prototype, {
|
||||
initialize: function(tracks){
|
||||
this.tracks = [];
|
||||
this.options = arguments[1] || {};
|
||||
this.addTracks(tracks);
|
||||
},
|
||||
addTracks: function(tracks){
|
||||
tracks.each(function(track){
|
||||
var data = $H(track).values().first();
|
||||
this.tracks.push($H({
|
||||
ids: $H(track).keys().first(),
|
||||
effect: Effect.Morph,
|
||||
options: { style: data }
|
||||
}));
|
||||
}.bind(this));
|
||||
return this;
|
||||
},
|
||||
play: function(){
|
||||
return new Effect.Parallel(
|
||||
this.tracks.map(function(track){
|
||||
var elements = [$(track.ids) || $$(track.ids)].flatten();
|
||||
return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
|
||||
}).flatten(),
|
||||
this.options
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Element.CSS_PROPERTIES = ['azimuth', 'backgroundAttachment', 'backgroundColor', 'backgroundImage',
|
||||
'backgroundPosition', 'backgroundRepeat', 'borderBottomColor', 'borderBottomStyle',
|
||||
'borderBottomWidth', 'borderCollapse', 'borderLeftColor', 'borderLeftStyle', 'borderLeftWidth',
|
||||
'borderRightColor', 'borderRightStyle', 'borderRightWidth', 'borderSpacing', 'borderTopColor',
|
||||
'borderTopStyle', 'borderTopWidth', 'bottom', 'captionSide', 'clear', 'clip', 'color', 'content',
|
||||
'counterIncrement', 'counterReset', 'cssFloat', 'cueAfter', 'cueBefore', 'cursor', 'direction',
|
||||
'display', 'elevation', 'emptyCells', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch',
|
||||
'fontStyle', 'fontVariant', 'fontWeight', 'height', 'left', 'letterSpacing', 'lineHeight',
|
||||
'listStyleImage', 'listStylePosition', 'listStyleType', 'marginBottom', 'marginLeft', 'marginRight',
|
||||
'marginTop', 'markerOffset', 'marks', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'opacity',
|
||||
'orphans', 'outlineColor', 'outlineOffset', 'outlineStyle', 'outlineWidth', 'overflowX', 'overflowY',
|
||||
'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop', 'page', 'pageBreakAfter', 'pageBreakBefore',
|
||||
'pageBreakInside', 'pauseAfter', 'pauseBefore', 'pitch', 'pitchRange', 'position', 'quotes',
|
||||
'richness', 'right', 'size', 'speakHeader', 'speakNumeral', 'speakPunctuation', 'speechRate', 'stress',
|
||||
'tableLayout', 'textAlign', 'textDecoration', 'textIndent', 'textShadow', 'textTransform', 'top',
|
||||
'unicodeBidi', 'verticalAlign', 'visibility', 'voiceFamily', 'volume', 'whiteSpace', 'widows',
|
||||
'width', 'wordSpacing', 'zIndex'];
|
||||
|
||||
Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
|
||||
|
||||
String.prototype.parseStyle = function(){
|
||||
var element = Element.extend(document.createElement('div'));
|
||||
element.innerHTML = '<div style="' + this + '"></div>';
|
||||
var style = element.down().style, styleRules = $H();
|
||||
|
||||
Element.CSS_PROPERTIES.each(function(property){
|
||||
if(style[property]) styleRules[property] = style[property];
|
||||
});
|
||||
|
||||
var result = $H();
|
||||
|
||||
styleRules.each(function(pair){
|
||||
var property = pair[0], value = pair[1], unit = null;
|
||||
|
||||
if(value.parseColor('#zzzzzz') != '#zzzzzz') {
|
||||
value = value.parseColor();
|
||||
unit = 'color';
|
||||
} else if(Element.CSS_LENGTH.test(value))
|
||||
var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/),
|
||||
value = parseFloat(components[1]), unit = (components.length == 3) ? components[2] : null;
|
||||
|
||||
result[property.underscore().dasherize()] = $H({ value:value, unit:unit });
|
||||
}.bind(this));
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Element.morph = function(element, style) {
|
||||
new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
|
||||
return element;
|
||||
};
|
||||
|
||||
['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
|
||||
'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each(
|
||||
'collectTextNodes','collectTextNodesIgnoreClass','morph'].each(
|
||||
function(f) { Element.Methods[f] = Element[f]; }
|
||||
);
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,43 +1,64 @@
|
|||
module ActionView
|
||||
module Helpers
|
||||
# Provides methods for converting a number into a formatted string that currently represents
|
||||
# one of the following forms: phone number, percentage, money, or precision level.
|
||||
module Helpers #:nodoc:
|
||||
# Provides methods for converting a numbers into formatted strings.
|
||||
# Methods are provided for phone numbers, currency, percentage,
|
||||
# precision, positional notation, and file size.
|
||||
module NumberHelper
|
||||
|
||||
# Formats a +number+ into a US phone number string. The +options+ can be a hash used to customize the format of the output.
|
||||
# The area code can be surrounded by parentheses by setting +:area_code+ to true; default is false
|
||||
# The delimiter can be set using +:delimiter+; default is "-"
|
||||
# Examples:
|
||||
# number_to_phone(1235551234) => 123-555-1234
|
||||
# number_to_phone(1235551234, {:area_code => true}) => (123) 555-1234
|
||||
# number_to_phone(1235551234, {:delimiter => " "}) => 123 555 1234
|
||||
# number_to_phone(1235551234, {:area_code => true, :extension => 555}) => (123) 555-1234 x 555
|
||||
# Formats a +number+ into a US phone number. You can customize the format
|
||||
# in the +options+ hash.
|
||||
# * <tt>:area_code</tt> - Adds parentheses around the area code.
|
||||
# * <tt>:delimiter</tt> - Specifies the delimiter to use, defaults to "-".
|
||||
# * <tt>:extension</tt> - Specifies an extension to add to the end of the
|
||||
# generated number
|
||||
# * <tt>:country_code</tt> - Sets the country code for the phone number.
|
||||
#
|
||||
# number_to_phone(1235551234) => 123-555-1234
|
||||
# number_to_phone(1235551234, :area_code => true) => (123) 555-1234
|
||||
# number_to_phone(1235551234, :delimiter => " ") => 123 555 1234
|
||||
# number_to_phone(1235551234, :area_code => true, :extension => 555) => (123) 555-1234 x 555
|
||||
# number_to_phone(1235551234, :country_code => 1)
|
||||
def number_to_phone(number, options = {})
|
||||
options = options.stringify_keys
|
||||
area_code = options.delete("area_code") { false }
|
||||
delimiter = options.delete("delimiter") { "-" }
|
||||
extension = options.delete("extension") { "" }
|
||||
number = number.to_s.strip unless number.nil?
|
||||
options = options.stringify_keys
|
||||
area_code = options["area_code"] || nil
|
||||
delimiter = options["delimiter"] || "-"
|
||||
extension = options["extension"].to_s.strip || nil
|
||||
country_code = options["country_code"] || nil
|
||||
|
||||
begin
|
||||
str = area_code == true ? number.to_s.gsub(/([0-9]{3})([0-9]{3})([0-9]{4})/,"(\\1) \\2#{delimiter}\\3") : number.to_s.gsub(/([0-9]{3})([0-9]{3})([0-9]{4})/,"\\1#{delimiter}\\2#{delimiter}\\3")
|
||||
extension.to_s.strip.empty? ? str : "#{str} x #{extension.to_s.strip}"
|
||||
str = ""
|
||||
str << "+#{country_code}#{delimiter}" unless country_code.blank?
|
||||
str << if area_code
|
||||
number.gsub!(/([0-9]{1,3})([0-9]{3})([0-9]{4}$)/,"(\\1) \\2#{delimiter}\\3")
|
||||
else
|
||||
number.gsub!(/([0-9]{1,3})([0-9]{3})([0-9]{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
|
||||
end
|
||||
str << " x #{extension}" unless extension.blank?
|
||||
str
|
||||
rescue
|
||||
number
|
||||
end
|
||||
end
|
||||
|
||||
# Formats a +number+ into a currency string. The +options+ hash can be used to customize the format of the output.
|
||||
# The +number+ can contain a level of precision using the +precision+ key; default is 2
|
||||
# The currency type can be set using the +unit+ key; default is "$"
|
||||
# The unit separator can be set using the +separator+ key; default is "."
|
||||
# The delimiter can be set using the +delimiter+ key; default is ","
|
||||
# Examples:
|
||||
# number_to_currency(1234567890.50) => $1,234,567,890.50
|
||||
# number_to_currency(1234567890.506) => $1,234,567,890.51
|
||||
# number_to_currency(1234567890.50, {:unit => "£", :separator => ",", :delimiter => ""}) => £1234567890,50
|
||||
# Formats a +number+ into a currency string. You can customize the format
|
||||
# in the +options+ hash.
|
||||
# * <tt>:precision</tt> - Sets the level of precision, defaults to 2
|
||||
# * <tt>:unit</tt> - Sets the denomination of the currency, defaults to "$"
|
||||
# * <tt>:separator</tt> - Sets the separator between the units, defaults to "."
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter, defaults to ","
|
||||
#
|
||||
# number_to_currency(1234567890.50) => $1,234,567,890.50
|
||||
# number_to_currency(1234567890.506) => $1,234,567,890.51
|
||||
# number_to_currency(1234567890.506, :precision => 3) => $1,234,567,890.506
|
||||
# number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "")
|
||||
# => £1234567890,50
|
||||
def number_to_currency(number, options = {})
|
||||
options = options.stringify_keys
|
||||
precision, unit, separator, delimiter = options.delete("precision") { 2 }, options.delete("unit") { "$" }, options.delete("separator") { "." }, options.delete("delimiter") { "," }
|
||||
separator = "" unless precision > 0
|
||||
options = options.stringify_keys
|
||||
precision = options["precision"] || 2
|
||||
unit = options["unit"] || "$"
|
||||
separator = precision > 0 ? options["separator"] || "." : ""
|
||||
delimiter = options["delimiter"] || ","
|
||||
|
||||
begin
|
||||
parts = number_with_precision(number, precision).split('.')
|
||||
unit + number_with_delimiter(parts[0], delimiter) + separator + parts[1].to_s
|
||||
|
@ -46,16 +67,19 @@ module ActionView
|
|||
end
|
||||
end
|
||||
|
||||
# Formats a +number+ as into a percentage string. The +options+ hash can be used to customize the format of the output.
|
||||
# The +number+ can contain a level of precision using the +precision+ key; default is 3
|
||||
# The unit separator can be set using the +separator+ key; default is "."
|
||||
# Examples:
|
||||
# number_to_percentage(100) => 100.000%
|
||||
# number_to_percentage(100, {:precision => 0}) => 100%
|
||||
# number_to_percentage(302.0574, {:precision => 2}) => 302.06%
|
||||
# Formats a +number+ as a percentage string. You can customize the
|
||||
# format in the +options+ hash.
|
||||
# * <tt>:precision</tt> - Sets the level of precision, defaults to 3
|
||||
# * <tt>:separator</tt> - Sets the separator between the units, defaults to "."
|
||||
#
|
||||
# number_to_percentage(100) => 100.000%
|
||||
# number_to_percentage(100, {:precision => 0}) => 100%
|
||||
# number_to_percentage(302.0574, {:precision => 2}) => 302.06%
|
||||
def number_to_percentage(number, options = {})
|
||||
options = options.stringify_keys
|
||||
precision, separator = options.delete("precision") { 3 }, options.delete("separator") { "." }
|
||||
options = options.stringify_keys
|
||||
precision = options["precision"] || 3
|
||||
separator = options["separator"] || "."
|
||||
|
||||
begin
|
||||
number = number_with_precision(number, precision)
|
||||
parts = number.split('.')
|
||||
|
@ -69,41 +93,63 @@ module ActionView
|
|||
end
|
||||
end
|
||||
|
||||
# Formats a +number+ with a +delimiter+.
|
||||
# Example:
|
||||
# number_with_delimiter(12345678) => 12,345,678
|
||||
def number_with_delimiter(number, delimiter=",")
|
||||
number.to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
|
||||
# Formats a +number+ with grouped thousands using +delimiter+. You
|
||||
# can customize the format using optional <em>delimiter</em> and <em>separator</em> parameters.
|
||||
# * <tt>delimiter</tt> - Sets the thousands delimiter, defaults to ","
|
||||
# * <tt>separator</tt> - Sets the separator between the units, defaults to "."
|
||||
#
|
||||
# number_with_delimiter(12345678) => 12,345,678
|
||||
# number_with_delimiter(12345678.05) => 12,345,678.05
|
||||
# number_with_delimiter(12345678, ".") => 12.345.678
|
||||
def number_with_delimiter(number, delimiter=",", separator=".")
|
||||
begin
|
||||
parts = number.to_s.split('.')
|
||||
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
|
||||
parts.join separator
|
||||
rescue
|
||||
number
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a formatted-for-humans file size.
|
||||
|
||||
# Formats a +number+ with the specified level of +precision+. The default
|
||||
# level of precision is 3.
|
||||
#
|
||||
# number_with_precision(111.2345) => 111.235
|
||||
# number_with_precision(111.2345, 2) => 111.24
|
||||
def number_with_precision(number, precision=3)
|
||||
"%01.#{precision}f" % number
|
||||
rescue
|
||||
number
|
||||
end
|
||||
|
||||
# Formats the bytes in +size+ into a more understandable representation.
|
||||
# Useful for reporting file sizes to users. This method returns nil if
|
||||
# +size+ cannot be converted into a number. You can change the default
|
||||
# precision of 1 in +precision+.
|
||||
#
|
||||
# Examples:
|
||||
# human_size(123) => 123 Bytes
|
||||
# human_size(1234) => 1.2 KB
|
||||
# human_size(12345) => 12.1 KB
|
||||
# human_size(1234567) => 1.2 MB
|
||||
# human_size(1234567890) => 1.1 GB
|
||||
def number_to_human_size(size)
|
||||
# number_to_human_size(123) => 123 Bytes
|
||||
# number_to_human_size(1234) => 1.2 KB
|
||||
# number_to_human_size(12345) => 12.1 KB
|
||||
# number_to_human_size(1234567) => 1.2 MB
|
||||
# number_to_human_size(1234567890) => 1.1 GB
|
||||
# number_to_human_size(1234567890123) => 1.1 TB
|
||||
# number_to_human_size(1234567, 2) => 1.18 MB
|
||||
def number_to_human_size(size, precision=1)
|
||||
size = Kernel.Float(size)
|
||||
case
|
||||
when size < 1.kilobyte: '%d Bytes' % size
|
||||
when size < 1.megabyte: '%.1f KB' % (size / 1.0.kilobyte)
|
||||
when size < 1.gigabyte: '%.1f MB' % (size / 1.0.megabyte)
|
||||
when size < 1.terabyte: '%.1f GB' % (size / 1.0.gigabyte)
|
||||
else '%.1f TB' % (size / 1.0.terabyte)
|
||||
when size == 1 : "1 Byte"
|
||||
when size < 1.kilobyte: "%d Bytes" % size
|
||||
when size < 1.megabyte: "%.#{precision}f KB" % (size / 1.0.kilobyte)
|
||||
when size < 1.gigabyte: "%.#{precision}f MB" % (size / 1.0.megabyte)
|
||||
when size < 1.terabyte: "%.#{precision}f GB" % (size / 1.0.gigabyte)
|
||||
else "%.#{precision}f TB" % (size / 1.0.terabyte)
|
||||
end.sub('.0', '')
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
alias_method :human_size, :number_to_human_size # deprecated alias
|
||||
|
||||
# Formats a +number+ with a level of +precision+.
|
||||
# Example:
|
||||
# number_with_precision(111.2345) => 111.235
|
||||
def number_with_precision(number, precision=3)
|
||||
sprintf("%01.#{precision}f", number)
|
||||
end
|
||||
deprecate :human_size => :number_to_human_size
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
require File.dirname(__FILE__) + '/javascript_helper'
|
||||
require 'set'
|
||||
|
||||
module ActionView
|
||||
|
@ -39,7 +38,7 @@ module ActionView
|
|||
# XMLHttpRequest. 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
|
||||
# either render_partial or render_partial_collection.
|
||||
# render :partial.
|
||||
#
|
||||
# Examples:
|
||||
# link_to_remote "Delete this post", :update => "posts",
|
||||
|
@ -60,6 +59,12 @@ module ActionView
|
|||
# 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>.
|
||||
#
|
||||
# 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:
|
||||
# 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
|
||||
|
@ -159,15 +164,20 @@ module ActionView
|
|||
#
|
||||
# By default the fall-through action is the same as the one specified in
|
||||
# the :url (and the default method is :post).
|
||||
def form_remote_tag(options = {})
|
||||
#
|
||||
# form_remote_tag also takes a block, like form_tag:
|
||||
# <% form_remote_tag :url => '/posts' do -%>
|
||||
# <div><%= submit_tag 'Save' %></div>
|
||||
# <% end -%>
|
||||
def form_remote_tag(options = {}, &block)
|
||||
options[:form] = true
|
||||
|
||||
options[:html] ||= {}
|
||||
options[:html][:onsubmit] = "#{remote_function(options)}; return false;"
|
||||
options[:html][:action] = options[:html][:action] || url_for(options[:url])
|
||||
options[:html][:method] = options[:html][:method] || "post"
|
||||
options[:html][:onsubmit] =
|
||||
(options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") +
|
||||
"#{remote_function(options)}; return false;"
|
||||
|
||||
tag("form", options[:html], true)
|
||||
form_tag(options[:html].delete(:action) || url_for(options[:url]), options[:html], &block)
|
||||
end
|
||||
|
||||
# Works like form_remote_tag, but uses form_for semantics.
|
||||
|
@ -194,81 +204,6 @@ module ActionView
|
|||
tag("input", options[:html], false)
|
||||
end
|
||||
|
||||
# Returns a JavaScript function (or expression) that'll update a DOM
|
||||
# element according to the options passed.
|
||||
#
|
||||
# * <tt>:content</tt>: The content to use for updating. Can be left out
|
||||
# if using block, see example.
|
||||
# * <tt>:action</tt>: Valid options are :update (assumed by default),
|
||||
# :empty, :remove
|
||||
# * <tt>:position</tt> If the :action is :update, you can optionally
|
||||
# specify one of the following positions: :before, :top, :bottom,
|
||||
# :after.
|
||||
#
|
||||
# Examples:
|
||||
# <%= javascript_tag(update_element_function("products",
|
||||
# :position => :bottom, :content => "<p>New product!</p>")) %>
|
||||
#
|
||||
# <% replacement_function = update_element_function("products") do %>
|
||||
# <p>Product 1</p>
|
||||
# <p>Product 2</p>
|
||||
# <% end %>
|
||||
# <%= javascript_tag(replacement_function) %>
|
||||
#
|
||||
# This method can also be used in combination with remote method call
|
||||
# where the result is evaluated afterwards to cause multiple updates on
|
||||
# a page. Example:
|
||||
#
|
||||
# # Calling view
|
||||
# <%= form_remote_tag :url => { :action => "buy" },
|
||||
# :complete => evaluate_remote_response %>
|
||||
# all the inputs here...
|
||||
#
|
||||
# # Controller action
|
||||
# def buy
|
||||
# @product = Product.find(1)
|
||||
# end
|
||||
#
|
||||
# # Returning view
|
||||
# <%= update_element_function(
|
||||
# "cart", :action => :update, :position => :bottom,
|
||||
# :content => "<p>New Product: #{@product.name}</p>")) %>
|
||||
# <% update_element_function("status", :binding => binding) do %>
|
||||
# You've bought a new product!
|
||||
# <% end %>
|
||||
#
|
||||
# Notice how the second call doesn't need to be in an ERb output block
|
||||
# since it uses a block and passes in the binding to render directly.
|
||||
# This trick will however only work in ERb (not Builder or other
|
||||
# template forms).
|
||||
#
|
||||
# See also JavaScriptGenerator and update_page.
|
||||
def update_element_function(element_id, options = {}, &block)
|
||||
content = escape_javascript(options[:content] || '')
|
||||
content = escape_javascript(capture(&block)) if block
|
||||
|
||||
javascript_function = case (options[:action] || :update)
|
||||
when :update
|
||||
if options[:position]
|
||||
"new Insertion.#{options[:position].to_s.camelize}('#{element_id}','#{content}')"
|
||||
else
|
||||
"$('#{element_id}').innerHTML = '#{content}'"
|
||||
end
|
||||
|
||||
when :empty
|
||||
"$('#{element_id}').innerHTML = ''"
|
||||
|
||||
when :remove
|
||||
"Element.remove('#{element_id}')"
|
||||
|
||||
else
|
||||
raise ArgumentError, "Invalid action, choose one of :update, :remove, :empty"
|
||||
end
|
||||
|
||||
javascript_function << ";\n"
|
||||
options[:binding] ? concat(javascript_function, options[:binding]) : javascript_function
|
||||
end
|
||||
|
||||
# Returns 'eval(request.responseText)' which is the JavaScript function
|
||||
# that form_remote_tag can call in :complete to evaluate a multiple
|
||||
# update return document using update_element_function calls.
|
||||
|
@ -289,7 +224,7 @@ module ActionView
|
|||
javascript_options = options_for_ajax(options)
|
||||
|
||||
update = ''
|
||||
if options[:update] and options[:update].is_a?Hash
|
||||
if options[:update] && options[:update].is_a?(Hash)
|
||||
update = []
|
||||
update << "success:'#{options[:update][:success]}'" if options[:update][:success]
|
||||
update << "failure:'#{options[:update][:failure]}'" if options[:update][:failure]
|
||||
|
@ -303,7 +238,7 @@ module ActionView
|
|||
"new Ajax.Updater(#{update}, "
|
||||
|
||||
url_options = options[:url]
|
||||
url_options = url_options.merge(:escape => false) if url_options.is_a? Hash
|
||||
url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash)
|
||||
function << "'#{url_for(url_options)}'"
|
||||
function << ", #{javascript_options})"
|
||||
|
||||
|
@ -438,7 +373,7 @@ module ActionView
|
|||
if ActionView::Base.debug_rjs
|
||||
source = javascript.dup
|
||||
javascript.replace "try {\n#{source}\n} catch (e) "
|
||||
javascript << "{ alert('RJS error:\\n\\n' + e.toString()); alert('#{source.gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }}'); throw e }"
|
||||
javascript << "{ alert('RJS error:\\n\\n' + e.toString()); alert('#{source.gsub('\\','\0\0').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }}'); throw e }"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -453,6 +388,12 @@ module ActionView
|
|||
JavaScriptElementProxy.new(self, id)
|
||||
end
|
||||
|
||||
# Returns an object whose <tt>#to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
|
||||
# expression as an argument to another JavaScriptGenerator method.
|
||||
def literal(code)
|
||||
ActiveSupport::JSON::Variable.new(code.to_s)
|
||||
end
|
||||
|
||||
# Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
|
||||
# used for further method calls. Examples:
|
||||
#
|
||||
|
@ -526,7 +467,7 @@ module ActionView
|
|||
#
|
||||
# # Replace the DOM element having ID 'person-45' with the
|
||||
# # 'person' partial for the appropriate object.
|
||||
# replace_html 'person-45', :partial => 'person', :object => @person
|
||||
# replace 'person-45', :partial => 'person', :object => @person
|
||||
#
|
||||
# This allows the same partial that is used for the +insert_html+ to
|
||||
# be also used for the input to +replace+ without resorting to
|
||||
|
@ -550,22 +491,22 @@ module ActionView
|
|||
|
||||
# Removes the DOM elements with the given +ids+ from the page.
|
||||
def remove(*ids)
|
||||
record "#{javascript_object_for(ids)}.each(Element.remove)"
|
||||
loop_on_multiple_args 'Element.remove', ids
|
||||
end
|
||||
|
||||
# Shows hidden DOM elements with the given +ids+.
|
||||
def show(*ids)
|
||||
call 'Element.show', *ids
|
||||
loop_on_multiple_args 'Element.show', ids
|
||||
end
|
||||
|
||||
# Hides the visible DOM elements with the given +ids+.
|
||||
def hide(*ids)
|
||||
call 'Element.hide', *ids
|
||||
loop_on_multiple_args 'Element.hide', ids
|
||||
end
|
||||
|
||||
# Toggles the visibility of the DOM elements with the given +ids+.
|
||||
def toggle(*ids)
|
||||
call 'Element.toggle', *ids
|
||||
loop_on_multiple_args 'Element.toggle', ids
|
||||
end
|
||||
|
||||
# Displays an alert dialog with the given +message+.
|
||||
|
@ -573,16 +514,18 @@ module ActionView
|
|||
call 'alert', message
|
||||
end
|
||||
|
||||
# Redirects the browser to the given +location+, in the same form as
|
||||
# +url_for+.
|
||||
# Redirects the browser to the given +location+, in the same form as +url_for+.
|
||||
def redirect_to(location)
|
||||
assign 'window.location.href', @context.url_for(location)
|
||||
end
|
||||
|
||||
# Calls the JavaScript +function+, optionally with the given
|
||||
# +arguments+.
|
||||
def call(function, *arguments)
|
||||
record "#{function}(#{arguments_for_call(arguments)})"
|
||||
# Calls the JavaScript +function+, optionally with the given +arguments+.
|
||||
#
|
||||
# If a block is given, the block will be passed to a new JavaScriptGenerator;
|
||||
# the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt>
|
||||
# and passed as the called function's final argument.
|
||||
def call(function, *arguments, &block)
|
||||
record "#{function}(#{arguments_for_call(arguments, block)})"
|
||||
end
|
||||
|
||||
# Assigns the JavaScript +variable+ the given +value+.
|
||||
|
@ -633,12 +576,18 @@ module ActionView
|
|||
end
|
||||
|
||||
private
|
||||
def loop_on_multiple_args(method, ids)
|
||||
record(ids.size>1 ?
|
||||
"#{javascript_object_for(ids)}.each(#{method})" :
|
||||
"#{method}(#{ids.first.to_json})")
|
||||
end
|
||||
|
||||
def page
|
||||
self
|
||||
end
|
||||
|
||||
def record(line)
|
||||
returning line = "#{line.to_s.chomp.gsub /\;$/, ''};" do
|
||||
returning line = "#{line.to_s.chomp.gsub(/\;\z/, '')};" do
|
||||
self << line
|
||||
end
|
||||
end
|
||||
|
@ -653,10 +602,16 @@ module ActionView
|
|||
object.respond_to?(:to_json) ? object.to_json : object.inspect
|
||||
end
|
||||
|
||||
def arguments_for_call(arguments)
|
||||
def arguments_for_call(arguments, block = nil)
|
||||
arguments << block_to_function(block) if block
|
||||
arguments.map { |argument| javascript_object_for(argument) }.join ', '
|
||||
end
|
||||
|
||||
def block_to_function(block)
|
||||
generator = self.class.new(@context, &block)
|
||||
literal("function() { #{generator.to_s} }")
|
||||
end
|
||||
|
||||
def method_missing(method, *arguments)
|
||||
JavaScriptProxy.new(self, method.to_s.camelize)
|
||||
end
|
||||
|
@ -673,8 +628,11 @@ module ActionView
|
|||
# Works like update_page but wraps the generated JavaScript in a <script>
|
||||
# tag. Use this to include generated JavaScript in an ERb template.
|
||||
# See JavaScriptGenerator for more information.
|
||||
def update_page_tag(&block)
|
||||
javascript_tag update_page(&block)
|
||||
#
|
||||
# +html_options+ may be a hash of <script> attributes to be passed
|
||||
# to ActionView::Helpers::JavaScriptHelper#javascript_tag.
|
||||
def update_page_tag(html_options = {}, &block)
|
||||
javascript_tag update_page(&block), html_options
|
||||
end
|
||||
|
||||
protected
|
||||
|
@ -738,16 +696,16 @@ module ActionView
|
|||
end
|
||||
|
||||
private
|
||||
def method_missing(method, *arguments)
|
||||
def method_missing(method, *arguments, &block)
|
||||
if method.to_s =~ /(.*)=$/
|
||||
assign($1, arguments.first)
|
||||
else
|
||||
call("#{method.to_s.camelize(:lower)}", *arguments)
|
||||
call("#{method.to_s.camelize(:lower)}", *arguments, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def call(function, *arguments)
|
||||
append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments)})")
|
||||
def call(function, *arguments, &block)
|
||||
append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments, block)})")
|
||||
self
|
||||
end
|
||||
|
||||
|
@ -756,7 +714,7 @@ module ActionView
|
|||
end
|
||||
|
||||
def function_chain
|
||||
@function_chain ||= @generator.instance_variable_get("@lines")
|
||||
@function_chain ||= @generator.instance_variable_get(:@lines)
|
||||
end
|
||||
|
||||
def append_to_function_chain!(call)
|
||||
|
@ -771,6 +729,21 @@ module ActionView
|
|||
super(generator, "$(#{id.to_json})")
|
||||
end
|
||||
|
||||
# Allows access of element attributes through +attribute+. Examples:
|
||||
#
|
||||
# page['foo']['style'] # => $('foo').style;
|
||||
# page['foo']['style']['color'] # => $('blank_slate').style.color;
|
||||
# page['foo']['style']['color'] = 'red' # => $('blank_slate').style.color = 'red';
|
||||
# page['foo']['style'].color = 'red' # => $('blank_slate').style.color = 'red';
|
||||
def [](attribute)
|
||||
append_to_function_chain!(attribute)
|
||||
self
|
||||
end
|
||||
|
||||
def []=(variable, value)
|
||||
assign(variable, value)
|
||||
end
|
||||
|
||||
def replace_html(*options_for_render)
|
||||
call 'update', @generator.send(:render, *options_for_render)
|
||||
end
|
||||
|
@ -779,8 +752,8 @@ module ActionView
|
|||
call 'replace', @generator.send(:render, *options_for_render)
|
||||
end
|
||||
|
||||
def reload
|
||||
replace :partial => @id.to_s
|
||||
def reload(options_for_replace = {})
|
||||
replace(options_for_replace.merge({ :partial => @id.to_s }))
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -811,8 +784,8 @@ module ActionView
|
|||
end
|
||||
|
||||
class JavaScriptCollectionProxy < JavaScriptProxy #:nodoc:
|
||||
ENUMERABLE_METHODS_WITH_RETURN = [:all, :any, :collect, :map, :detect, :find, :find_all, :select, :max, :min, :partition, :reject, :sort_by]
|
||||
ENUMERABLE_METHODS = ENUMERABLE_METHODS_WITH_RETURN + [:each]
|
||||
ENUMERABLE_METHODS_WITH_RETURN = [:all, :any, :collect, :map, :detect, :find, :find_all, :select, :max, :min, :partition, :reject, :sort_by] unless defined? ENUMERABLE_METHODS_WITH_RETURN
|
||||
ENUMERABLE_METHODS = ENUMERABLE_METHODS_WITH_RETURN + [:each] unless defined? ENUMERABLE_METHODS
|
||||
attr_reader :generator
|
||||
delegate :arguments_for_call, :to => :generator
|
||||
|
||||
|
@ -899,3 +872,5 @@ module ActionView
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
require File.dirname(__FILE__) + '/javascript_helper'
|
||||
|
|
|
@ -69,6 +69,11 @@ module ActionView
|
|||
# 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.
|
||||
#
|
||||
#
|
||||
# You can change the behaviour with various options, see
|
||||
# http://script.aculo.us for more documentation.
|
||||
def sortable_element(element_id, options = {})
|
||||
|
|
|
@ -2,39 +2,87 @@ require 'cgi'
|
|||
require 'erb'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
# This is poor man's Builder for the rare cases where you need to programmatically make tags but can't use Builder.
|
||||
module Helpers #:nodoc:
|
||||
# Use these methods to generate HTML tags programmatically when you can't use
|
||||
# a Builder. By default, they output XHTML compliant tags.
|
||||
module TagHelper
|
||||
include ERB::Util
|
||||
|
||||
# Examples:
|
||||
# * <tt>tag("br") => <br /></tt>
|
||||
# * <tt>tag("input", { "type" => "text"}) => <input type="text" /></tt>
|
||||
# Returns an empty HTML tag of type +name+ which by default is XHTML
|
||||
# compliant. Setting +open+ to true will create an open tag compatible
|
||||
# with HTML 4.0 and below. Add HTML attributes by passing an attributes
|
||||
# hash to +options+. For attributes with no value like (disabled and
|
||||
# readonly), give it a value of true in the +options+ hash. You can use
|
||||
# symbols or strings for the attribute names.
|
||||
#
|
||||
# tag("br")
|
||||
# # => <br />
|
||||
# tag("br", nil, true)
|
||||
# # => <br>
|
||||
# tag("input", { :type => 'text', :disabled => true })
|
||||
# # => <input type="text" disabled="disabled" />
|
||||
def tag(name, options = nil, open = false)
|
||||
"<#{name}#{tag_options(options.stringify_keys) if options}" + (open ? ">" : " />")
|
||||
"<#{name}#{tag_options(options) if options}" + (open ? ">" : " />")
|
||||
end
|
||||
|
||||
# Examples:
|
||||
# * <tt>content_tag("p", "Hello world!") => <p>Hello world!</p></tt>
|
||||
# * <tt>content_tag("div", content_tag("p", "Hello world!"), "class" => "strong") => </tt>
|
||||
# <tt><div class="strong"><p>Hello world!</p></div></tt>
|
||||
def content_tag(name, content, options = nil)
|
||||
"<#{name}#{tag_options(options.stringify_keys) if options}>#{content}</#{name}>"
|
||||
# Returns an HTML block tag of type +name+ surrounding the +content+. Add
|
||||
# HTML attributes by passing an attributes hash to +options+. For attributes
|
||||
# with no value like (disabled and readonly), give it a value of true in
|
||||
# the +options+ hash. You can use symbols or strings for the attribute names.
|
||||
#
|
||||
# content_tag(:p, "Hello world!")
|
||||
# # => <p>Hello world!</p>
|
||||
# content_tag(:div, content_tag(:p, "Hello world!"), :class => "strong")
|
||||
# # => <div class="strong"><p>Hello world!</p></div>
|
||||
# content_tag("select", options, :multiple => true)
|
||||
# # => <select multiple="multiple">...options...</select>
|
||||
#
|
||||
# Instead of passing the content as an argument, you can also use a block
|
||||
# in which case, you pass your +options+ as the second parameter.
|
||||
#
|
||||
# <% content_tag :div, :class => "strong" do -%>
|
||||
# Hello world!
|
||||
# <% end -%>
|
||||
# # => <div class="strong"><p>Hello world!</p></div>
|
||||
def content_tag(name, content_or_options_with_block = nil, options = nil, &block)
|
||||
if block_given?
|
||||
options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
|
||||
content = capture(&block)
|
||||
concat(content_tag_string(name, content, options), block.binding)
|
||||
else
|
||||
content = content_or_options_with_block
|
||||
content_tag_string(name, content, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a CDATA section for the given +content+. CDATA sections
|
||||
# Returns a CDATA section with the given +content+. CDATA sections
|
||||
# are used to escape blocks of text containing characters which would
|
||||
# otherwise be recognized as markup. CDATA sections begin with the string
|
||||
# <tt><![CDATA[</tt> and end with (and may not contain) the string
|
||||
# <tt>]]></tt>.
|
||||
# <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>.
|
||||
#
|
||||
# cdata_section("<hello world>")
|
||||
# # => <![CDATA[<hello world>]]>
|
||||
def cdata_section(content)
|
||||
"<![CDATA[#{content}]]>"
|
||||
end
|
||||
|
||||
# Returns the escaped +html+ without affecting existing escaped entities.
|
||||
#
|
||||
# escape_once("1 > 2 & 3")
|
||||
# # => "1 < 2 & 3"
|
||||
def escape_once(html)
|
||||
fix_double_escape(html_escape(html.to_s))
|
||||
end
|
||||
|
||||
private
|
||||
def content_tag_string(name, content, options)
|
||||
tag_options = options ? tag_options(options) : ""
|
||||
"<#{name}#{tag_options}>#{content}</#{name}>"
|
||||
end
|
||||
|
||||
def tag_options(options)
|
||||
cleaned_options = convert_booleans(options.stringify_keys.reject {|key, value| value.nil?})
|
||||
' ' + cleaned_options.map {|key, value| %(#{key}="#{html_escape(value.to_s)}")}.sort * ' ' unless cleaned_options.empty?
|
||||
' ' + cleaned_options.map {|key, value| %(#{key}="#{escape_once(value)}")}.sort * ' ' unless cleaned_options.empty?
|
||||
end
|
||||
|
||||
def convert_booleans(options)
|
||||
|
@ -45,6 +93,11 @@ module ActionView
|
|||
def boolean_attribute(options, attribute)
|
||||
options[attribute] ? options[attribute] = attribute : options.delete(attribute)
|
||||
end
|
||||
|
||||
# Fix double-escaped entities, such as &amp;, &#123;, etc.
|
||||
def fix_double_escape(escaped)
|
||||
escaped.gsub(/&([a-z]+|(#\d+));/i) { "&#{$1};" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,65 +2,90 @@ require File.dirname(__FILE__) + '/tag_helper'
|
|||
|
||||
module ActionView
|
||||
module Helpers #:nodoc:
|
||||
# Provides a set of methods for working with text strings that can help unburden the level of inline Ruby code in the
|
||||
# templates. In the example below we iterate over a collection of posts provided to the template and print each title
|
||||
# after making sure it doesn't run longer than 20 characters:
|
||||
# <% for post in @posts %>
|
||||
# Title: <%= truncate(post.title, 20) %>
|
||||
# The TextHelper Module provides a set of methods for filtering, formatting
|
||||
# and transforming strings that can reduce the amount of inline Ruby code in
|
||||
# your views. These helper methods extend ActionView making them callable
|
||||
# within your template files as shown in the following example which truncates
|
||||
# the title of each post to 10 characters.
|
||||
#
|
||||
# <% @posts.each do |post| %>
|
||||
# # post == 'This is my title'
|
||||
# Title: <%= truncate(post.title, 10) %>
|
||||
# <% end %>
|
||||
# => Title: This is my...
|
||||
module TextHelper
|
||||
# The regular puts and print are outlawed in eRuby. It's recommended to use the <%= "hello" %> form instead of print "hello".
|
||||
# If you absolutely must use a method-based output, you can use concat. It's used like this: <% concat "hello", binding %>. Notice that
|
||||
# it doesn't have an equal sign in front. Using <%= concat "hello" %> would result in a double hello.
|
||||
# The preferred method of outputting text in your views is to use the
|
||||
# <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods
|
||||
# do not operate as expected in an eRuby code block. If you absolutely must
|
||||
# output text within a code block, you can use the concat method.
|
||||
#
|
||||
# <% concat "hello", binding %>
|
||||
# is equivalent to using:
|
||||
# <%= "hello" %>
|
||||
def concat(string, binding)
|
||||
eval("_erbout", binding).concat(string)
|
||||
end
|
||||
|
||||
# Truncates +text+ to the length of +length+ and replaces the last three characters with the +truncate_string+
|
||||
# if the +text+ is longer than +length+.
|
||||
# If +text+ is longer than +length+, +text+ will be truncated to the length of
|
||||
# +length+ and the last three characters will be replaced with the +truncate_string+.
|
||||
#
|
||||
# truncate("Once upon a time in a world far far away", 14)
|
||||
# => Once upon a...
|
||||
def truncate(text, length = 30, truncate_string = "...")
|
||||
if text.nil? then return end
|
||||
l = length - truncate_string.length
|
||||
if $KCODE == "NONE"
|
||||
text.length > length ? text[0...l] + truncate_string : text
|
||||
else
|
||||
chars = text.split(//)
|
||||
chars.length > length ? chars[0...l].join + truncate_string : text
|
||||
end
|
||||
l = length - truncate_string.chars.length
|
||||
text.chars.length > length ? text.chars[0...l] + truncate_string : text
|
||||
end
|
||||
|
||||
# Highlights the +phrase+ where it is found in the +text+ by surrounding it like
|
||||
# <strong class="highlight">I'm a highlight phrase</strong>. The highlighter can be specialized by
|
||||
# passing +highlighter+ as single-quoted string with \1 where the phrase is supposed to be inserted.
|
||||
# N.B.: The +phrase+ is sanitized to include only letters, digits, and spaces before use.
|
||||
# Highlights +phrase+ everywhere it is found in +text+ by inserting it into
|
||||
# a +highlighter+ string. The highlighter can be specialized by passing +highlighter+
|
||||
# as a single-quoted string with \1 where the phrase is to be inserted.
|
||||
#
|
||||
# highlight('You searched for: rails', 'rails')
|
||||
# => You searched for: <strong class="highlight">rails</strong>
|
||||
def highlight(text, phrase, highlighter = '<strong class="highlight">\1</strong>')
|
||||
if phrase.blank? then return text end
|
||||
text.gsub(/(#{Regexp.escape(phrase)})/i, highlighter) unless text.nil?
|
||||
end
|
||||
|
||||
# Extracts an excerpt from the +text+ surrounding the +phrase+ with a number of characters on each side determined
|
||||
# by +radius+. If the phrase isn't found, nil is returned. Ex:
|
||||
# excerpt("hello my world", "my", 3) => "...lo my wo..."
|
||||
# Extracts an excerpt from +text+ that matches the first instance of +phrase+.
|
||||
# The +radius+ expands the excerpt on each side of +phrase+ by the number of characters
|
||||
# defined in +radius+. If the excerpt radius overflows the beginning or end of the +text+,
|
||||
# then the +excerpt_string+ will be prepended/appended accordingly. If the +phrase+
|
||||
# isn't found, nil is returned.
|
||||
#
|
||||
# excerpt('This is an example', 'an', 5)
|
||||
# => "...s is an examp..."
|
||||
#
|
||||
# excerpt('This is an example', 'is', 5)
|
||||
# => "This is an..."
|
||||
def excerpt(text, phrase, radius = 100, excerpt_string = "...")
|
||||
if text.nil? || phrase.nil? then return end
|
||||
phrase = Regexp.escape(phrase)
|
||||
|
||||
if found_pos = text =~ /(#{phrase})/i
|
||||
if found_pos = text.chars =~ /(#{phrase})/i
|
||||
start_pos = [ found_pos - radius, 0 ].max
|
||||
end_pos = [ found_pos + phrase.length + radius, text.length ].min
|
||||
end_pos = [ found_pos + phrase.chars.length + radius, text.chars.length ].min
|
||||
|
||||
prefix = start_pos > 0 ? excerpt_string : ""
|
||||
postfix = end_pos < text.length ? excerpt_string : ""
|
||||
postfix = end_pos < text.chars.length ? excerpt_string : ""
|
||||
|
||||
prefix + text[start_pos..end_pos].strip + postfix
|
||||
prefix + text.chars[start_pos..end_pos].strip + postfix
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Attempts to pluralize the +singular+ word unless +count+ is 1. See source for pluralization rules.
|
||||
# Attempts to pluralize the +singular+ word unless +count+ is 1. If +plural+
|
||||
# is supplied, it will use that when count is > 1, if the ActiveSupport Inflector
|
||||
# is loaded, it will use the Inflector to determine the plural form, otherwise
|
||||
# it will just add an 's' to the +singular+ word.
|
||||
#
|
||||
# pluralize(1, 'person') => 1 person
|
||||
# pluralize(2, 'person') => 2 people
|
||||
# pluralize(3, 'person', 'users') => 3 users
|
||||
def pluralize(count, singular, plural = nil)
|
||||
"#{count} " + if count == 1
|
||||
"#{count} " + if count == 1 || count == '1'
|
||||
singular
|
||||
elsif plural
|
||||
plural
|
||||
|
@ -71,7 +96,11 @@ module ActionView
|
|||
end
|
||||
end
|
||||
|
||||
# Word wrap long lines to line_width.
|
||||
# Wraps the +text+ into lines no longer than +line_width+ width. This method
|
||||
# breaks on the first whitespace character that does not exceed +line_width+.
|
||||
#
|
||||
# word_wrap('Once upon a time', 4)
|
||||
# => Once\nupon\na\ntime
|
||||
def word_wrap(text, line_width = 80)
|
||||
text.gsub(/\n/, "\n\n").gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip
|
||||
end
|
||||
|
@ -79,8 +108,9 @@ module ActionView
|
|||
begin
|
||||
require_library_or_gem "redcloth" unless Object.const_defined?(:RedCloth)
|
||||
|
||||
# Returns the text with all the Textile codes turned into HTML-tags.
|
||||
# <i>This method is only available if RedCloth can be required</i>.
|
||||
# Returns the text with all the Textile codes turned into HTML tags.
|
||||
# <i>This method is only available if RedCloth[http://whytheluckystiff.net/ruby/redcloth/]
|
||||
# is available</i>.
|
||||
def textilize(text)
|
||||
if text.blank?
|
||||
""
|
||||
|
@ -91,8 +121,10 @@ module ActionView
|
|||
end
|
||||
end
|
||||
|
||||
# Returns the text with all the Textile codes turned into HTML-tags, but without the regular bounding <p> tag.
|
||||
# <i>This method is only available if RedCloth can be required</i>.
|
||||
# Returns the text with all the Textile codes turned into HTML tags,
|
||||
# but without the bounding <p> tag that RedCloth adds.
|
||||
# <i>This method is only available if RedCloth[http://whytheluckystiff.net/ruby/redcloth/]
|
||||
# is available</i>.
|
||||
def textilize_without_paragraph(text)
|
||||
textiled = textilize(text)
|
||||
if textiled[0..2] == "<p>" then textiled = textiled[3..-1] end
|
||||
|
@ -106,8 +138,9 @@ module ActionView
|
|||
begin
|
||||
require_library_or_gem "bluecloth" unless Object.const_defined?(:BlueCloth)
|
||||
|
||||
# Returns the text with all the Markdown codes turned into HTML-tags.
|
||||
# <i>This method is only available if BlueCloth can be required</i>.
|
||||
# Returns the text with all the Markdown codes turned into HTML tags.
|
||||
# <i>This method is only available if BlueCloth[http://www.deveiate.org/projects/BlueCloth]
|
||||
# is available</i>.
|
||||
def markdown(text)
|
||||
text.blank? ? "" : BlueCloth.new(text).to_html
|
||||
end
|
||||
|
@ -115,29 +148,30 @@ module ActionView
|
|||
# We can't really help what's not there
|
||||
end
|
||||
|
||||
# Returns +text+ transformed into HTML using very simple formatting rules
|
||||
# Surrounds paragraphs with <tt><p></tt> tags, and converts line breaks into <tt><br/></tt>
|
||||
# Two consecutive newlines(<tt>\n\n</tt>) are considered as a paragraph, one newline (<tt>\n</tt>) is
|
||||
# considered a linebreak, three or more consecutive newlines are turned into two newlines
|
||||
# Returns +text+ transformed into HTML using simple formatting rules.
|
||||
# Two or more consecutive newlines(<tt>\n\n</tt>) are considered as a
|
||||
# paragraph and wrapped in <tt><p></tt> tags. One newline (<tt>\n</tt>) is
|
||||
# considered as a linebreak and a <tt><br /></tt> tag is appended. This
|
||||
# method does not remove the newlines from the +text+.
|
||||
def simple_format(text)
|
||||
text.gsub!(/(\r\n|\n|\r)/, "\n") # lets make them newlines crossplatform
|
||||
text.gsub!(/\n\n+/, "\n\n") # zap dupes
|
||||
text.gsub!(/\n\n/, '</p>\0<p>') # turn two newlines into paragraph
|
||||
text.gsub!(/([^\n])(\n)([^\n])/, '\1\2<br />\3') # turn single newline into <br />
|
||||
|
||||
content_tag("p", text)
|
||||
content_tag 'p', text.to_s.
|
||||
gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
|
||||
gsub(/\n\n+/, "</p>\n\n<p>"). # 2+ newline -> paragraph
|
||||
gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
|
||||
end
|
||||
|
||||
# Turns all urls and email addresses into clickable links. The +link+ parameter can limit what should be linked.
|
||||
# Options are <tt>:all</tt> (default), <tt>:email_addresses</tt>, and <tt>:urls</tt>.
|
||||
# Turns all urls and email addresses into clickable links. The +link+ parameter
|
||||
# will limit what should be linked. You can add html attributes to the links using
|
||||
# +href_options+. Options for +link+ are <tt>:all</tt> (default),
|
||||
# <tt>:email_addresses</tt>, and <tt>:urls</tt>.
|
||||
#
|
||||
# Example:
|
||||
# auto_link("Go to http://www.rubyonrails.com and say hello to david@loudthinking.com") =>
|
||||
# Go to <a href="http://www.rubyonrails.com">http://www.rubyonrails.com</a> and
|
||||
# auto_link("Go to http://www.rubyonrails.org and say hello to david@loudthinking.com") =>
|
||||
# Go to <a href="http://www.rubyonrails.org">http://www.rubyonrails.org</a> and
|
||||
# say hello to <a href="mailto:david@loudthinking.com">david@loudthinking.com</a>
|
||||
#
|
||||
# If a block is given, each url and email address is yielded and the
|
||||
# result is used as the link text. Example:
|
||||
# result is used as the link text.
|
||||
#
|
||||
# auto_link(post.body, :all, :target => '_blank') do |text|
|
||||
# truncate(text, 15)
|
||||
# end
|
||||
|
@ -150,9 +184,12 @@ module ActionView
|
|||
end
|
||||
end
|
||||
|
||||
# Turns all links into words, like "<a href="something">else</a>" to "else".
|
||||
# Strips link tags from +text+ leaving just the link label.
|
||||
#
|
||||
# strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>')
|
||||
# => Ruby on Rails
|
||||
def strip_links(text)
|
||||
text.gsub(/<a.*>(.*)<\/a>/m, '\1')
|
||||
text.gsub(/<a\b.*?>(.*?)<\/a>/mi, '\1')
|
||||
end
|
||||
|
||||
# Try to require the html-scanner library
|
||||
|
@ -161,22 +198,26 @@ module ActionView
|
|||
require 'html/node'
|
||||
rescue LoadError
|
||||
# if there isn't a copy installed, use the vendor version in
|
||||
# action controller
|
||||
# ActionController
|
||||
$:.unshift File.join(File.dirname(__FILE__), "..", "..",
|
||||
"action_controller", "vendor", "html-scanner")
|
||||
require 'html/tokenizer'
|
||||
require 'html/node'
|
||||
end
|
||||
|
||||
VERBOTEN_TAGS = %w(form script) unless defined?(VERBOTEN_TAGS)
|
||||
VERBOTEN_TAGS = %w(form script plaintext) unless defined?(VERBOTEN_TAGS)
|
||||
VERBOTEN_ATTRS = /^on/i unless defined?(VERBOTEN_ATTRS)
|
||||
|
||||
# Sanitizes the given HTML by making form and script tags into regular
|
||||
# Sanitizes the +html+ by converting <form> and <script> tags into regular
|
||||
# text, and removing all "onxxx" attributes (so that arbitrary Javascript
|
||||
# cannot be executed). Also removes href attributes that start with
|
||||
# "javascript:".
|
||||
# cannot be executed). It also removes href= and src= attributes that start with
|
||||
# "javascript:". You can modify what gets sanitized by defining VERBOTEN_TAGS
|
||||
# and VERBOTEN_ATTRS before this Module is loaded.
|
||||
#
|
||||
# Returns the sanitized text.
|
||||
# sanitize('<script> do_nasty_stuff() </script>')
|
||||
# => <script> do_nasty_stuff() </script>
|
||||
# sanitize('<a href="javascript: sucker();">Click here for $100</a>')
|
||||
# => <a>Click here for $100</a>
|
||||
def sanitize(html)
|
||||
# only do this if absolutely necessary
|
||||
if html.index("<")
|
||||
|
@ -192,8 +233,8 @@ module ActionView
|
|||
else
|
||||
if node.closing != :close
|
||||
node.attributes.delete_if { |attr,v| attr =~ VERBOTEN_ATTRS }
|
||||
if node.attributes["href"] =~ /^javascript:/i
|
||||
node.attributes.delete "href"
|
||||
%w(href src).each do |attr|
|
||||
node.attributes.delete attr if node.attributes[attr] =~ /^javascript:/i
|
||||
end
|
||||
end
|
||||
node.to_s
|
||||
|
@ -209,11 +250,11 @@ module ActionView
|
|||
html
|
||||
end
|
||||
|
||||
# Strips all HTML tags from the input, including comments. This uses the html-scanner
|
||||
# tokenizer and so it's HTML parsing ability is limited by that of html-scanner.
|
||||
#
|
||||
# Returns the tag free text.
|
||||
def strip_tags(html)
|
||||
# Strips all HTML tags from the +html+, including comments. This uses the
|
||||
# html-scanner tokenizer and so its HTML parsing ability is limited by
|
||||
# that of html-scanner.
|
||||
def strip_tags(html)
|
||||
return html if html.blank?
|
||||
if html.index("<")
|
||||
text = ""
|
||||
tokenizer = HTML::Tokenizer.new(html)
|
||||
|
@ -231,32 +272,33 @@ module ActionView
|
|||
end
|
||||
end
|
||||
|
||||
# Returns a Cycle object whose to_s value cycles through items of an
|
||||
# array every time it is called. This can be used to alternate classes
|
||||
# for table rows:
|
||||
# Creates a Cycle object whose _to_s_ method cycles through elements of an
|
||||
# array every time it is called. This can be used for example, to alternate
|
||||
# classes for table rows:
|
||||
#
|
||||
# <%- for item in @items do -%>
|
||||
# <tr class="<%= cycle("even", "odd") %>">
|
||||
# ... use item ...
|
||||
# <% @items.each do |item| %>
|
||||
# <tr class="<%= cycle("even", "odd") -%>">
|
||||
# <td>item</td>
|
||||
# </tr>
|
||||
# <%- end -%>
|
||||
# <% end %>
|
||||
#
|
||||
# You can use named cycles to prevent clashes in nested loops. You'll
|
||||
# have to reset the inner cycle, manually:
|
||||
# You can use named cycles to allow nesting in loops. Passing a Hash as
|
||||
# the last parameter with a <tt>:name</tt> key will create a named cycle.
|
||||
# You can manually reset a cycle by calling reset_cycle and passing the
|
||||
# name of the cycle.
|
||||
#
|
||||
# <%- for item in @items do -%>
|
||||
# <% @items.each do |item| %>
|
||||
# <tr class="<%= cycle("even", "odd", :name => "row_class")
|
||||
# <td>
|
||||
# <%- for value in item.values do -%>
|
||||
# <span style="color:'<%= cycle("red", "green", "blue"
|
||||
# :name => "colors") %>'">
|
||||
# item
|
||||
# <% item.values.each do |value| %>
|
||||
# <span style="color:<%= cycle("red", "green", "blue", :name => "colors") -%>">
|
||||
# value
|
||||
# </span>
|
||||
# <%- end -%>
|
||||
# <%- reset_cycle("colors") -%>
|
||||
# <% end %>
|
||||
# <% reset_cycle("colors") %>
|
||||
# </td>
|
||||
# </tr>
|
||||
# <%- end -%>
|
||||
# <% end %>
|
||||
def cycle(first_value, *values)
|
||||
if (values.last.instance_of? Hash)
|
||||
params = values.pop
|
||||
|
@ -273,12 +315,11 @@ module ActionView
|
|||
return cycle.to_s
|
||||
end
|
||||
|
||||
# Resets a cycle so that it starts from the first element in the array
|
||||
# the next time it is used.
|
||||
# Resets a cycle so that it starts from the first element the next time
|
||||
# it is called. Pass in +name+ to reset a named cycle.
|
||||
def reset_cycle(name = "default")
|
||||
cycle = get_cycle(name)
|
||||
return if cycle.nil?
|
||||
cycle.reset
|
||||
cycle.reset unless cycle.nil?
|
||||
end
|
||||
|
||||
class Cycle #:nodoc:
|
||||
|
@ -305,42 +346,42 @@ module ActionView
|
|||
# guaranteed to be reset every time a page is rendered, so it
|
||||
# uses an instance variable of ActionView::Base.
|
||||
def get_cycle(name)
|
||||
@_cycles = Hash.new if @_cycles.nil?
|
||||
@_cycles = Hash.new unless defined?(@_cycles)
|
||||
return @_cycles[name]
|
||||
end
|
||||
|
||||
def set_cycle(name, cycle_object)
|
||||
@_cycles = Hash.new if @_cycles.nil?
|
||||
@_cycles = Hash.new unless defined?(@_cycles)
|
||||
@_cycles[name] = cycle_object
|
||||
end
|
||||
|
||||
AUTO_LINK_RE = /
|
||||
( # leading text
|
||||
<\w+.*?>| # leading HTML tag, or
|
||||
[^=!:'"\/]| # leading punctuation, or
|
||||
^ # beginning of line
|
||||
|
||||
AUTO_LINK_RE = %r{
|
||||
( # leading text
|
||||
<\w+.*?>| # leading HTML tag, or
|
||||
[^=!:'"/]| # leading punctuation, or
|
||||
^ # beginning of line
|
||||
)
|
||||
(
|
||||
(?:http[s]?:\/\/)| # protocol spec, or
|
||||
(?:www\.) # www.*
|
||||
(?:https?://)| # protocol spec, or
|
||||
(?:www\.) # www.*
|
||||
)
|
||||
(
|
||||
([\w]+:?[=?&\/.-]?)* # url segment
|
||||
\w+[\/]? # url tail
|
||||
(?:\#\w*)? # trailing anchor
|
||||
[-\w]+ # subdomain or domain
|
||||
(?:\.[-\w]+)* # remaining subdomains or domain
|
||||
(?::\d+)? # port
|
||||
(?:/(?:(?:[~\w\+%-]|(?:[,.;:][^\s$]))+)?)* # path
|
||||
(?:\?[\w\+%&=.;-]+)? # query string
|
||||
(?:\#[\w\-]*)? # trailing anchor
|
||||
)
|
||||
([[:punct:]]|\s|<|$) # trailing text
|
||||
/x unless const_defined?(:AUTO_LINK_RE)
|
||||
([[:punct:]]|\s|<|$) # trailing text
|
||||
}x unless const_defined?(:AUTO_LINK_RE)
|
||||
|
||||
# Turns all urls into clickable links. If a block is given, each url
|
||||
# is yielded and the result is used as the link text. Example:
|
||||
# auto_link_urls(post.body, :all, :target => '_blank') do |text|
|
||||
# truncate(text, 15)
|
||||
# end
|
||||
# is yielded and the result is used as the link text.
|
||||
def auto_link_urls(text, href_options = {})
|
||||
extra_options = tag_options(href_options.stringify_keys) || ""
|
||||
text.gsub(AUTO_LINK_RE) do
|
||||
all, a, b, c, d = $&, $1, $2, $3, $5
|
||||
all, a, b, c, d = $&, $1, $2, $3, $4
|
||||
if a =~ /<a\s/i # don't replace URL's that are already linked
|
||||
all
|
||||
else
|
||||
|
@ -353,10 +394,6 @@ module ActionView
|
|||
|
||||
# Turns all email addresses into clickable links. If a block is given,
|
||||
# each email is yielded and the result is used as the link text.
|
||||
# Example:
|
||||
# auto_link_email_addresses(post.body) do |text|
|
||||
# truncate(text, 15)
|
||||
# end
|
||||
def auto_link_email_addresses(text)
|
||||
text.gsub(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
|
||||
text = $1
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
require File.dirname(__FILE__) + '/javascript_helper'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
# Provides a set of methods for making easy links and getting urls that depend on the controller and action. This means that
|
||||
# you can use the same format for links in the views that you do in the controller. The different methods are even named
|
||||
# synchronously, so link_to uses that same url as is generated by url_for, which again is the same url used for
|
||||
# redirection in redirect_to.
|
||||
module Helpers #:nodoc:
|
||||
# Provides a set of methods for making easy links and getting urls that
|
||||
# depend on the controller and action. This means that you can use the
|
||||
# same format for links in the views that you do in the controller.
|
||||
module UrlHelper
|
||||
include JavaScriptHelper
|
||||
|
||||
# Returns the URL for the set of +options+ provided. This takes the same options
|
||||
# as url_for. For a list, see the documentation for ActionController::Base#url_for.
|
||||
# Note that it'll set :only_path => true so you'll get /controller/action instead of the
|
||||
# http://example.com/controller/action part (makes it harder to parse httpd log files)
|
||||
#
|
||||
# When called from a view, url_for returns an HTML escaped url. If you need an unescaped
|
||||
# url, pass :escape => false to url_for.
|
||||
#
|
||||
# Returns the URL for the set of +options+ provided. This takes the
|
||||
# same options as url_for in action controller. For a list, see the
|
||||
# documentation for ActionController::Base#url_for. Note that it'll
|
||||
# set :only_path => true so you'll get the relative /controller/action
|
||||
# instead of the fully qualified http://example.com/controller/action.
|
||||
#
|
||||
# When called from a view, url_for returns an HTML escaped url. If you
|
||||
# need an unescaped url, pass :escape => false in the +options+.
|
||||
def url_for(options = {}, *parameters_for_method_reference)
|
||||
if options.kind_of? Hash
|
||||
options = { :only_path => true }.update(options.symbolize_keys)
|
||||
|
@ -24,30 +23,46 @@ module ActionView
|
|||
else
|
||||
escape = true
|
||||
end
|
||||
|
||||
url = @controller.send(:url_for, options, *parameters_for_method_reference)
|
||||
escape ? html_escape(url) : url
|
||||
end
|
||||
|
||||
# Creates a link tag of the given +name+ using an URL created by the set of +options+. See the valid options in
|
||||
# the documentation for ActionController::Base#url_for. It's also possible to pass a string instead of an options hash to
|
||||
# get a link tag that just points without consideration. If nil is passed as a name, the link itself will become the name.
|
||||
# Creates a link tag of the given +name+ using a URL created by the set
|
||||
# of +options+. See the valid options in the documentation for
|
||||
# ActionController::Base#url_for. It's also possible to pass a string instead
|
||||
# of an options hash to get a link tag that uses the value of the string as the
|
||||
# href for the link. If nil is passed as a name, the link itself will become
|
||||
# the name.
|
||||
#
|
||||
# The html_options has three special features. One for creating javascript confirm alerts where if you pass :confirm => 'Are you sure?',
|
||||
# the link will be guarded with a JS popup asking that question. If the user accepts, the link is processed, otherwise not.
|
||||
# The +html_options+ will accept a hash of html attributes for the link tag.
|
||||
# It also accepts 3 modifiers that specialize the link behavior.
|
||||
#
|
||||
# Another for creating a popup window, which is done by either passing :popup with true or the options of the window in
|
||||
# Javascript form.
|
||||
# * <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-thru to JavaScripts window.open method.
|
||||
# * <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
|
||||
# in dangerous actions like deleting a record (which search bots can follow
|
||||
# while spidering your site). Supported verbs are :post, :delete and :put.
|
||||
# Note that if the user has JavaScript disabled, the request will fall back
|
||||
# to using GET. If you are relying on the POST behavior, your should check
|
||||
# for it in your controllers action by using the request objects methods
|
||||
# for post?, delete? or put?.
|
||||
#
|
||||
# And a third for making the link do a POST request (instead of the regular GET) through a dynamically added form element that
|
||||
# is instantly submitted. Note that if the user has turned off Javascript, the request will fall back on the GET. So its
|
||||
# your responsibility to determine what the action should be once it arrives at the controller. The POST form is turned on by
|
||||
# passing :post as true. Note, it's not possible to use POST requests and popup targets at the same time (an exception will be thrown).
|
||||
# You can mix and match the +html_options+ with the exception of
|
||||
# :popup and :method which will raise an ActionView::ActionViewError
|
||||
# exception.
|
||||
#
|
||||
# Examples:
|
||||
# link_to "Delete this page", { :action => "destroy", :id => @page.id }, :confirm => "Are you sure?"
|
||||
# link_to "Visit Other Site", "http://www.rubyonrails.org/", :confirm => "Are you sure?"
|
||||
# link_to "Help", { :action => "help" }, :popup => true
|
||||
# link_to "Busy loop", { :action => "busy" }, :popup => ['new_window', 'height=300,width=600']
|
||||
# link_to "Destroy account", { :action => "destroy" }, :confirm => "Are you sure?", :post => true
|
||||
# link_to "View Image", { :action => "view" }, :popup => ['new_window_name', 'height=300,width=600']
|
||||
# link_to "Delete Image", { :action => "delete", :id => @image.id }, :confirm => "Are you sure?", :method => :delete
|
||||
def link_to(name, options = {}, html_options = nil, *parameters_for_method_reference)
|
||||
if html_options
|
||||
html_options = html_options.stringify_keys
|
||||
|
@ -56,76 +71,77 @@ module ActionView
|
|||
else
|
||||
tag_options = nil
|
||||
end
|
||||
|
||||
url = options.is_a?(String) ? options : self.url_for(options, *parameters_for_method_reference)
|
||||
"<a href=\"#{url}\"#{tag_options}>#{name || url}</a>"
|
||||
end
|
||||
|
||||
# Generates a form containing a sole button that submits to the
|
||||
# URL given by _options_. Use this method instead of +link_to+
|
||||
# for actions that do not have the safe HTTP GET semantics
|
||||
# implied by using a hypertext link.
|
||||
# Generates a form containing a single button that submits to the URL created
|
||||
# by the set of +options+. This is the safest method to ensure links that
|
||||
# cause changes to your data are not triggered by search bots or accelerators.
|
||||
# If the HTML button does not work with your layout, you can also consider
|
||||
# using the link_to method with the <tt>:method</tt> modifier as described in
|
||||
# the link_to documentation.
|
||||
#
|
||||
# The parameters are the same as for +link_to+. Any _html_options_
|
||||
# that you pass will be applied to the inner +input+ element.
|
||||
# In particular, pass
|
||||
#
|
||||
# :disabled => true/false
|
||||
# The generated FORM element has a class name of <tt>button-to</tt>
|
||||
# to allow styling of the form itself and its children. You can control
|
||||
# the form submission and input element behavior using +html_options+.
|
||||
# This method accepts the <tt>:method</tt> and <tt>:confirm</tt> modifiers
|
||||
# described in the link_to documentation. If no <tt>:method</tt> modifier
|
||||
# is given, it will default to performing a POST operation. You can also
|
||||
# disable the button by passing <tt>:disabled => true</tt> in +html_options+.
|
||||
#
|
||||
# as part of _html_options_ to control whether the button is
|
||||
# disabled. The generated form element is given the class
|
||||
# 'button-to', to which you can attach CSS styles for display
|
||||
# purposes.
|
||||
# button_to "New", :action => "new"
|
||||
#
|
||||
# Example 1:
|
||||
# Generates the following HTML:
|
||||
#
|
||||
# # inside of controller for "feeds"
|
||||
# button_to "Edit", :action => 'edit', :id => 3
|
||||
#
|
||||
# Generates the following HTML (sans formatting):
|
||||
#
|
||||
# <form method="post" action="/feeds/edit/3" class="button-to">
|
||||
# <div><input value="Edit" type="submit" /></div>
|
||||
# <form method="post" action="/controller/new" class="button-to">
|
||||
# <div><input value="New" type="submit" /></div>
|
||||
# </form>
|
||||
#
|
||||
# Example 2:
|
||||
# If you are using RESTful routes, you can pass the <tt>:method</tt>
|
||||
# to change the HTTP verb used to submit the form.
|
||||
#
|
||||
# button_to "Destroy", { :action => 'destroy', :id => 3 },
|
||||
# :confirm => "Are you sure?"
|
||||
# button_to "Delete Image", { :action => "delete", :id => @image.id },
|
||||
# :confirm => "Are you sure?", :method => :delete
|
||||
#
|
||||
# Generates the following HTML (sans formatting):
|
||||
# Which generates the following HTML:
|
||||
#
|
||||
# <form method="post" action="/feeds/destroy/3" class="button-to">
|
||||
# <div><input onclick="return confirm('Are you sure?');"
|
||||
# value="Destroy" type="submit" />
|
||||
# <form method="post" action="/images/delete/1" class="button-to">
|
||||
# <div>
|
||||
# <input type="hidden" name="_method" value="delete" />
|
||||
# <input onclick="return confirm('Are you sure?');"
|
||||
# value="Delete" type="submit" />
|
||||
# </div>
|
||||
# </form>
|
||||
#
|
||||
# *NOTE*: This method generates HTML code that represents a form.
|
||||
# Forms are "block" content, which means that you should not try to
|
||||
# insert them into your HTML where only inline content is expected.
|
||||
# For example, you can legally insert a form inside of a +div+ or
|
||||
# +td+ element or in between +p+ elements, but not in the middle of
|
||||
# a run of text, nor can you place a form within another form.
|
||||
# (Bottom line: Always validate your HTML before going public.)
|
||||
def button_to(name, options = {}, html_options = nil)
|
||||
html_options = (html_options || {}).stringify_keys
|
||||
def button_to(name, options = {}, html_options = {})
|
||||
html_options = html_options.stringify_keys
|
||||
convert_boolean_attributes!(html_options, %w( disabled ))
|
||||
|
||||
|
||||
method_tag = ''
|
||||
if (method = html_options.delete('method')) && %w{put delete}.include?(method.to_s)
|
||||
method_tag = tag('input', :type => 'hidden', :name => '_method', :value => method.to_s)
|
||||
end
|
||||
|
||||
form_method = method.to_s == 'get' ? 'get' : 'post'
|
||||
|
||||
if confirm = html_options.delete("confirm")
|
||||
html_options["onclick"] = "return #{confirm_javascript_function(confirm)};"
|
||||
end
|
||||
|
||||
url = options.is_a?(String) ? options : url_for(options)
|
||||
name ||= url
|
||||
|
||||
html_options.merge!("type" => "submit", "value" => name)
|
||||
|
||||
"<form method=\"post\" action=\"#{h url}\" class=\"button-to\"><div>" +
|
||||
tag("input", html_options) + "</div></form>"
|
||||
url = options.is_a?(String) ? options : self.url_for(options)
|
||||
name ||= url
|
||||
|
||||
html_options.merge!("type" => "submit", "value" => name)
|
||||
|
||||
"<form method=\"#{form_method}\" action=\"#{escape_once url}\" class=\"button-to\"><div>" +
|
||||
method_tag + tag("input", html_options) + "</div></form>"
|
||||
end
|
||||
|
||||
|
||||
# This tag is deprecated. Combine the link_to and AssetTagHelper::image_tag yourself instead, like:
|
||||
# DEPRECATED. It is reccommended to use the AssetTagHelper::image_tag within
|
||||
# a link_to method to generate a linked image.
|
||||
#
|
||||
# link_to(image_tag("rss", :size => "30x45", :border => 0), "http://www.example.com")
|
||||
def link_image_to(src, options = {}, html_options = {}, *parameters_for_method_reference)
|
||||
image_options = { "src" => src.include?("/") ? src : "/images/#{src}" }
|
||||
|
@ -157,18 +173,42 @@ module ActionView
|
|||
link_to(tag("img", image_options), options, html_options, *parameters_for_method_reference)
|
||||
end
|
||||
|
||||
alias_method :link_to_image, :link_image_to # deprecated name
|
||||
alias_method :link_to_image, :link_image_to
|
||||
deprecate :link_to_image => "use link_to(image_tag(...), url)",
|
||||
:link_image_to => "use link_to(image_tag(...), url)"
|
||||
|
||||
# Creates a link tag of the given +name+ using an URL created by the set of +options+, unless the current
|
||||
# request uri is the same as the link's, in which case only the name is returned (or the
|
||||
# given block is yielded, if one exists). This is useful for creating link bars where you don't want to link
|
||||
# to the page currently being viewed.
|
||||
# Creates a link tag of the given +name+ using a URL created by the set of
|
||||
# +options+ unless the current request uri is the same as the links, in
|
||||
# which case only the name is returned (or the given block is yielded, if
|
||||
# one exists). Refer to the documentation for link_to_unless for block usage.
|
||||
#
|
||||
# <ul id="navbar">
|
||||
# <li><%= link_to_unless_current("Home", { :action => "index" }) %></li>
|
||||
# <li><%= link_to_unless_current("About Us", { :action => "about" }) %></li>
|
||||
# </ul>
|
||||
#
|
||||
# This will render the following HTML when on the about us page:
|
||||
#
|
||||
# <ul id="navbar">
|
||||
# <li><a href="/controller/index">Home</a></li>
|
||||
# <li>About Us</li>
|
||||
# </ul>
|
||||
def link_to_unless_current(name, options = {}, html_options = {}, *parameters_for_method_reference, &block)
|
||||
link_to_unless current_page?(options), name, options, html_options, *parameters_for_method_reference, &block
|
||||
end
|
||||
|
||||
# Create a link tag of the given +name+ using an URL created by the set of +options+, unless +condition+
|
||||
# is true, in which case only the name is returned (or the given block is yielded, if one exists).
|
||||
# Creates a link tag of the given +name+ using a URL created by the set of
|
||||
# +options+ unless +condition+ is true, in which case only the name is
|
||||
# returned. To specialize the default behavior, you can pass a block that
|
||||
# accepts the name or the full argument list for link_to_unless (see the example).
|
||||
#
|
||||
# <%= link_to_unless(@current_user.nil?, "Reply", { :action => "reply" }) %>
|
||||
#
|
||||
# This example uses a block to modify the link if the condition isn't met.
|
||||
#
|
||||
# <%= link_to_unless(@current_user.nil?, "Reply", { :action => "reply" }) do |name|
|
||||
# link_to(name, { :controller => "accounts", :action => "signup" })
|
||||
# end %>
|
||||
def link_to_unless(condition, name, options = {}, html_options = {}, *parameters_for_method_reference, &block)
|
||||
if condition
|
||||
if block_given?
|
||||
|
@ -181,30 +221,56 @@ module ActionView
|
|||
end
|
||||
end
|
||||
|
||||
# Create a link tag of the given +name+ using an URL created by the set of +options+, if +condition+
|
||||
# is true, in which case only the name is returned (or the given block is yielded, if one exists).
|
||||
# Creates a link tag of the given +name+ using a URL created by the set of
|
||||
# +options+ if +condition+ is true, in which case only the name is
|
||||
# returned. To specialize the default behavior, you can pass a block that
|
||||
# accepts the name or the full argument list for link_to_unless (see the examples
|
||||
# in link_to_unless).
|
||||
def link_to_if(condition, name, options = {}, html_options = {}, *parameters_for_method_reference, &block)
|
||||
link_to_unless !condition, name, options, html_options, *parameters_for_method_reference, &block
|
||||
end
|
||||
|
||||
# Creates a link tag for starting an email to the specified <tt>email_address</tt>, which is also used as the name of the
|
||||
# link unless +name+ is specified. Additional HTML options, such as class or id, can be passed in the <tt>html_options</tt> hash.
|
||||
# Creates a mailto link tag to the specified +email_address+, which is
|
||||
# also used as the name of the link unless +name+ is specified. Additional
|
||||
# html attributes for the link can be passed in +html_options+.
|
||||
#
|
||||
# mail_to has several methods for hindering email harvestors and customizing
|
||||
# the email itself by passing special keys to +html_options+.
|
||||
#
|
||||
# Special HTML Options:
|
||||
#
|
||||
# * <tt>:encode</tt> - This key will accept the strings "javascript" or "hex".
|
||||
# Passing "javascript" will dynamically create and encode the mailto: link then
|
||||
# eval it into the DOM of the page. This method will not show the link on
|
||||
# the page if the user has JavaScript disabled. Passing "hex" will hex
|
||||
# encode the +email_address+ before outputting the mailto: link.
|
||||
# * <tt>:replace_at</tt> - When the link +name+ isn't provided, the
|
||||
# +email_address+ is used for the link label. You can use this option to
|
||||
# obfuscate the +email_address+ by substituting the @ sign with the string
|
||||
# given as the value.
|
||||
# * <tt>:replace_dot</tt> - When the link +name+ isn't provided, the
|
||||
# +email_address+ is used for the link label. You can use this option to
|
||||
# obfuscate the +email_address+ by substituting the . in the email with the
|
||||
# string given as the value.
|
||||
# * <tt>:subject</tt> - Preset the subject line of the email.
|
||||
# * <tt>:body</tt> - Preset the body of the email.
|
||||
# * <tt>:cc</tt> - Carbon Copy addition recipients on the email.
|
||||
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
|
||||
#
|
||||
# You can also make it difficult for spiders to harvest email address by obfuscating them.
|
||||
# Examples:
|
||||
# mail_to "me@domain.com" # => <a href="mailto:me@domain.com">me@domain.com</a>
|
||||
# mail_to "me@domain.com", "My email", :encode => "javascript" # =>
|
||||
# <script type="text/javascript" language="javascript">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>
|
||||
# <script type="text/javascript">eval(unescape('%64%6f%63...%6d%65%6e'))</script>
|
||||
#
|
||||
# mail_to "me@domain.com", "My email", :encode => "hex" # =>
|
||||
# <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>
|
||||
#
|
||||
# You can also specify the cc address, bcc address, subject, and body parts of the message header to create a complex e-mail using the
|
||||
# corresponding +cc+, +bcc+, +subject+, and +body+ <tt>html_options</tt> keys. Each of these options are URI escaped and then appended to
|
||||
# the <tt>email_address</tt> before being output. <b>Be aware that javascript keywords will not be escaped and may break this feature
|
||||
# when encoding with javascript.</b>
|
||||
# Examples:
|
||||
# mail_to "me@domain.com", "My email", :cc => "ccaddress@domain.com", :bcc => "bccaddress@domain.com", :subject => "This is an example email", :body => "This is the body of the message." # =>
|
||||
# <a href="mailto:me@domain.com?cc="ccaddress@domain.com"&bcc="bccaddress@domain.com"&body="This%20is%20the%20body%20of%20the%20message."&subject="This%20is%20an%20example%20email">My email</a>
|
||||
# mail_to "me@domain.com", nil, :replace_at => "_at_", :replace_dot => "_dot_", :class => "email" # =>
|
||||
# <a href="mailto:me@domain.com" class="email">me_at_domain_dot_com</a>
|
||||
#
|
||||
# mail_to "me@domain.com", "My email", :cc => "ccaddress@domain.com",
|
||||
# :subject => "This is an example email" # =>
|
||||
# <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a>
|
||||
def mail_to(email_address, name = nil, html_options = {})
|
||||
html_options = html_options.stringify_keys
|
||||
encode = html_options.delete("encode")
|
||||
|
@ -218,17 +284,19 @@ module ActionView
|
|||
extras << "subject=#{CGI.escape(subject).gsub("+", "%20")}&" unless subject.nil?
|
||||
extras = "?" << extras.gsub!(/&?$/,"") unless extras.empty?
|
||||
|
||||
email_address = email_address.to_s
|
||||
|
||||
email_address_obfuscated = email_address.dup
|
||||
email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.has_key?("replace_at")
|
||||
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot")
|
||||
|
||||
if encode == 'javascript'
|
||||
tmp = "document.write('#{content_tag("a", name || email_address, html_options.merge({ "href" => "mailto:"+email_address.to_s+extras }))}');"
|
||||
if encode == "javascript"
|
||||
tmp = "document.write('#{content_tag("a", name || email_address, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');"
|
||||
for i in 0...tmp.length
|
||||
string << sprintf("%%%x",tmp[i])
|
||||
end
|
||||
"<script type=\"text/javascript\">eval(unescape('#{string}'))</script>"
|
||||
elsif encode == 'hex'
|
||||
elsif encode == "hex"
|
||||
for i in 0...email_address.length
|
||||
if email_address[i,1] =~ /\w/
|
||||
string << sprintf("%%%x",email_address[i])
|
||||
|
@ -242,26 +310,42 @@ module ActionView
|
|||
end
|
||||
end
|
||||
|
||||
# Returns true if the current page uri is generated by the options passed (in url_for format).
|
||||
# True if the current request uri was generated by the given +options+.
|
||||
def current_page?(options)
|
||||
CGI.escapeHTML(url_for(options)) == @controller.request.request_uri
|
||||
url_string = CGI.escapeHTML(url_for(options))
|
||||
request = @controller.request
|
||||
if url_string =~ /^\w+:\/\//
|
||||
url_string == "#{request.protocol}#{request.host_with_port}#{request.request_uri}"
|
||||
else
|
||||
url_string == request.request_uri
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def convert_options_to_javascript!(html_options)
|
||||
confirm, popup, post = html_options.delete("confirm"), html_options.delete("popup"), html_options.delete("post")
|
||||
confirm, popup = html_options.delete("confirm"), html_options.delete("popup")
|
||||
|
||||
# post is deprecated, but if its specified and method is not, assume that method = :post
|
||||
method, post = html_options.delete("method"), html_options.delete("post")
|
||||
if !method && post
|
||||
ActiveSupport::Deprecation.warn(
|
||||
"Passing :post as a link modifier is deprecated. " +
|
||||
"Use :method => \"post\" instead. :post will be removed in Rails 2.0."
|
||||
)
|
||||
method = :post
|
||||
end
|
||||
|
||||
html_options["onclick"] = case
|
||||
when popup && post
|
||||
when popup && method
|
||||
raise ActionView::ActionViewError, "You can't use :popup and :post in the same link"
|
||||
when confirm && popup
|
||||
"if (#{confirm_javascript_function(confirm)}) { #{popup_javascript_function(popup)} };return false;"
|
||||
when confirm && post
|
||||
"if (#{confirm_javascript_function(confirm)}) { #{post_javascript_function} };return false;"
|
||||
when confirm && method
|
||||
"if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method)} };return false;"
|
||||
when confirm
|
||||
"return #{confirm_javascript_function(confirm)};"
|
||||
when post
|
||||
"#{post_javascript_function}return false;"
|
||||
when method
|
||||
"#{method_javascript_function(method)}return false;"
|
||||
when popup
|
||||
popup_javascript_function(popup) + 'return false;'
|
||||
else
|
||||
|
@ -277,8 +361,17 @@ module ActionView
|
|||
popup.is_a?(Array) ? "window.open(this.href,'#{popup.first}','#{popup.last}');" : "window.open(this.href);"
|
||||
end
|
||||
|
||||
def post_javascript_function
|
||||
"var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit();"
|
||||
def method_javascript_function(method)
|
||||
submit_function =
|
||||
"var f = document.createElement('form'); f.style.display = 'none'; " +
|
||||
"this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;"
|
||||
|
||||
unless method == :post
|
||||
submit_function << "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); "
|
||||
submit_function << "m.setAttribute('name', '_method'); m.setAttribute('value', '#{method}'); f.appendChild(m);"
|
||||
end
|
||||
|
||||
submit_function << "f.submit();"
|
||||
end
|
||||
|
||||
# Processes the _html_options_ hash, converting the boolean
|
||||
|
|
|
@ -6,16 +6,22 @@ module ActionView
|
|||
|
||||
attr_reader :original_exception
|
||||
|
||||
def initialize(base_path, file_name, assigns, source, original_exception)
|
||||
@base_path, @assigns, @source, @original_exception =
|
||||
base_path, assigns, source, original_exception
|
||||
@file_name = file_name
|
||||
def initialize(base_path, file_path, assigns, source, original_exception)
|
||||
@base_path, @assigns, @source, @original_exception =
|
||||
base_path, assigns.dup, source, original_exception
|
||||
@file_path = file_path
|
||||
|
||||
remove_deprecated_assigns!
|
||||
end
|
||||
|
||||
|
||||
def message
|
||||
original_exception.message
|
||||
ActiveSupport::Deprecation.silence { original_exception.message }
|
||||
end
|
||||
|
||||
|
||||
def clean_backtrace
|
||||
original_exception.clean_backtrace
|
||||
end
|
||||
|
||||
def sub_template_message
|
||||
if @sub_templates
|
||||
"Trace of template inclusion: " +
|
||||
|
@ -24,63 +30,81 @@ module ActionView
|
|||
""
|
||||
end
|
||||
end
|
||||
|
||||
def source_extract(indention = 0)
|
||||
source_code = IO.readlines(@file_name)
|
||||
|
||||
start_on_line = [ line_number - SOURCE_CODE_RADIUS - 1, 0 ].max
|
||||
end_on_line = [ line_number + SOURCE_CODE_RADIUS - 1, source_code.length].min
|
||||
|
||||
def source_extract(indentation = 0)
|
||||
return unless num = line_number
|
||||
num = num.to_i
|
||||
|
||||
source_code = IO.readlines(@file_path)
|
||||
|
||||
start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max
|
||||
end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min
|
||||
|
||||
indent = ' ' * indentation
|
||||
line_counter = start_on_line
|
||||
extract = source_code[start_on_line..end_on_line].collect do |line|
|
||||
|
||||
source_code[start_on_line..end_on_line].sum do |line|
|
||||
line_counter += 1
|
||||
"#{' ' * indention}#{line_counter}: " + line
|
||||
"#{indent}#{line_counter}: #{line}"
|
||||
end
|
||||
|
||||
extract.join
|
||||
end
|
||||
|
||||
def sub_template_of(file_name)
|
||||
def sub_template_of(template_path)
|
||||
@sub_templates ||= []
|
||||
@sub_templates << file_name
|
||||
@sub_templates << template_path
|
||||
end
|
||||
|
||||
|
||||
def line_number
|
||||
if file_name
|
||||
regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/
|
||||
[@original_exception.message, @original_exception.clean_backtrace].flatten.each do |line|
|
||||
return $1.to_i if regexp =~ line
|
||||
@line_number ||=
|
||||
if file_name
|
||||
regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/
|
||||
|
||||
$1 if message =~ regexp or clean_backtrace.find { |line| line =~ regexp }
|
||||
end
|
||||
end
|
||||
0
|
||||
end
|
||||
|
||||
|
||||
def file_name
|
||||
stripped = strip_base_path(@file_name)
|
||||
stripped[0] == ?/ ? stripped[1..-1] : stripped
|
||||
stripped = strip_base_path(@file_path)
|
||||
stripped.slice!(0,1) if stripped[0] == ?/
|
||||
stripped
|
||||
end
|
||||
|
||||
|
||||
def to_s
|
||||
"\n\n#{self.class} (#{message}) on line ##{line_number} of #{file_name}:\n" +
|
||||
source_extract + "\n " +
|
||||
original_exception.clean_backtrace.join("\n ") +
|
||||
"\n\n"
|
||||
"\n\n#{self.class} (#{message}) #{source_location}:\n" +
|
||||
"#{source_extract}\n #{clean_backtrace.join("\n ")}\n\n"
|
||||
end
|
||||
|
||||
def backtrace
|
||||
[
|
||||
"On line ##{line_number} of #{file_name}\n\n#{source_extract(4)}\n " +
|
||||
original_exception.clean_backtrace.join("\n ")
|
||||
[
|
||||
"#{source_location.capitalize}\n\n#{source_extract(4)}\n " +
|
||||
clean_backtrace.join("\n ")
|
||||
]
|
||||
end
|
||||
|
||||
private
|
||||
def strip_base_path(file_name)
|
||||
file_name = File.expand_path(file_name).gsub(/^#{Regexp.escape File.expand_path(RAILS_ROOT)}/, '')
|
||||
file_name.gsub(@base_path, "")
|
||||
def remove_deprecated_assigns!
|
||||
ActionController::Base::DEPRECATED_INSTANCE_VARIABLES.each do |ivar|
|
||||
@assigns.delete(ivar)
|
||||
end
|
||||
end
|
||||
|
||||
def strip_base_path(path)
|
||||
File.expand_path(path).
|
||||
gsub(/^#{Regexp.escape File.expand_path(RAILS_ROOT)}/, '').
|
||||
gsub(@base_path, "")
|
||||
end
|
||||
|
||||
def source_location
|
||||
if line_number
|
||||
"on line ##{line_number} of "
|
||||
else
|
||||
'in '
|
||||
end + file_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Exception::TraceSubstitutions << [/:in\s+`_run_(html|xml).*'\s*$/, ''] if defined?(Exception::TraceSubstitutions)
|
||||
Exception::TraceSubstitutions << [%r{^\s*#{Regexp.escape RAILS_ROOT}}, '#{RAILS_ROOT}'] if defined?(RAILS_ROOT)
|
||||
if defined?(Exception::TraceSubstitutions)
|
||||
Exception::TraceSubstitutions << [/:in\s+`_run_(html|xml).*'\s*$/, '']
|
||||
Exception::TraceSubstitutions << [%r{^\s*#{Regexp.escape RAILS_ROOT}}, '#{RAILS_ROOT}'] if defined?(RAILS_ROOT)
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue