aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/abstract_controller/asset_paths.rb4
-rw-r--r--actionpack/lib/abstract_controller/base.rb2
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb2
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb2
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb3
-rw-r--r--actionpack/lib/action_controller/base.rb4
-rw-r--r--actionpack/lib/action_controller/metal.rb6
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb2
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb20
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb9
-rw-r--r--actionpack/lib/action_controller/railtie.rb1
-rw-r--r--actionpack/lib/action_controller/railties/paths.rb8
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb14
-rw-r--r--actionpack/lib/action_dispatch.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/closed_error.rb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb28
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb68
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb43
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb5
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb204
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb28
-rw-r--r--actionpack/lib/action_view.rb11
-rw-r--r--actionpack/lib/action_view/base.rb47
-rw-r--r--actionpack/lib/action_view/buffers.rb43
-rw-r--r--actionpack/lib/action_view/flows.rb79
-rw-r--r--actionpack/lib/action_view/helpers.rb6
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb3
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb142
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb14
-rw-r--r--actionpack/lib/action_view/helpers/atom_feed_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb23
-rw-r--r--actionpack/lib/action_view/helpers/csrf_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb7
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb119
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb852
-rw-r--r--actionpack/lib/action_view/helpers/scriptaculous_helper.rb263
-rw-r--r--actionpack/lib/action_view/helpers/sprockets_helper.rb85
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb7
-rw-r--r--actionpack/lib/action_view/path_set.rb1
-rw-r--r--actionpack/lib/action_view/railtie.rb2
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb4
-rw-r--r--actionpack/lib/action_view/renderer/streaming_template_renderer.rb129
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb30
-rw-r--r--actionpack/lib/action_view/rendering.rb38
-rw-r--r--actionpack/lib/action_view/template.rb37
-rw-r--r--actionpack/lib/action_view/template/handlers.rb2
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb22
-rw-r--r--actionpack/lib/action_view/template/handlers/rjs.rb13
-rw-r--r--actionpack/lib/action_view/template/resolver.rb2
-rw-r--r--actionpack/lib/action_view/test_case.rb2
-rw-r--r--actionpack/lib/sprockets/railtie.rb56
55 files changed, 775 insertions, 1759 deletions
diff --git a/actionpack/lib/abstract_controller/asset_paths.rb b/actionpack/lib/abstract_controller/asset_paths.rb
index 9ca2fb742f..ad14cd6d87 100644
--- a/actionpack/lib/abstract_controller/asset_paths.rb
+++ b/actionpack/lib/abstract_controller/asset_paths.rb
@@ -3,7 +3,7 @@ module AbstractController
extend ActiveSupport::Concern
included do
- config_accessor :asset_host, :asset_path, :assets_dir, :javascripts_dir, :stylesheets_dir
+ config_accessor :asset_host, :asset_path, :assets_dir, :javascripts_dir, :stylesheets_dir, :use_sprockets
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 07ff5ad9f3..0951267fea 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -1,3 +1,4 @@
+require 'erubis'
require 'active_support/configurable'
require 'active_support/descendants_tracker'
require 'active_support/core_ext/module/anonymous'
@@ -18,6 +19,7 @@ module AbstractController
include ActiveSupport::Configurable
extend ActiveSupport::DescendantsTracker
+ undef_method :not_implemented
class << self
attr_reader :abstract
alias_method :abstract?, :abstract
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 95992c2698..f7b2b7ff53 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -29,7 +29,7 @@ module AbstractController
#
# ==== Options
# * <tt>only</tt> - The callback should be run only for this action
- # * <tt>except<tt> - The callback should be run for all actions except this action
+ # * <tt>except</tt> - The callback should be run for all actions except this action
def _normalize_callback_options(options)
if only = options[:only]
only = Array(only).map {|o| "action_name == '#{o}'"}.join(" || ")
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 4ee54474cc..d1b87b67ee 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -334,7 +334,7 @@ module AbstractController
# ==== Parameters
# * <tt>details</tt> - A list of details to restrict the search by. This
# might include details like the format or locale of the template.
- # * <tt>require_logout</tt> - If this is true, raise an ArgumentError
+ # * <tt>require_layout</tt> - If this is true, raise an ArgumentError
# with details about the fact that the exception could not be
# found (defaults to false)
#
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 691310d5d2..66f6d0eebb 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -1,5 +1,6 @@
require "abstract_controller/base"
require "action_view"
+require "active_support/core_ext/object/instance_variables"
module AbstractController
class DoubleRenderError < Error
@@ -173,7 +174,7 @@ module AbstractController
options[:partial] = action_name
end
- if (options.keys & [:partial, :file, :template, :once]).empty?
+ if (options.keys & [:partial, :file, :template]).empty?
options[:prefixes] ||= _prefixes
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index e6523e56d2..5f9e082cd3 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -105,7 +105,7 @@ module ActionController
# == Renders
#
# Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering
- # of a template. Included in the Action Pack is the Action View, which enables rendering of ERb templates. It's automatically configured.
+ # of a template. Included in the Action Pack is the Action View, which enables rendering of ERB templates. It's automatically configured.
# The controller passes objects to the view by assigning instance variables:
#
# def show
@@ -128,7 +128,7 @@ module ActionController
# end
# end
#
- # Read more about writing ERb and Builder templates in ActionView::Base.
+ # Read more about writing ERB and Builder templates in ActionView::Base.
#
# == Redirects
#
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index e5db31061b..585bd5e5ab 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -201,19 +201,23 @@ module ActionController
class_attribute :middleware_stack
self.middleware_stack = ActionController::MiddlewareStack.new
- def self.inherited(base)
+ def self.inherited(base) #nodoc:
base.middleware_stack = self.middleware_stack.dup
super
end
+ # Adds given middleware class and its args to bottom of middleware_stack
def self.use(*args, &block)
middleware_stack.use(*args, &block)
end
+ # Alias for middleware_stack
def self.middleware
middleware_stack
end
+ # Makes the controller a rack endpoint that points to the action in
+ # the given env's action_dispatch.request.path_parameters key.
def self.call(env)
action(env['action_dispatch.request.path_parameters'][:action]).call(env)
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index b98429792d..1d6df89007 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -67,7 +67,7 @@ module ActionController
# class PostsController < ApplicationController
# REALM = "SuperSecret"
# USERS = {"dhh" => "secret", #plain text password
- # "dap" => Digest:MD5::hexdigest(["dap",REALM,"secret"].join(":")) #ha1 digest password
+ # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":")) #ha1 digest password
#
# before_filter :authenticate, :except => [:index]
#
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 7a8fa7bd86..16d48e4677 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -1,8 +1,9 @@
require 'abstract_controller/collector'
require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/object/inclusion'
module ActionController #:nodoc:
- module MimeResponds #:nodoc:
+ module MimeResponds
extend ActiveSupport::Concern
included do
@@ -32,10 +33,10 @@ module ActionController #:nodoc:
# and all actions except <tt>:edit</tt> respond to <tt>:xml</tt> and
# <tt>:json</tt>.
#
- # respond_to :rjs, :only => :create
+ # respond_to :json, :only => :create
#
# This specifies that the <tt>:create</tt> action and no other responds
- # to <tt>:rjs</tt>.
+ # to <tt>:json</tt>.
def respond_to(*mimes)
options = mimes.extract_options!
@@ -105,8 +106,8 @@ module ActionController #:nodoc:
# end
# end
#
- # If the client wants HTML, we just redirect them back to the person list. If they want Javascript
- # (format.js), then it is an RJS request and we render the RJS template associated with this action.
+ # If the client wants HTML, we just redirect them back to the person list. If they want JavaScript,
+ # then it is an Ajax request and we render the JavaScript template associated with this action.
# Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
# include the person's company in the rendered XML, so you get something like this:
#
@@ -222,6 +223,9 @@ module ActionController #:nodoc:
# is quite simple (it just needs to respond to call), you can even give
# a proc to it.
#
+ # In order to use respond_with, first you need to declare the formats your
+ # controller responds to in the class level with a call to <tt>respond_to</tt>.
+ #
def respond_with(*resources, &block)
raise "In order to use respond_with, first you need to declare the formats your " <<
"controller responds to in the class level" if self.class.mimes_for_respond_to.empty?
@@ -245,9 +249,9 @@ module ActionController #:nodoc:
config = self.class.mimes_for_respond_to[mime]
if config[:except]
- !config[:except].include?(action)
+ !action.in?(config[:except])
elsif config[:only]
- config[:only].include?(action)
+ action.in?(config[:only])
else
true
end
@@ -257,7 +261,7 @@ module ActionController #:nodoc:
# Collects mimes and return the response for the negotiated format. Returns
# nil if :not_acceptable was sent to the client.
#
- def retrieve_response_from_mimes(mimes=nil, &block)
+ def retrieve_response_from_mimes(mimes=nil, &block) #:nodoc:
mimes ||= collect_mimes_from_class_level
collector = Collector.new(mimes) { |options| default_render(options || {}) }
block.call(collector) if block_given?
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 38711c8462..dfda6618e7 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -41,7 +41,7 @@ module ActionController
end
# Hash of available renderers, mapping a renderer name to its proc.
- # Default keys are :json, :js, :xml and :update.
+ # Default keys are :json, :js, :xml.
RENDERERS = {}
# Adds a new renderer to call within controller actions.
@@ -107,12 +107,5 @@ module ActionController
self.content_type ||= Mime::XML
self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
end
-
- add :update do |proc, options|
- view_context = self.view_context
- generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc)
- self.content_type = Mime::JS
- self.response_body = generator.to_s
- end
end
end
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index f0c29825ba..d2ba052c8d 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -4,6 +4,7 @@ require "action_dispatch/railtie"
require "action_view/railtie"
require "abstract_controller/railties/routes_helpers"
require "action_controller/railties/paths"
+require "sprockets/railtie"
module ActionController
class Railtie < Rails::Railtie
diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb
index dce3c2fe88..699c44c62c 100644
--- a/actionpack/lib/action_controller/railties/paths.rb
+++ b/actionpack/lib/action_controller/railties/paths.rb
@@ -16,14 +16,6 @@ module ActionController
if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
klass.helper :all
end
-
- if app.config.serve_static_assets && namespace
- paths = namespace._railtie.config.paths
-
- klass.config.assets_dir = paths["public"].first
- klass.config.javascripts_dir = paths["public/javascripts"].first
- klass.config.stylesheets_dir = paths["public/stylesheets"].first
- end
end
end
end
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index 3de40b0de3..2def78b51a 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -18,18 +18,12 @@ module ActionController
# post = Post.find(params[:id])
# post.destroy
#
- # respond_to do |format|
- # format.html { redirect_to(post) } # Calls polymorphic_url(post) which in turn calls post_url(post)
- # format.js do
- # # Calls: new Effect.fade('post_45');
- # render(:update) { |page| page[post].visual_effect(:fade) }
- # end
- # end
+ # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
# end
#
- # As the example above shows, you can stop caring to a large extent what the actual id of the post is. You just know
- # that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming
- # convention and allows you to write less code if you follow it.
+ # As the example above shows, you can stop caring to a large extent what the actual id of the post is.
+ # You just know that one is being assigned and that the subsequent calls in redirect_to expect that
+ # same naming convention and allows you to write less code if you follow it.
module RecordIdentifier
extend self
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 49971fc9f8..7f972fc281 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -60,6 +60,7 @@ module ActionDispatch
autoload :Static
end
+ autoload :ClosedError, 'action_dispatch/middleware/closed_error'
autoload :MiddlewareStack, 'action_dispatch/middleware/stack'
autoload :Routing
diff --git a/actionpack/lib/action_dispatch/middleware/closed_error.rb b/actionpack/lib/action_dispatch/middleware/closed_error.rb
new file mode 100644
index 0000000000..0a4db47f4b
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/closed_error.rb
@@ -0,0 +1,7 @@
+module ActionDispatch
+ class ClosedError < StandardError #:nodoc:
+ def initialize(kind)
+ super "Cannot modify #{kind} because it was closed. This means it was already streamed back to the client or converted to HTTP headers."
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 7ac608f0a8..24ebb8fed7 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -83,7 +83,7 @@ module ActionDispatch
# Raised when storing more than 4K of session data.
class CookieOverflow < StandardError; end
- class CookieJar < Hash #:nodoc:
+ class CookieJar #:nodoc:
# This regular expression is used to split the levels of a domain.
# The top level domain can be any string without a period or
@@ -115,13 +115,22 @@ module ActionDispatch
@delete_cookies = {}
@host = host
@secure = secure
-
- super()
+ @closed = false
+ @cookies = {}
end
+ attr_reader :closed
+ alias :closed? :closed
+ def close!; @closed = true end
+
# Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
def [](name)
- super(name.to_s)
+ @cookies[name.to_s]
+ end
+
+ def update(other_hash)
+ @cookies.update other_hash
+ self
end
def handle_options(options) #:nodoc:
@@ -145,6 +154,7 @@ module ActionDispatch
# Sets the cookie named +name+. The second argument may be the very cookie
# value, or a hash of options as documented above.
def []=(key, options)
+ raise ClosedError, :cookies if closed?
if options.is_a?(Hash)
options.symbolize_keys!
value = options[:value]
@@ -153,7 +163,7 @@ module ActionDispatch
options = { :value => value }
end
- value = super(key.to_s, value)
+ value = @cookies[key.to_s] = value
handle_options(options)
@@ -170,7 +180,7 @@ module ActionDispatch
handle_options(options)
- value = super(key.to_s)
+ value = @cookies.delete(key.to_s)
@delete_cookies[key] = options
value
end
@@ -225,6 +235,7 @@ module ActionDispatch
end
def []=(key, options)
+ raise ClosedError, :cookies if closed?
if options.is_a?(Hash)
options.symbolize_keys!
else
@@ -263,6 +274,7 @@ module ActionDispatch
end
def []=(key, options)
+ raise ClosedError, :cookies if closed?
if options.is_a?(Hash)
options.symbolize_keys!
options[:value] = @verifier.generate(options[:value])
@@ -305,6 +317,7 @@ module ActionDispatch
end
def call(env)
+ cookie_jar = nil
status, headers, body = @app.call(env)
if cookie_jar = env['action_dispatch.cookies']
@@ -315,6 +328,9 @@ module ActionDispatch
end
[status, headers, body]
+ ensure
+ cookie_jar = ActionDispatch::Request.new(env).cookie_jar unless cookie_jar
+ cookie_jar.close!
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 21aeeb217a..027ff7f8ac 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -43,9 +43,15 @@ module ActionDispatch
class FlashNow #:nodoc:
def initialize(flash)
@flash = flash
+ @closed = false
end
+ attr_reader :closed
+ alias :closed? :closed
+ def close!; @closed = true end
+
def []=(k, v)
+ raise ClosedError, :flash if closed?
@flash[k] = v
@flash.discard(k)
v
@@ -66,27 +72,70 @@ module ActionDispatch
end
end
- class FlashHash < Hash
+ class FlashHash
+ include Enumerable
+
def initialize #:nodoc:
- super
- @used = Set.new
+ @used = Set.new
+ @closed = false
+ @flashes = {}
end
+ attr_reader :closed
+ alias :closed? :closed
+ def close!; @closed = true end
+
def []=(k, v) #:nodoc:
+ raise ClosedError, :flash if closed?
keep(k)
- super
+ @flashes[k] = v
+ end
+
+ def [](k)
+ @flashes[k]
end
def update(h) #:nodoc:
h.keys.each { |k| keep(k) }
- super
+ @flashes.update h
+ self
+ end
+
+ def keys
+ @flashes.keys
+ end
+
+ def key?(name)
+ @flashes.key? name
+ end
+
+ def delete(key)
+ @flashes.delete key
+ self
+ end
+
+ def to_hash
+ @flashes.dup
+ end
+
+ def empty?
+ @flashes.empty?
+ end
+
+ def clear
+ @flashes.clear
+ end
+
+ def each(&block)
+ @flashes.each(&block)
end
alias :merge! :update
def replace(h) #:nodoc:
@used = Set.new
- super
+ @flashes.replace h
+ self
end
# Sets a flash that will not be available to the next action, only to the current.
@@ -100,7 +149,7 @@ module ActionDispatch
#
# Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
def now
- FlashNow.new(self)
+ @now ||= FlashNow.new(self)
end
# Keeps either the entire current flash or a specific flash entry available for the next action:
@@ -184,8 +233,11 @@ module ActionDispatch
session = env['rack.session'] || {}
flash_hash = env['action_dispatch.request.flash_hash']
- if flash_hash && (!flash_hash.empty? || session.key?('flash'))
+ if flash_hash
+ if !flash_hash.empty? || session.key?('flash')
session["flash"] = flash_hash
+ end
+ flash_hash.close!
end
if session.key?('flash') && session['flash'].empty?
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 64d3a87fd0..1a811ce1b1 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -29,7 +29,9 @@ module ActionDispatch
end
def generate_sid
- ActiveSupport::SecureRandom.hex(16)
+ sid = ActiveSupport::SecureRandom.hex(16)
+ sid.encode!('UTF-8') if sid.respond_to?(:encode!)
+ sid
end
protected
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index c57f694c4d..348f7b86b8 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -2,25 +2,23 @@ require 'rack/utils'
module ActionDispatch
class FileHandler
- def initialize(at, root)
- @at, @root = at.chomp('/'), root.chomp('/')
- @compiled_at = @at.blank? ? nil : /^#{Regexp.escape(at)}/
+ def initialize(root)
+ @root = root.chomp('/')
@compiled_root = /^#{Regexp.escape(root)}/
@file_server = ::Rack::File.new(@root)
end
def match?(path)
path = path.dup
- if !@compiled_at || path.sub!(@compiled_at, '')
- full_path = path.empty? ? @root : File.join(@root, ::Rack::Utils.unescape(path))
- paths = "#{full_path}#{ext}"
- matches = Dir[paths]
- match = matches.detect { |m| File.file?(m) }
- if match
- match.sub!(@compiled_root, '')
- match
- end
+ full_path = path.empty? ? @root : File.join(@root, ::Rack::Utils.unescape(path))
+ paths = "#{full_path}#{ext}"
+
+ matches = Dir[paths]
+ match = matches.detect { |m| File.file?(m) }
+ if match
+ match.sub!(@compiled_root, '')
+ match
end
end
@@ -39,9 +37,9 @@ module ActionDispatch
class Static
FILE_METHODS = %w(GET HEAD).freeze
- def initialize(app, roots)
+ def initialize(app, path)
@app = app
- @file_handlers = create_file_handlers(roots)
+ @file_handler = FileHandler.new(path)
end
def call(env)
@@ -49,24 +47,13 @@ module ActionDispatch
method = env['REQUEST_METHOD']
if FILE_METHODS.include?(method)
- @file_handlers.each do |file_handler|
- if match = file_handler.match?(path)
- env["PATH_INFO"] = match
- return file_handler.call(env)
- end
+ if match = @file_handler.match?(path)
+ env["PATH_INFO"] = match
+ return @file_handler.call(env)
end
end
@app.call(env)
end
-
- private
- def create_file_handlers(roots)
- roots = { '' => roots } unless roots.is_a?(Hash)
-
- roots.map do |at, root|
- FileHandler.new(at, root) if File.exist?(root)
- end.compact
- end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 35be0b3a27..a65f6e1fce 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,6 +1,7 @@
require 'erb'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/object/inclusion'
require 'active_support/inflector'
require 'action_dispatch/routing/redirection'
@@ -1345,11 +1346,11 @@ module ActionDispatch
end
def resource_scope? #:nodoc:
- [:resource, :resources].include?(@scope[:scope_level])
+ @scope[:scope_level].in?([:resource, :resources])
end
def resource_method_scope? #:nodoc:
- [:collection, :member, :new].include?(@scope[:scope_level])
+ @scope[:scope_level].in?([:collection, :member, :new])
end
def with_exclusive_scope
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 77a15f3e97..8a04cfa886 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/inclusion'
+
module ActionDispatch
module Assertions
# A small suite of assertions that test responses from \Rails applications.
@@ -33,7 +35,7 @@ module ActionDispatch
def assert_response(type, message = nil)
validate_request!
- if [ :success, :missing, :redirect, :error ].include?(type) && @response.send("#{type}?")
+ if type.in?([:success, :missing, :redirect, :error]) && @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
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index 2b862fb7d6..c67a0664dc 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -1,4 +1,5 @@
require 'action_controller/vendor/html-scanner'
+require 'active_support/core_ext/object/inclusion'
#--
# Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
@@ -18,7 +19,7 @@ module ActionDispatch
# from the response HTML or elements selected by the enclosing assertion.
#
# In addition to HTML responses, you can make the following assertions:
- # * +assert_select_rjs+ - Assertions on HTML content of RJS update and insertion operations.
+ #
# * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions.
# * +assert_select_email+ - Assertions on the HTML body of an e-mail.
#
@@ -79,7 +80,7 @@ module ActionDispatch
return matches
else
- root = response_from_page_or_rjs
+ root = response_from_page
end
case arg
@@ -203,7 +204,7 @@ module ActionDispatch
root.children.concat @selected
else
# Otherwise just operate on the response document.
- root = response_from_page_or_rjs
+ root = response_from_page
end
# First or second argument is the selector: string and we pass
@@ -325,144 +326,6 @@ module ActionDispatch
end
end
- # Selects content from the RJS response.
- #
- # === Narrowing down
- #
- # With no arguments, asserts that one or more elements are updated or
- # inserted by RJS statements.
- #
- # Use the +id+ argument to narrow down the assertion to only statements
- # that update or insert an element with that identifier.
- #
- # Use the first argument to narrow down assertions to only statements
- # of that type. Possible values are <tt>:replace</tt>, <tt>:replace_html</tt>,
- # <tt>:show</tt>, <tt>:hide</tt>, <tt>:toggle</tt>, <tt>:remove</tta>,
- # <tt>:insert_html</tt> and <tt>:redirect</tt>.
- #
- # Use the argument <tt>:insert</tt> followed by an insertion position to narrow
- # down the assertion to only statements that insert elements in that
- # position. Possible values are <tt>:top</tt>, <tt>:bottom</tt>, <tt>:before</tt>
- # and <tt>:after</tt>.
- #
- # Use the argument <tt>:redirect</tt> followed by a path to check that an statement
- # which redirects to the specified path is generated.
- #
- # Using the <tt>:remove</tt> statement, you will be able to pass a block, but it will
- # be ignored as there is no HTML passed for this statement.
- #
- # === Using blocks
- #
- # Without a block, +assert_select_rjs+ merely asserts that the response
- # contains one or more RJS statements that replace or update content.
- #
- # With a block, +assert_select_rjs+ also selects all elements used in
- # these statements and passes them to the block. Nested assertions are
- # supported.
- #
- # Calling +assert_select_rjs+ with no arguments and using nested asserts
- # asserts that the HTML content is returned by one or more RJS statements.
- # Using +assert_select+ directly makes the same assertion on the content,
- # but without distinguishing whether the content is returned in an HTML
- # or JavaScript.
- #
- # ==== Examples
- #
- # # Replacing the element foo.
- # # page.replace 'foo', ...
- # assert_select_rjs :replace, "foo"
- #
- # # Replacing with the chained RJS proxy.
- # # page[:foo].replace ...
- # assert_select_rjs :chained_replace, 'foo'
- #
- # # Inserting into the element bar, top position.
- # assert_select_rjs :insert, :top, "bar"
- #
- # # Remove the element bar
- # assert_select_rjs :remove, "bar"
- #
- # # Changing the element foo, with an image.
- # assert_select_rjs "foo" do
- # assert_select "img[src=/images/logo.gif""
- # end
- #
- # # RJS inserts or updates a list with four items.
- # assert_select_rjs do
- # assert_select "ol>li", 4
- # end
- #
- # # The same, but shorter.
- # assert_select "ol>li", 4
- #
- # # Checking for a redirect.
- # assert_select_rjs :redirect, root_path
- def assert_select_rjs(*args, &block)
- rjs_type = args.first.is_a?(Symbol) ? args.shift : nil
- id = args.first.is_a?(String) ? args.shift : nil
-
- # If the first argument is a symbol, it's the type of RJS statement we're looking
- # for (update, replace, insertion, etc). Otherwise, we're looking for just about
- # any RJS statement.
- if rjs_type
- if rjs_type == :insert
- position = args.shift
- id = args.shift
- insertion = "insert_#{position}".to_sym
- raise ArgumentError, "Unknown RJS insertion type #{position}" unless RJS_STATEMENTS[insertion]
- statement = "(#{RJS_STATEMENTS[insertion]})"
- else
- raise ArgumentError, "Unknown RJS statement type #{rjs_type}" unless RJS_STATEMENTS[rjs_type]
- statement = "(#{RJS_STATEMENTS[rjs_type]})"
- end
- else
- statement = "#{RJS_STATEMENTS[:any]}"
- end
-
- # Next argument we're looking for is the element identifier. If missing, we pick
- # any element, otherwise we replace it in the statement.
- pattern = Regexp.new(
- id ? statement.gsub(RJS_ANY_ID, "\"#{id}\"") : statement
- )
-
- # Duplicate the body since the next step involves destroying it.
- matches = nil
- case rjs_type
- when :remove, :show, :hide, :toggle
- matches = @response.body.match(pattern)
- else
- @response.body.gsub(pattern) do |match|
- html = unescape_rjs(match)
- matches ||= []
- matches.concat HTML::Document.new(html).root.children.select { |n| n.tag? }
- ""
- end
- end
-
- if matches
- assert_block("") { true } # to count the assertion
- if block_given? && !([:remove, :show, :hide, :toggle].include? rjs_type)
- begin
- @selected ||= nil
- in_scope, @selected = @selected, matches
- yield matches
- ensure
- @selected = in_scope
- end
- end
- matches
- else
- # RJS statement not found.
- case rjs_type
- when :remove, :show, :hide, :toggle
- flunk_message = "No RJS statement that #{rjs_type.to_s}s '#{id}' was rendered."
- else
- flunk_message = "No RJS statement that replaces or inserts HTML content."
- end
- flunk args.shift || flunk_message
- end
- end
-
# Extracts the content of an element, treats it as encoded HTML and runs
# nested assertion on it.
#
@@ -562,62 +425,9 @@ module ActionDispatch
end
protected
- RJS_PATTERN_HTML = "\"((\\\\\"|[^\"])*)\""
- RJS_ANY_ID = "\"([^\"])*\""
- RJS_STATEMENTS = {
- :chained_replace => "\\$\\(#{RJS_ANY_ID}\\)\\.replace\\(#{RJS_PATTERN_HTML}\\)",
- :chained_replace_html => "\\$\\(#{RJS_ANY_ID}\\)\\.update\\(#{RJS_PATTERN_HTML}\\)",
- :replace_html => "Element\\.update\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)",
- :replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)",
- :redirect => "window.location.href = #{RJS_ANY_ID}"
- }
- [:remove, :show, :hide, :toggle].each do |action|
- RJS_STATEMENTS[action] = "Element\\.#{action}\\(#{RJS_ANY_ID}\\)"
- end
- RJS_INSERTIONS = ["top", "bottom", "before", "after"]
- RJS_INSERTIONS.each do |insertion|
- RJS_STATEMENTS["insert_#{insertion}".to_sym] = "Element.insert\\(#{RJS_ANY_ID}, \\{ #{insertion}: #{RJS_PATTERN_HTML} \\}\\)"
- end
- RJS_STATEMENTS[:insert_html] = "Element.insert\\(#{RJS_ANY_ID}, \\{ (#{RJS_INSERTIONS.join('|')}): #{RJS_PATTERN_HTML} \\}\\)"
- RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})")
- RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/
-
- # +assert_select+ and +css_select+ call this to obtain the content in the HTML
- # page, or from all the RJS statements, depending on the type of response.
- def response_from_page_or_rjs()
- content_type = @response.content_type
-
- if content_type && Mime::JS =~ content_type
- body = @response.body.dup
- root = HTML::Node.new(nil)
-
- while true
- next if body.sub!(RJS_STATEMENTS[:any]) do |match|
- html = unescape_rjs(match)
- matches = HTML::Document.new(html).root.children.select { |n| n.tag? }
- root.children.concat matches
- ""
- end
- break
- end
-
- root
- else
- html_document.root
- end
- end
-
- # Unescapes a RJS string.
- def unescape_rjs(rjs_string)
- # RJS encodes double quotes and line breaks.
- unescaped= rjs_string.gsub('\"', '"')
- unescaped.gsub!(/\\\//, '/')
- unescaped.gsub!('\n', "\n")
- unescaped.gsub!('\076', '>')
- unescaped.gsub!('\074', '<')
- # RJS encodes non-ascii characters.
- unescaped.gsub!(RJS_PATTERN_UNICODE_ESCAPED_CHAR) {|u| [$1.hex].pack('U*')}
- unescaped
+ # +assert_select+ and +css_select+ call this to obtain the content in the HTML page.
+ def response_from_page
+ html_document.root
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 5c6416a19e..7d707d03a9 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -1,6 +1,7 @@
require 'stringio'
require 'uri'
require 'active_support/core_ext/kernel/singleton_class'
+require 'active_support/core_ext/object/inclusion'
require 'active_support/core_ext/object/try'
require 'rack/test'
require 'test/unit/assertions'
@@ -26,31 +27,31 @@ module ActionDispatch
# object's <tt>@response</tt> instance variable will point to the same
# response object.
#
- # You can also perform POST, PUT, DELETE, and HEAD requests with +post+,
- # +put+, +delete+, and +head+.
+ # 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. See get() for more
+ # 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
+ # 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
+ # 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
+ # 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
@@ -59,7 +60,7 @@ module ActionDispatch
# Performs an XMLHttpRequest request with the given parameters, mirroring
# a request from the Prototype library.
#
- # The request_method is :get, :post, :put, :delete or :head; the
+ # The request_method is +:get+, +:post+, +:put+, +:delete+ or +:head+; the
# parameters are +nil+, a hash, or a url-encoded or multipart string;
# the headers are a hash. Keys are automatically upcased and prefixed
# with 'HTTP_' if not already.
@@ -243,7 +244,8 @@ module ActionDispatch
end
# Performs the actual request.
- def process(method, path, parameters = nil, rack_environment = nil)
+ def process(method, path, parameters = nil, env = nil)
+ env ||= {}
if path =~ %r{://}
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
@@ -259,7 +261,7 @@ module ActionDispatch
hostname, port = host.split(':')
- env = {
+ default_env = {
:method => method,
:params => parameters,
@@ -277,9 +279,7 @@ module ActionDispatch
session = Rack::Test::Session.new(_mock_session)
- (rack_environment || {}).each do |key, value|
- env[key] = value
- end
+ env.reverse_merge!(default_env)
# NOTE: rack-test v0.5 doesn't build a default uri correctly
# Make sure requested path is always a full uri
@@ -321,7 +321,7 @@ module ActionDispatch
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)
+ @html_document = nil unless method.in?(["cookies", "assigns"])
integration_session.__send__(method, *args).tap do
copy_session_variables!
end
@@ -384,7 +384,7 @@ module ActionDispatch
end
end
- # An test that spans multiple controllers and actions,
+ # An integration test spans multiple controllers and actions,
# tying them all together to ensure they work together as expected. It tests
# more completely than either unit or functional tests do, exercising the
# entire stack, from the dispatcher to the database.
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 60665387b6..4547aceb28 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -44,6 +44,7 @@ module ActionView
autoload :AbstractRenderer
autoload :PartialRenderer
autoload :TemplateRenderer
+ autoload :StreamingTemplateRenderer
end
autoload_at "action_view/template/resolver" do
@@ -53,6 +54,16 @@ module ActionView
autoload :FallbackFileSystemResolver
end
+ autoload_at "action_view/buffers" do
+ autoload :OutputBuffer
+ autoload :StreamingBuffer
+ end
+
+ autoload_at "action_view/flows" do
+ autoload :OutputFlow
+ autoload :StreamingFlow
+ end
+
autoload_at "action_view/template/error" do
autoload :MissingTemplate
autoload :ActionViewError
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index ab8c6259c5..9e8a3c51a3 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -8,13 +8,12 @@ require 'action_view/log_subscriber'
module ActionView #:nodoc:
# = Action View Base
#
- # Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
+ # Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
# (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> (or <tt>.rxml</tt>) extension then Jim Weirich's Builder::XmlMarkup library is used.
- # If the template file has a <tt>.rjs</tt> extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.
#
- # == ERb
+ # == ERB
#
- # You trigger ERb by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
+ # You trigger ERB by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
# following loop for names:
#
# <b>Names of all the people</b>
@@ -23,7 +22,7 @@ module ActionView #:nodoc:
# <% end %>
#
# The loop is setup in regular embedding tags <% %> and the name is written using the output embedding tag <%= %>. Note that this
- # is not just a usage suggestion. Regular output functions like print or puts won't work with ERb templates. So this would be wrong:
+ # is not just a usage suggestion. Regular output functions like print or puts won't work with ERB templates. So this would be wrong:
#
# <%# WRONG %>
# Hi, Mr. <% puts "Frodo" %>
@@ -81,7 +80,7 @@ module ActionView #:nodoc:
#
# == Builder
#
- # Builder templates are a more programmatic alternative to ERb. They are especially useful for generating XML content. An XmlMarkup object
+ # Builder templates are a more programmatic alternative to ERB. They are especially useful for generating XML content. An XmlMarkup object
# named +xml+ is automatically made available to templates with a <tt>.builder</tt> extension.
#
# Here are some basic examples:
@@ -131,37 +130,9 @@ module ActionView #:nodoc:
# end
#
# More builder documentation can be found at http://builder.rubyforge.org.
- #
- # == JavaScriptGenerator
- #
- # JavaScriptGenerator templates end in <tt>.rjs</tt>. Unlike conventional templates which are used to
- # render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to
- # modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax
- # and make updates to the page where the request originated from.
- #
- # An instance of the JavaScriptGenerator object named +page+ is automatically made available to your template, which is implicitly wrapped in an ActionView::Helpers::PrototypeHelper#update_page block.
- #
- # When an <tt>.rjs</tt> action is called with +link_to_remote+, the generated JavaScript is automatically evaluated. Example:
- #
- # link_to_remote :url => {:action => 'delete'}
- #
- # The subsequently rendered <tt>delete.rjs</tt> might look like:
- #
- # page.replace_html 'sidebar', :partial => 'sidebar'
- # page.remove "person-#{@person.id}"
- # page.visual_effect :highlight, 'user-list'
- #
- # This refreshes the sidebar, removes a person element and highlights the user list.
- #
- # See the ActionView::Helpers::PrototypeHelper::JavaScriptGenerator::GeneratorMethods documentation for more details.
class Base
include Helpers, Rendering, Partials, ::ERB::Util, Context
- # Specify whether RJS responses should be wrapped in a try/catch block
- # that alert()s the caught exception (and then re-raises it).
- cattr_accessor :debug_rjs
- @@debug_rjs = false
-
# Specify the proc used to decorate input tags that refer to attributes with errors.
cattr_accessor :field_error_proc
@@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
@@ -182,7 +153,7 @@ module ActionView #:nodoc:
end
end
- attr_accessor :_template
+ attr_accessor :_template, :_view_flow
attr_internal :request, :controller, :config, :assigns, :lookup_context
delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context
@@ -210,8 +181,8 @@ module ActionView #:nodoc:
self.helpers = Module.new unless self.class.helpers
@_config = {}
- @_content_for = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
@_virtual_path = nil
+ @_view_flow = OutputFlow.new
@output_buffer = nil
if @_controller = controller
@@ -224,10 +195,6 @@ module ActionView #:nodoc:
@_lookup_context.formats = formats if formats
end
- def store_content_for(key, value)
- @_content_for[key] = value
- end
-
def controller_path
@controller_path ||= controller && controller.controller_path
end
diff --git a/actionpack/lib/action_view/buffers.rb b/actionpack/lib/action_view/buffers.rb
new file mode 100644
index 0000000000..089fc68706
--- /dev/null
+++ b/actionpack/lib/action_view/buffers.rb
@@ -0,0 +1,43 @@
+require 'active_support/core_ext/string/output_safety'
+
+module ActionView
+ class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
+ def initialize(*)
+ super
+ encode! if encoding_aware?
+ end
+
+ def <<(value)
+ super(value.to_s)
+ end
+ alias :append= :<<
+ alias :safe_append= :safe_concat
+ end
+
+ class StreamingBuffer #:nodoc:
+ def initialize(block)
+ @block = block
+ end
+
+ def <<(value)
+ value = value.to_s
+ value = ERB::Util.h(value) unless value.html_safe?
+ @block.call(value)
+ end
+ alias :concat :<<
+ alias :append= :<<
+
+ def safe_concat(value)
+ @block.call(value.to_s)
+ end
+ alias :safe_append= :safe_concat
+
+ def html_safe?
+ true
+ end
+
+ def html_safe
+ self
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/flows.rb b/actionpack/lib/action_view/flows.rb
new file mode 100644
index 0000000000..386a06511f
--- /dev/null
+++ b/actionpack/lib/action_view/flows.rb
@@ -0,0 +1,79 @@
+require 'active_support/core_ext/string/output_safety'
+
+module ActionView
+ class OutputFlow #:nodoc:
+ attr_reader :content
+
+ def initialize
+ @content = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
+ end
+
+ # Called by _layout_for to read stored values.
+ def get(key)
+ @content[key]
+ end
+
+ # Called by each renderer object to set the layout contents.
+ def set(key, value)
+ @content[key] = value
+ end
+
+ # Called by content_for
+ def append(key, value)
+ @content[key] << value
+ end
+
+ # Called by provide
+ def append!(key, value)
+ @content[key] << value
+ end
+ end
+
+ class StreamingFlow < OutputFlow #:nodoc:
+ def initialize(view, fiber)
+ @view = view
+ @parent = nil
+ @child = view.output_buffer
+ @content = view._view_flow.content
+ @fiber = fiber
+ @root = Fiber.current.object_id
+ end
+
+ # Try to get an stored content. If the content
+ # is not available and we are inside the layout
+ # fiber, we set that we are waiting for the given
+ # key and yield.
+ def get(key)
+ return super if @content.key?(key)
+
+ if inside_fiber?
+ view = @view
+
+ begin
+ @waiting_for = key
+ view.output_buffer, @parent = @child, view.output_buffer
+ Fiber.yield
+ ensure
+ @waiting_for = nil
+ view.output_buffer, @child = @parent, view.output_buffer
+ end
+ end
+
+ super
+ end
+
+ # Appends the contents for the given key. This is called
+ # by provides and resumes back to the fiber if it is
+ # the key it is waiting for.
+ def append!(key, value)
+ super
+ @fiber.resume if @waiting_for == key
+ end
+
+ private
+
+ def inside_fiber?
+ Fiber.current.object_id != @root
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
index d338ce616a..205116f610 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -17,11 +17,10 @@ module ActionView #:nodoc:
autoload :FormTagHelper
autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"
autoload :NumberHelper
- autoload :PrototypeHelper
autoload :OutputSafetyHelper
autoload :RecordTagHelper
autoload :SanitizeHelper
- autoload :ScriptaculousHelper
+ autoload :SprocketsHelper
autoload :TagHelper
autoload :TextHelper
autoload :TranslationHelper
@@ -47,11 +46,10 @@ module ActionView #:nodoc:
include FormTagHelper
include JavaScriptHelper
include NumberHelper
- include PrototypeHelper
include OutputSafetyHelper
include RecordTagHelper
include SanitizeHelper
- include ScriptaculousHelper
+ include SprocketsHelper
include TagHelper
include TextHelper
include TranslationHelper
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
index 014a03c54d..1e00fd996b 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
@@ -30,9 +30,6 @@ module ActionView
source = rewrite_extension(source, dir, ext) if ext
source = "/#{dir}/#{source}" unless source[0] == ?/
- if controller.respond_to?(:env) && controller.env["action_dispatch.asset_path"]
- source = rewrite_asset_path(source, controller.env["action_dispatch.asset_path"])
- end
source = rewrite_asset_path(source, config.asset_path)
has_request = controller.respond_to?(:request)
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
index 82bbfcc7d2..ce5a7dc2e5 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
@@ -86,99 +86,119 @@ module ActionView
# javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr
# javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js
def javascript_path(source)
- asset_paths.compute_public_path(source, 'javascripts', 'js')
+ if config.use_sprockets
+ sprockets_javascript_path(source)
+ else
+ asset_paths.compute_public_path(source, 'javascripts', 'js')
+ end
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
- # 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 <tt>public/javascripts</tt> 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
- # <tt>:defaults</tt>, if an <tt>application.js</tt> file exists in
- # <tt>public/javascripts</tt> it will be included as well. You can modify the
- # HTML attributes of the script tag by passing a hash as the last argument.
+ # Returns an HTML script tag for each of the +sources+ provided.
+ #
+ # Sources may be paths to JavaScript files. Relative paths are assumed to be relative
+ # to <tt>public/javascripts</tt>, full paths are assumed to be relative to the document
+ # root. Relative paths are idiomatic, use absolute paths only when needed.
+ #
+ # When passing paths, the ".js" extension is optional.
+ #
+ # To include the default JavaScript expansion pass <tt>:defaults</tt> as source.
+ # By default, <tt>:defaults</tt> loads jQuery. If the application was generated
+ # with "-j prototype" the libraries Prototype and Scriptaculous are loaded instead.
+ # In any case, the defaults can be overridden in <tt>config/application.rb</tt>:
+ #
+ # config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js)
+ #
+ # When using <tt>:defaults</tt>, if an <tt>application.js</tt> file exists in
+ # <tt>public/javascripts</tt> it will be included as well at the end.
+ #
+ # You can modify the HTML attributes of the script tag by passing a hash as the
+ # last argument.
#
# ==== Examples
- # javascript_include_tag "xmlhr" # =>
- # <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script>
+ # javascript_include_tag "xmlhr"
+ # # => <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script>
#
- # javascript_include_tag "xmlhr.js" # =>
- # <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script>
+ # javascript_include_tag "xmlhr.js"
+ # # => <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script>
#
- # javascript_include_tag "common.javascript", "/elsewhere/cools" # =>
- # <script type="text/javascript" src="/javascripts/common.javascript?1284139606"></script>
- # <script type="text/javascript" src="/elsewhere/cools.js?1423139606"></script>
+ # javascript_include_tag "common.javascript", "/elsewhere/cools"
+ # # => <script type="text/javascript" src="/javascripts/common.javascript?1284139606"></script>
+ # # <script type="text/javascript" src="/elsewhere/cools.js?1423139606"></script>
#
- # javascript_include_tag "http://www.railsapplication.com/xmlhr" # =>
- # <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js?1284139606"></script>
+ # javascript_include_tag "http://www.railsapplication.com/xmlhr"
+ # # => <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js?1284139606"></script>
#
- # javascript_include_tag "http://www.railsapplication.com/xmlhr.js" # =>
- # <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js?1284139606"></script>
+ # javascript_include_tag "http://www.railsapplication.com/xmlhr.js"
+ # # => <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js?1284139606"></script>
#
- # javascript_include_tag :defaults # =>
- # <script type="text/javascript" src="/javascripts/prototype.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/effects.js?1284139606"></script>
- # ...
- # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
+ # javascript_include_tag :defaults
+ # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/rails.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
#
# * = The application.js file is only referenced if it exists
#
- # You can also include all javascripts in the +javascripts+ directory using <tt>:all</tt> as the source:
+ # You can also include all JavaScripts in the +javascripts+ directory using <tt>:all</tt> as the source:
#
- # javascript_include_tag :all # =>
- # <script type="text/javascript" src="/javascripts/prototype.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/effects.js?1284139606"></script>
- # ...
- # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script>
+ # javascript_include_tag :all
+ # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/rails.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script>
#
- # Note that the default javascript files will be included first. So Prototype and Scriptaculous are available to
- # all subsequently included files.
+ # Note that your defaults of choice will be included first, so they will be available to all subsequently
+ # included files.
#
- # If you want Rails to search in all the subdirectories under javascripts, you should explicitly set <tt>:recursive</tt>:
+ # If you want Rails to search in all the subdirectories under <tt>public/javascripts</tt>, you should
+ # explicitly set <tt>:recursive</tt>:
#
# javascript_include_tag :all, :recursive => true
#
- # == Caching multiple javascripts into one
+ # == Caching multiple JavaScripts into one
#
- # You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
- # compressed by gzip (leading to faster transfers). Caching will only happen if config.perform_caching
- # is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development
- # environment).
+ # You can also cache multiple JavaScripts into one file, which requires less HTTP connections to download
+ # and can better be compressed by gzip (leading to faster transfers). Caching will only happen if
+ # <tt>config.perform_caching</tt> is set to true (which is the case by default for the Rails
+ # production environment, but not for the development environment).
#
# ==== Examples
- # javascript_include_tag :all, :cache => true # when config.perform_caching is false =>
- # <script type="text/javascript" src="/javascripts/prototype.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/effects.js?1284139606"></script>
- # ...
- # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script>
#
- # javascript_include_tag :all, :cache => true # when config.perform_caching is true =>
- # <script type="text/javascript" src="/javascripts/all.js?1344139789"></script>
+ # # assuming config.perform_caching is false
+ # javascript_include_tag :all, :cache => true
+ # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/rails.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script>
+ #
+ # # assuming config.perform_caching is true
+ # javascript_include_tag :all, :cache => true
+ # # => <script type="text/javascript" src="/javascripts/all.js?1344139789"></script>
#
- # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when config.perform_caching is false =>
- # <script type="text/javascript" src="/javascripts/prototype.js?1284139606"></script>
- # <script type="text/javascript" src="/javascripts/cart.js?1289139157"></script>
- # <script type="text/javascript" src="/javascripts/checkout.js?1299139816"></script>
+ # # assuming config.perform_caching is false
+ # javascript_include_tag "jquery", "cart", "checkout", :cache => "shop"
+ # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script>
+ # # <script type="text/javascript" src="/javascripts/cart.js?1289139157"></script>
+ # # <script type="text/javascript" src="/javascripts/checkout.js?1299139816"></script>
#
- # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when config.perform_caching is true =>
- # <script type="text/javascript" src="/javascripts/shop.js?1299139816"></script>
+ # # assuming config.perform_caching is true
+ # javascript_include_tag "jquery", "cart", "checkout", :cache => "shop"
+ # # => <script type="text/javascript" src="/javascripts/shop.js?1299139816"></script>
#
# The <tt>:recursive</tt> option is also available for caching:
#
# javascript_include_tag :all, :cache => true, :recursive => true
def javascript_include_tag(*sources)
- @javascript_include ||= JavascriptIncludeTag.new(config, asset_paths)
- @javascript_include.include_tag(*sources)
+ if config.use_sprockets
+ sprockets_javascript_include_tag(*sources)
+ else
+ @javascript_include ||= JavascriptIncludeTag.new(config, asset_paths)
+ @javascript_include.include_tag(*sources)
+ end
end
-
end
-
end
end
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
index a48c87b49a..a994afb65e 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
@@ -63,7 +63,11 @@ module ActionView
# stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style
# stylesheet_path "http://www.railsapplication.com/css/style.css" # => http://www.railsapplication.com/css/style.css
def stylesheet_path(source)
- asset_paths.compute_public_path(source, 'stylesheets', 'css')
+ if config.use_sprockets
+ sprockets_stylesheet_path(source)
+ else
+ asset_paths.compute_public_path(source, 'stylesheets', 'css')
+ end
end
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
@@ -136,8 +140,12 @@ module ActionView
# stylesheet_link_tag :all, :concat => true
#
def stylesheet_link_tag(*sources)
- @stylesheet_include ||= StylesheetIncludeTag.new(config, asset_paths)
- @stylesheet_include.include_tag(*sources)
+ if config.use_sprockets
+ sprockets_stylesheet_link_tag(*sources)
+ else
+ @stylesheet_include ||= StylesheetIncludeTag.new(config, asset_paths)
+ @stylesheet_include.include_tag(*sources)
+ end
end
end
diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
index db9d7a08ff..96e5722252 100644
--- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
@@ -4,7 +4,7 @@ module ActionView
# = Action View Atom Feed Helpers
module Helpers #:nodoc:
module AtomFeedHelper
- # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERb or any other
+ # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
# template languages).
#
# Full usage example:
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index c88bd1efd5..0139714240 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -14,7 +14,7 @@ module ActionView
# variable. You can then use this variable anywhere in your templates or layout.
#
# ==== Examples
- # The capture method can be used in ERb templates...
+ # The capture method can be used in ERB templates...
#
# <% @greeting = capture do %>
# Welcome to my shiny new web page! The date and time is
@@ -107,8 +107,8 @@ module ActionView
# <%= javascript_include_tag :defaults %>
# <% end %>
#
- # That will place <tt>script</tt> tags for Prototype, Scriptaculous, and application.js (if it exists)
- # on the page; this technique is useful if you'll only be using these scripts in a few views.
+ # That will place +script+ tags for your default set of JavaScript files on the page;
+ # this technique is useful if you'll only be using these scripts in a few views.
#
# Note that content_for concatenates the blocks it is given for a particular
# identifier in order. For example:
@@ -135,8 +135,19 @@ module ActionView
# for elements that will be fragment cached.
def content_for(name, content = nil, &block)
content = capture(&block) if block_given?
- @_content_for[name] << content if content
- @_content_for[name] unless content
+ result = @_view_flow.append(name, content) if content
+ result unless content
+ end
+
+ # The same as +content_for+ but when used with streaming flushes
+ # straight back to the layout. In other words, if you want to
+ # concatenate several times to the same buffer when rendering a given
+ # template, you should use +content_for+, if not, use +provide+ to tell
+ # the layout to stop looking for more contents.
+ def provide(name, content = nil, &block)
+ content = capture(&block) if block_given?
+ result = @_view_flow.append!(name, content) if content
+ result unless content
end
# content_for? simply checks whether any content has been captured yet using content_for
@@ -158,7 +169,7 @@ module ActionView
# </body>
# </html>
def content_for?(name)
- @_content_for[name].present?
+ @_view_flow.get(name).present?
end
# Use an alternate output buffer for the duration of the block.
diff --git a/actionpack/lib/action_view/helpers/csrf_helper.rb b/actionpack/lib/action_view/helpers/csrf_helper.rb
index 65c8debc76..1f2bc28cac 100644
--- a/actionpack/lib/action_view/helpers/csrf_helper.rb
+++ b/actionpack/lib/action_view/helpers/csrf_helper.rb
@@ -17,10 +17,12 @@ module ActionView
# Note that regular forms generate hidden fields, and that Ajax calls are whitelisted,
# so they do not use these tags.
def csrf_meta_tags
- <<-METAS.strip_heredoc.chomp.html_safe if protect_against_forgery?
- <meta name="csrf-param" content="#{Rack::Utils.escape_html(request_forgery_protection_token)}"/>
- <meta name="csrf-token" content="#{Rack::Utils.escape_html(form_authenticity_token)}"/>
- METAS
+ if protect_against_forgery?
+ [
+ tag('meta', :name => 'csrf-param', :content => request_forgery_protection_token),
+ tag('meta', :name => 'csrf-token', :content => form_authenticity_token)
+ ].join("\n").html_safe
+ end
end
# For backwards compatibility.
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 6cd1565031..313a2591bf 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -26,9 +26,9 @@ module ActionView
# 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
+ # 89 mins, 30 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
+ # 23 hrs, 59 mins, 30 secs <-> 41 hrs, 59 mins, 29 secs # => 1 day
+ # 41 hrs, 59 mins, 30 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 1 sec # => [2..12] months
# 1 yr <-> 1 yr, 3 months # => about 1 year
@@ -89,8 +89,8 @@ module ActionView
when 2..44 then locale.t :x_minutes, :count => distance_in_minutes
when 45..89 then locale.t :about_x_hours, :count => 1
when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
- when 1440..2529 then locale.t :x_days, :count => 1
- when 2530..43199 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
+ when 1440..2519 then locale.t :x_days, :count => 1
+ when 2520..43199 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
when 43200..86399 then locale.t :about_x_months, :count => 1
when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
else
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 9025d9e24c..440acafa88 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -610,10 +610,11 @@ module ActionView
# label(:post, :body)
# # => <label for="post_body">Write your entire text here</label>
#
- # Localization can also be based purely on the translation of the attribute-name like this:
+ # Localization can also be based purely on the translation of the attribute-name
+ # (if you are using ActiveRecord):
#
- # activemodel:
- # attribute:
+ # activerecord:
+ # attributes:
# post:
# cost: "Total cost"
#
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index a19ba7a968..d7228bab67 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -1,42 +1,8 @@
require 'action_view/helpers/tag_helper'
module ActionView
- # = Action View JavaScript Helpers
module Helpers
- # Provides functionality for working with JavaScript in your views.
- #
- # == Ajax, controls and visual effects
- #
- # * For information on using Ajax, see
- # ActionView::Helpers::PrototypeHelper.
- # * For information on using controls and visual effects, see
- # ActionView::Helpers::ScriptaculousHelper.
- #
- # == Including the JavaScript libraries into your pages
- #
- # Rails includes the Prototype JavaScript framework and the Scriptaculous
- # JavaScript controls and visual effects library. If you wish to use
- # these libraries and their helpers (ActionView::Helpers::PrototypeHelper
- # and ActionView::Helpers::ScriptaculousHelper), you must do one of the
- # following:
- #
- # * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD
- # section of your page (recommended): This function will return
- # references to the JavaScript files created by the +rails+ command in
- # your <tt>public/javascripts</tt> directory. Using it is recommended as
- # the browser can then cache the libraries instead of fetching all the
- # functions anew on every request.
- # * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but
- # will only include the Prototype core library, which means you are able
- # to use all basic AJAX functionality. For the Scriptaculous-based
- # JavaScript helpers, like visual effects, autocompletion, drag and drop
- # and so on, you should use the method described above.
- #
- # For documentation on +javascript_include_tag+ see
- # ActionView::Helpers::AssetTagHelper.
module JavaScriptHelper
- include PrototypeHelper
-
JS_ESCAPE_MAP = {
'\\' => '\\\\',
'</' => '<\/',
@@ -96,87 +62,34 @@ module ActionView
"\n//#{cdata_section("\n#{content}\n//")}\n".html_safe
end
- # Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the
- # onclick handler.
- #
- # The first argument +name+ is used as the button's value or display text.
- #
- # The next arguments are optional and may include the javascript function definition and a hash of html_options.
+ # Returns a button whose +onclick+ handler triggers the passed JavaScript.
#
- # The +function+ argument can be omitted in favor of an +update_page+
- # block, which evaluates to a string when the template is rendered
- # (instead of making an Ajax request first).
+ # The helper receives a name, JavaScript code, and an optional hash of HTML options. The
+ # name is used as button label and the JavaScript code goes into its +onclick+ attribute.
+ # If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+.
#
- # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
+ # button_to_function "Greeting", "alert('Hello world!')", :class => "ok"
+ # # => <input class="ok" onclick="alert('Hello world!');" type="button" value="Greeting" />
#
- # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
- #
- # Examples:
- # button_to_function "Greeting", "alert('Hello world!')"
- # button_to_function "Delete", "if (confirm('Really?')) do_delete()"
- # button_to_function "Details" do |page|
- # page[:details].visual_effect :toggle_slide
- # end
- # button_to_function "Details", :class => "details_button" do |page|
- # page[:details].visual_effect :toggle_slide
- # end
- def button_to_function(name, *args, &block)
- html_options = args.extract_options!.symbolize_keys
-
- function = block_given? ? update_page(&block) : args[0] || ''
+ def button_to_function(name, function=nil, html_options={})
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
end
- # Returns a link of the given +name+ that will trigger a JavaScript +function+ using the
- # onclick handler and return false after the fact.
- #
- # The first argument +name+ is used as the link text.
+ # Returns a link whose +onclick+ handler triggers the passed JavaScript.
#
- # The next arguments are optional and may include the javascript function definition and a hash of html_options.
+ # The helper receives a name, JavaScript code, and an optional hash of HTML options. The
+ # name is used as the link text and the JavaScript code goes into the +onclick+ attribute.
+ # If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+. Once all
+ # the JavaScript is set, the helper appends "; return false;".
#
- # The +function+ argument can be omitted in favor of an +update_page+
- # block, which evaluates to a string when the template is rendered
- # (instead of making an Ajax request first).
+ # The +href+ attribute of the tag is set to "#" unles +html_options+ has one.
#
- # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
+ # link_to_function "Greeting", "alert('Hello world!')", :class => "nav_link"
+ # # => <a class="nav_link" href="#" onclick="alert('Hello world!'); return false;">Greeting</a>
#
- # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
- #
- #
- # Examples:
- # link_to_function "Greeting", "alert('Hello world!')"
- # Produces:
- # <a onclick="alert('Hello world!'); return false;" href="#">Greeting</a>
- #
- # link_to_function(image_tag("delete"), "if (confirm('Really?')) do_delete()")
- # Produces:
- # <a onclick="if (confirm('Really?')) do_delete(); return false;" href="#">
- # <img src="/images/delete.png?" alt="Delete"/>
- # </a>
- #
- # link_to_function("Show me more", nil, :id => "more_link") do |page|
- # page[:details].visual_effect :toggle_blind
- # page[:more_link].replace_html "Show me less"
- # end
- # Produces:
- # <a href="#" id="more_link" onclick="try {
- # $(&quot;details&quot;).visualEffect(&quot;toggle_blind&quot;);
- # $(&quot;more_link&quot;).update(&quot;Show me less&quot;);
- # }
- # catch (e) {
- # alert('RJS error:\n\n' + e.toString());
- # alert('$(\&quot;details\&quot;).visualEffect(\&quot;toggle_blind\&quot;);
- # \n$(\&quot;more_link\&quot;).update(\&quot;Show me less\&quot;);');
- # throw e
- # };
- # return false;">Show me more</a>
- #
- def link_to_function(name, *args, &block)
- html_options = args.extract_options!.symbolize_keys
-
- function = block_given? ? update_page(&block) : args[0] || ''
+ def link_to_function(name, function, html_options={})
onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
href = html_options[:href] || '#'
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
deleted file mode 100644
index 18e303778c..0000000000
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ /dev/null
@@ -1,852 +0,0 @@
-require 'set'
-require 'active_support/json'
-require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/string/output_safety'
-
-module ActionView
- # = Action View Prototype Helpers
- module Helpers
- # Prototype[http://www.prototypejs.org/] is a JavaScript library that provides
- # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation,
- # Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php]
- # functionality, and more traditional object-oriented facilities for JavaScript.
- # This module provides a set of helpers to make it more convenient to call
- # functions from Prototype using Rails, including functionality to call remote
- # Rails methods (that is, making a background request to a Rails action) using Ajax.
- # This means that you can call actions in your controllers without
- # reloading the page, but still update certain parts of it using
- # injections into the DOM. A common use case is having a form that adds
- # a new element to a list without reloading the page or updating a shopping
- # cart total when a new item is added.
- #
- # == Usage
- # To be able to use these helpers, you must first include the Prototype
- # JavaScript framework in your pages.
- #
- # javascript_include_tag 'prototype'
- #
- # (See the documentation for
- # ActionView::Helpers::JavaScriptHelper for more information on including
- # this and other JavaScript files in your Rails templates.)
- #
- # Now you're ready to call a remote action either through a link...
- #
- # link_to_remote "Add to cart",
- # :url => { :action => "add", :id => product.id },
- # :update => { :success => "cart", :failure => "error" }
- #
- # ...through a form...
- #
- # <%= form_remote_tag :url => '/shipping' do -%>
- # <div><%= submit_tag 'Recalculate Shipping' %></div>
- # <% end -%>
- #
- # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than
- # are listed here); check out the documentation for each method to find out more about its usage and options.
- #
- # === Common Options
- # See link_to_remote for documentation of options common to all Ajax
- # helpers; any of the options specified by link_to_remote can be used
- # by the other helpers.
- #
- # == Designing your Rails actions for Ajax
- # When building your action handlers (that is, the Rails actions that receive your background requests), it's
- # important to remember a few things. First, whatever your action would normally return to the browser, it will
- # return to the Ajax call. As such, you typically don't want to render with a layout. This call will cause
- # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up.
- # You can turn the layout off on particular actions by doing the following:
- #
- # class SiteController < ActionController::Base
- # layout "standard", :except => [:ajax_method, :more_ajax, :another_ajax]
- # end
- #
- # Optionally, you could do this in the method you wish to lack a layout:
- #
- # render :layout => false
- #
- # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the
- # method that Ajax uses to make background requests) method.
- # def name
- # # Is this an XmlHttpRequest request?
- # if (request.xhr?)
- # render :text => @name.to_s
- # else
- # # No? Then render an action.
- # render :action => 'view_attribute', :attr => @name
- # end
- # end
- #
- # The else clause can be left off and the current action will render with full layout and template. An extension
- # to this solution was posted to Ryan Heneise's blog at ArtOfMission["http://www.artofmission.com/"].
- #
- # layout proc{ |c| c.request.xhr? ? false : "application" }
- #
- # Dropping this in your ApplicationController turns the layout off for every request that is an "xhr" request.
- #
- # If you are just returning a little data or don't want to build a template for your output, you may opt to simply
- # render text output, like this:
- #
- # render :text => 'Return this from my method!'
- #
- # Since whatever the method returns is injected into the DOM, this will simply inject some text (or HTML, if you
- # tell it to). This is usually how small updates, such updating a cart total or a file count, are handled.
- #
- # == Updating multiple elements
- # See JavaScriptGenerator for information on updating multiple elements
- # on the page in an Ajax response.
- module PrototypeHelper
- CALLBACKS = Set.new([ :create, :uninitialized, :loading, :loaded,
- :interactive, :complete, :failure, :success ] +
- (100..599).to_a)
- AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url,
- :asynchronous, :method, :insertion, :position,
- :form, :with, :update, :script, :type ]).merge(CALLBACKS)
-
- # Returns the JavaScript needed for a remote function.
- # See the link_to_remote documentation at https://github.com/rails/prototype_legacy_helper as it takes the same arguments.
- #
- # Example:
- # # Generates: <select id="options" onchange="new Ajax.Updater('options',
- # # '/testing/update_options', {asynchronous:true, evalScripts:true})">
- # <select id="options" onchange="<%= remote_function(:update => "options",
- # :url => { :action => :update_options }) %>">
- # <option value="0">Hello</option>
- # <option value="1">World</option>
- # </select>
- def remote_function(options)
- javascript_options = options_for_ajax(options)
-
- update = ''
- 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]
- update = '{' + update.join(',') + '}'
- elsif options[:update]
- update << "'#{options[:update]}'"
- end
-
- function = update.empty? ?
- "new Ajax.Request(" :
- "new Ajax.Updater(#{update}, "
-
- url_options = options[:url]
- function << "'#{ERB::Util.html_escape(escape_javascript(url_for(url_options)))}'"
- function << ", #{javascript_options})"
-
- function = "#{options[:before]}; #{function}" if options[:before]
- function = "#{function}; #{options[:after]}" if options[:after]
- function = "if (#{options[:condition]}) { #{function}; }" if options[:condition]
- function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm]
-
- return function.html_safe
- end
-
- # All the methods were moved to GeneratorMethods so that
- # #include_helpers_from_context has nothing to overwrite.
- class JavaScriptGenerator #:nodoc:
- def initialize(context, &block) #:nodoc:
- @context, @lines = context, []
- include_helpers_from_context
- @context.with_output_buffer(@lines) do
- @context.instance_exec(self, &block)
- end
- end
-
- private
- def include_helpers_from_context
- extend @context.helpers if @context.respond_to?(:helpers)
- extend GeneratorMethods
- end
-
- # JavaScriptGenerator generates blocks of JavaScript code that allow you
- # to change the content and presentation of multiple DOM elements. Use
- # this in your Ajax response bodies, either in a <tt>\<script></tt> tag
- # or as plain JavaScript sent with a Content-type of "text/javascript".
- #
- # Create new instances with PrototypeHelper#update_page or with
- # ActionController::Base#render, then call +insert_html+, +replace_html+,
- # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in
- # methods on the yielded generator in any order you like to modify the
- # content and appearance of the current page.
- #
- # Example:
- #
- # # Generates:
- # # new Element.insert("list", { bottom: "<li>Some item</li>" });
- # # new Effect.Highlight("list");
- # # ["status-indicator", "cancel-link"].each(Element.hide);
- # update_page do |page|
- # page.insert_html :bottom, 'list', "<li>#{@item.name}</li>"
- # page.visual_effect :highlight, 'list'
- # page.hide 'status-indicator', 'cancel-link'
- # end
- #
- #
- # Helper methods can be used in conjunction with JavaScriptGenerator.
- # When a helper method is called inside an update block on the +page+
- # object, that method will also have access to a +page+ object.
- #
- # Example:
- #
- # module ApplicationHelper
- # def update_time
- # page.replace_html 'time', Time.now.to_s(:db)
- # page.visual_effect :highlight, 'time'
- # end
- # end
- #
- # # Controller action
- # def poll
- # render(:update) { |page| page.update_time }
- # end
- #
- # Calls to JavaScriptGenerator not matching a helper method below
- # generate a proxy to the JavaScript Class named by the method called.
- #
- # Examples:
- #
- # # Generates:
- # # Foo.init();
- # update_page do |page|
- # page.foo.init
- # end
- #
- # # Generates:
- # # Event.observe('one', 'click', function () {
- # # $('two').show();
- # # });
- # update_page do |page|
- # page.event.observe('one', 'click') do |p|
- # p[:two].show
- # end
- # end
- #
- # You can also use PrototypeHelper#update_page_tag instead of
- # PrototypeHelper#update_page to wrap the generated JavaScript in a
- # <tt>\<script></tt> tag.
- module GeneratorMethods
- def to_s #:nodoc:
- (@lines * $/).tap do |javascript|
- 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('\\','\0\0').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }}'); throw e }"
- end
- end
- end
-
- # Returns a element reference by finding it through +id+ in the DOM. This element can then be
- # used for further method calls. Examples:
- #
- # page['blank_slate'] # => $('blank_slate');
- # page['blank_slate'].show # => $('blank_slate').show();
- # page['blank_slate'].show('first').up # => $('blank_slate').show('first').up();
- #
- # You can also pass in a record, which will use ActionController::RecordIdentifier.dom_id to lookup
- # the correct id:
- #
- # page[@post] # => $('post_45')
- # page[Post.new] # => $('new_post')
- def [](id)
- case id
- when String, Symbol, NilClass
- JavaScriptElementProxy.new(self, id)
- else
- JavaScriptElementProxy.new(self, ActionController::RecordIdentifier.dom_id(id))
- end
- 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:
- #
- # page.select('p') # => $$('p');
- # page.select('p.welcome b').first # => $$('p.welcome b').first();
- # page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
- #
- # You can also use prototype enumerations with the collection. Observe:
- #
- # # Generates: $$('#items li').each(function(value) { value.hide(); });
- # page.select('#items li').each do |value|
- # value.hide
- # end
- #
- # Though you can call the block param anything you want, they are always rendered in the
- # javascript as 'value, index.' Other enumerations, like collect() return the last statement:
- #
- # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
- # page.select('#items li').collect('hidden') do |item|
- # item.hide
- # end
- #
- def select(pattern)
- JavaScriptElementCollectionProxy.new(self, pattern)
- end
-
- # Inserts HTML at the specified +position+ relative to the DOM element
- # identified by the given +id+.
- #
- # +position+ may be one of:
- #
- # <tt>:top</tt>:: HTML is inserted inside the element, before the
- # element's existing content.
- # <tt>:bottom</tt>:: HTML is inserted inside the element, after the
- # element's existing content.
- # <tt>:before</tt>:: HTML is inserted immediately preceding the element.
- # <tt>:after</tt>:: HTML is inserted immediately following the element.
- #
- # +options_for_render+ may be either a string of HTML to insert, or a hash
- # of options to be passed to ActionView::Base#render. For example:
- #
- # # Insert the rendered 'navigation' partial just before the DOM
- # # element with ID 'content'.
- # # Generates: Element.insert("content", { before: "-- Contents of 'navigation' partial --" });
- # page.insert_html :before, 'content', :partial => 'navigation'
- #
- # # Add a list item to the bottom of the <ul> with ID 'list'.
- # # Generates: Element.insert("list", { bottom: "<li>Last item</li>" });
- # page.insert_html :bottom, 'list', '<li>Last item</li>'
- #
- def insert_html(position, id, *options_for_render)
- content = javascript_object_for(render(*options_for_render))
- record "Element.insert(\"#{id}\", { #{position.to_s.downcase}: #{content} });"
- end
-
- # Replaces the inner HTML of the DOM element with the given +id+.
- #
- # +options_for_render+ may be either a string of HTML to insert, or a hash
- # of options to be passed to ActionView::Base#render. For example:
- #
- # # Replace the HTML of the DOM element having ID 'person-45' with the
- # # 'person' partial for the appropriate object.
- # # Generates: Element.update("person-45", "-- Contents of 'person' partial --");
- # page.replace_html 'person-45', :partial => 'person', :object => @person
- #
- def replace_html(id, *options_for_render)
- call 'Element.update', id, render(*options_for_render)
- end
-
- # Replaces the "outer HTML" (i.e., the entire element, not just its
- # contents) of the DOM element with the given +id+.
- #
- # +options_for_render+ may be either a string of HTML to insert, or a hash
- # of options to be passed to ActionView::Base#render. For example:
- #
- # # Replace the DOM element having ID 'person-45' with the
- # # 'person' partial for the appropriate object.
- # page.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
- # the use of wrapper elements.
- #
- # Examples:
- #
- # <div id="people">
- # <%= render :partial => 'person', :collection => @people %>
- # </div>
- #
- # # Insert a new person
- # #
- # # Generates: new Insertion.Bottom({object: "Matz", partial: "person"}, "");
- # page.insert_html :bottom, :partial => 'person', :object => @person
- #
- # # Replace an existing person
- #
- # # Generates: Element.replace("person_45", "-- Contents of partial --");
- # page.replace 'person_45', :partial => 'person', :object => @person
- #
- def replace(id, *options_for_render)
- call 'Element.replace', id, render(*options_for_render)
- end
-
- # Removes the DOM elements with the given +ids+ from the page.
- #
- # Example:
- #
- # # Remove a few people
- # # Generates: ["person_23", "person_9", "person_2"].each(Element.remove);
- # page.remove 'person_23', 'person_9', 'person_2'
- #
- def remove(*ids)
- loop_on_multiple_args 'Element.remove', ids
- end
-
- # Shows hidden DOM elements with the given +ids+.
- #
- # Example:
- #
- # # Show a few people
- # # Generates: ["person_6", "person_13", "person_223"].each(Element.show);
- # page.show 'person_6', 'person_13', 'person_223'
- #
- def show(*ids)
- loop_on_multiple_args 'Element.show', ids
- end
-
- # Hides the visible DOM elements with the given +ids+.
- #
- # Example:
- #
- # # Hide a few people
- # # Generates: ["person_29", "person_9", "person_0"].each(Element.hide);
- # page.hide 'person_29', 'person_9', 'person_0'
- #
- def hide(*ids)
- loop_on_multiple_args 'Element.hide', ids
- end
-
- # Toggles the visibility of the DOM elements with the given +ids+.
- # Example:
- #
- # # Show a few people
- # # Generates: ["person_14", "person_12", "person_23"].each(Element.toggle);
- # page.toggle 'person_14', 'person_12', 'person_23' # Hides the elements
- # page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements
- #
- def toggle(*ids)
- loop_on_multiple_args 'Element.toggle', ids
- end
-
- # Displays an alert dialog with the given +message+.
- #
- # Example:
- #
- # # Generates: alert('This message is from Rails!')
- # page.alert('This message is from Rails!')
- def alert(message)
- call 'alert', message
- end
-
- # Redirects the browser to the given +location+ using JavaScript, in the same form as +url_for+.
- #
- # Examples:
- #
- # # Generates: window.location.href = "/mycontroller";
- # page.redirect_to(:action => 'index')
- #
- # # Generates: window.location.href = "/account/signup";
- # page.redirect_to(:controller => 'account', :action => 'signup')
- def redirect_to(location)
- url = location.is_a?(String) ? location : @context.url_for(location)
- record "window.location.href = #{url.inspect}"
- end
-
- # Reloads the browser's current +location+ using JavaScript
- #
- # Examples:
- #
- # # Generates: window.location.reload();
- # page.reload
- def reload
- record 'window.location.reload()'
- end
-
- # 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.
- #
- # Examples:
- #
- # # Generates: Element.replace(my_element, "My content to replace with.")
- # page.call 'Element.replace', 'my_element', "My content to replace with."
- #
- # # Generates: alert('My message!')
- # page.call 'alert', 'My message!'
- #
- # # Generates:
- # # my_method(function() {
- # # $("one").show();
- # # $("two").hide();
- # # });
- # page.call(:my_method) do |p|
- # p[:one].show
- # p[:two].hide
- # end
- def call(function, *arguments, &block)
- record "#{function}(#{arguments_for_call(arguments, block)})"
- end
-
- # Assigns the JavaScript +variable+ the given +value+.
- #
- # Examples:
- #
- # # Generates: my_string = "This is mine!";
- # page.assign 'my_string', 'This is mine!'
- #
- # # Generates: record_count = 33;
- # page.assign 'record_count', 33
- #
- # # Generates: tabulated_total = 47
- # page.assign 'tabulated_total', @total_from_cart
- #
- def assign(variable, value)
- record "#{variable} = #{javascript_object_for(value)}"
- end
-
- # Writes raw JavaScript to the page.
- #
- # Example:
- #
- # page << "alert('JavaScript with Prototype.');"
- def <<(javascript)
- @lines << javascript
- end
-
- # Executes the content of the block after a delay of +seconds+. Example:
- #
- # # Generates:
- # # setTimeout(function() {
- # # ;
- # # new Effect.Fade("notice",{});
- # # }, 20000);
- # page.delay(20) do
- # page.visual_effect :fade, 'notice'
- # end
- def delay(seconds = 1)
- record "setTimeout(function() {\n\n"
- yield
- record "}, #{(seconds * 1000).to_i})"
- end
-
- private
- def loop_on_multiple_args(method, ids)
- record(ids.size>1 ?
- "#{javascript_object_for(ids)}.each(#{method})" :
- "#{method}(#{javascript_object_for(ids.first)})")
- end
-
- def page
- self
- end
-
- def record(line)
- line = "#{line.to_s.chomp.gsub(/\;\z/, '')};"
- self << line
- line
- end
-
- def render(*options)
- with_formats(:html) do
- case option = options.first
- when Hash
- @context.render(*options)
- else
- option.to_s
- end
- end
- end
-
- def with_formats(*args)
- @context ? @context.lookup_context.update_details(:formats => args) { yield } : yield
- end
-
- def javascript_object_for(object)
- ::ActiveSupport::JSON.encode(object)
- end
-
- 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
- end
- end
-
- # Yields a JavaScriptGenerator and returns the generated JavaScript code.
- # Use this to update multiple elements on a page in an Ajax response.
- # See JavaScriptGenerator for more information.
- #
- # Example:
- #
- # update_page do |page|
- # page.hide 'spinner'
- # end
- def update_page(&block)
- JavaScriptGenerator.new(self, &block).to_s.html_safe
- end
-
- # Works like update_page but wraps the generated JavaScript in a
- # <tt>\<script></tt> tag. Use this to include generated JavaScript in an
- # ERb template. See JavaScriptGenerator for more information.
- #
- # +html_options+ may be a hash of <tt>\<script></tt> 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
- def options_for_javascript(options)
- if options.empty?
- '{}'
- else
- "{#{options.keys.map { |k| "#{k}:#{options[k]}" }.sort.join(', ')}}"
- end
- end
-
- def options_for_ajax(options)
- js_options = build_callbacks(options)
-
- js_options['asynchronous'] = options[:type] != :synchronous
- js_options['method'] = method_option_to_s(options[:method]) if options[:method]
- js_options['insertion'] = "'#{options[:position].to_s.downcase}'" if options[:position]
- js_options['evalScripts'] = options[:script].nil? || options[:script]
-
- if options[:form]
- js_options['parameters'] = 'Form.serialize(this)'
- elsif options[:submit]
- js_options['parameters'] = "Form.serialize('#{options[:submit]}')"
- elsif options[:with]
- js_options['parameters'] = options[:with]
- end
-
- if protect_against_forgery? && !options[:form]
- if js_options['parameters']
- js_options['parameters'] << " + '&"
- else
- js_options['parameters'] = "'"
- end
- js_options['parameters'] << "#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}')"
- end
-
- options_for_javascript(js_options)
- end
-
- def method_option_to_s(method)
- (method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'"
- end
-
- def build_callbacks(options)
- callbacks = {}
- options.each do |callback, code|
- if CALLBACKS.include?(callback)
- name = 'on' + callback.to_s.capitalize
- callbacks[name] = "function(request){#{code}}"
- end
- end
- callbacks
- end
- end
-
- # Converts chained method calls on DOM proxy elements into JavaScript chains
- class JavaScriptProxy < ActiveSupport::BasicObject #:nodoc:
-
- def initialize(generator, root = nil)
- @generator = generator
- @generator << root if root
- end
-
- def is_a?(klass)
- klass == JavaScriptProxy
- end
-
- private
- def method_missing(method, *arguments, &block)
- if method.to_s =~ /(.*)=$/
- assign($1, arguments.first)
- else
- call("#{method.to_s.camelize(:lower)}", *arguments, &block)
- end
- end
-
- def call(function, *arguments, &block)
- append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments, block)})")
- self
- end
-
- def assign(variable, value)
- append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}")
- end
-
- def function_chain
- @function_chain ||= @generator.instance_variable_get(:@lines)
- end
-
- def append_to_function_chain!(call)
- function_chain[-1].chomp!(';')
- function_chain[-1] += ".#{call};"
- end
- end
-
- class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
- def initialize(generator, id)
- @id = id
- super(generator, "$(#{::ActiveSupport::JSON.encode(id)})")
- 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
-
- def replace(*options_for_render)
- call 'replace', @generator.send(:render, *options_for_render)
- end
-
- def reload(options_for_replace = {})
- replace(options_for_replace.merge({ :partial => @id.to_s }))
- end
-
- end
-
- class JavaScriptVariableProxy < JavaScriptProxy #:nodoc:
- def initialize(generator, variable)
- @variable = ::ActiveSupport::JSON::Variable.new(variable)
- @empty = true # only record lines if we have to. gets rid of unnecessary linebreaks
- super(generator)
- end
-
- # The JSON Encoder calls this to check for the +to_json+ method
- # Since it's a blank slate object, I suppose it responds to anything.
- def respond_to?(*)
- true
- end
-
- def as_json(options = nil)
- @variable
- end
-
- private
- def append_to_function_chain!(call)
- @generator << @variable if @empty
- @empty = false
- super
- end
- end
-
- class JavaScriptCollectionProxy < JavaScriptProxy #:nodoc:
- ENUMERABLE_METHODS_WITH_RETURN = [:all, :any, :collect, :map, :detect, :find, :find_all, :select, :max, :min, :partition, :reject, :sort_by, :in_groups_of, :each_slice] 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
-
- def initialize(generator, pattern)
- super(generator, @pattern = pattern)
- end
-
- def each_slice(variable, number, &block)
- if block
- enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block
- else
- add_variable_assignment!(variable)
- append_enumerable_function!("eachSlice(#{::ActiveSupport::JSON.encode(number)});")
- end
- end
-
- def grep(variable, pattern, &block)
- enumerate :grep, :variable => variable, :return => true, :method_args => [::ActiveSupport::JSON::Variable.new(pattern.inspect)], :yield_args => %w(value index), &block
- end
-
- def in_groups_of(variable, number, fill_with = nil)
- arguments = [number]
- arguments << fill_with unless fill_with.nil?
- add_variable_assignment!(variable)
- append_enumerable_function!("inGroupsOf(#{arguments_for_call arguments});")
- end
-
- def inject(variable, memo, &block)
- enumerate :inject, :variable => variable, :method_args => [memo], :yield_args => %w(memo value index), :return => true, &block
- end
-
- def pluck(variable, property)
- add_variable_assignment!(variable)
- append_enumerable_function!("pluck(#{::ActiveSupport::JSON.encode(property)});")
- end
-
- def zip(variable, *arguments, &block)
- add_variable_assignment!(variable)
- append_enumerable_function!("zip(#{arguments_for_call arguments}")
- if block
- function_chain[-1] += ", function(array) {"
- yield ::ActiveSupport::JSON::Variable.new('array')
- add_return_statement!
- @generator << '});'
- else
- function_chain[-1] += ');'
- end
- end
-
- private
- def method_missing(method, *arguments, &block)
- if ENUMERABLE_METHODS.include?(method)
- returnable = ENUMERABLE_METHODS_WITH_RETURN.include?(method)
- variable = arguments.first if returnable
- enumerate(method, {:variable => (arguments.first if returnable), :return => returnable, :yield_args => %w(value index)}, &block)
- else
- super
- end
- end
-
- # Options
- # * variable - name of the variable to set the result of the enumeration to
- # * method_args - array of the javascript enumeration method args that occur before the function
- # * yield_args - array of the javascript yield args
- # * return - true if the enumeration should return the last statement
- def enumerate(enumerable, options = {}, &block)
- options[:method_args] ||= []
- options[:yield_args] ||= []
- yield_args = options[:yield_args] * ', '
- method_args = arguments_for_call options[:method_args] # foo, bar, function
- method_args << ', ' unless method_args.blank?
- add_variable_assignment!(options[:variable]) if options[:variable]
- append_enumerable_function!("#{enumerable.to_s.camelize(:lower)}(#{method_args}function(#{yield_args}) {")
- # only yield as many params as were passed in the block
- yield(*options[:yield_args].collect { |p| JavaScriptVariableProxy.new(@generator, p) }[0..block.arity-1])
- add_return_statement! if options[:return]
- @generator << '});'
- end
-
- def add_variable_assignment!(variable)
- function_chain.push("var #{variable} = #{function_chain.pop}")
- end
-
- def add_return_statement!
- unless function_chain.last =~ /return/
- function_chain.push("return #{function_chain.pop.chomp(';')};")
- end
- end
-
- def append_enumerable_function!(call)
- function_chain[-1].chomp!(';')
- function_chain[-1] += ".#{call}"
- end
- end
-
- class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
- def initialize(generator, pattern)
- super(generator, "$$(#{::ActiveSupport::JSON.encode(pattern)})")
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb b/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
deleted file mode 100644
index 8610c2469e..0000000000
--- a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
+++ /dev/null
@@ -1,263 +0,0 @@
-require 'action_view/helpers/javascript_helper'
-require 'active_support/json'
-
-module ActionView
- # = Action View Scriptaculous Helpers
- module Helpers
- # Provides a set of helpers for calling Scriptaculous[http://script.aculo.us/]
- # JavaScript functions, including those which create Ajax controls and visual
- # effects.
- #
- # To be able to use these helpers, you must include the Prototype
- # JavaScript framework and the Scriptaculous JavaScript library in your
- # pages. See the documentation for ActionView::Helpers::JavaScriptHelper
- # for more information on including the necessary JavaScript.
- #
- # The Scriptaculous helpers' behavior can be tweaked with various options.
- #
- # See the documentation at http://script.aculo.us for more information on
- # using these helpers in your application.
- module ScriptaculousHelper
- TOGGLE_EFFECTS = [:toggle_appear, :toggle_slide, :toggle_blind]
-
- # Returns a JavaScript snippet to be used on the Ajax callbacks for
- # starting visual effects.
- #
- # If no +element_id+ is given, it assumes "element" which should be a local
- # variable in the generated JavaScript execution context. This can be
- # used for example with +drop_receiving_element+:
- #
- # <%= drop_receiving_element (...), :loading => visual_effect(:fade) %>
- #
- # This would fade the element that was dropped on the drop receiving
- # element.
- #
- # For toggling visual effects, you can use <tt>:toggle_appear</tt>, <tt>:toggle_slide</tt>, and
- # <tt>:toggle_blind</tt> which will alternate between appear/fade, slidedown/slideup, and
- # blinddown/blindup respectively.
- #
- # You can change the behaviour with various options, see
- # http://script.aculo.us for more documentation.
- def visual_effect(name, element_id = false, js_options = {})
- element = element_id ? ActiveSupport::JSON.encode(element_id) : "element"
-
- js_options[:queue] = if js_options[:queue].is_a?(Hash)
- '{' + js_options[:queue].map {|k, v| k == :limit ? "#{k}:#{v}" : "#{k}:'#{v}'" }.join(',') + '}'
- elsif js_options[:queue]
- "'#{js_options[:queue]}'"
- end if js_options[:queue]
-
- [:endcolor, :direction, :startcolor, :scaleMode, :restorecolor].each do |option|
- js_options[option] = "'#{js_options[option]}'" if js_options[option]
- end
-
- if TOGGLE_EFFECTS.include? name.to_sym
- "Effect.toggle(#{element},'#{name.to_s.gsub(/^toggle_/,'')}',#{options_for_javascript(js_options)});"
- else
- "new Effect.#{name.to_s.camelize}(#{element},#{options_for_javascript(js_options)});"
- end
- end
-
- # Makes the element with the DOM ID specified by +element_id+ sortable
- # by drag-and-drop and make an Ajax call whenever the sort order has
- # changed. By default, the action called gets the serialized sortable
- # element as parameters.
- #
- # Example:
- #
- # <%= sortable_element("my_list", :url => { :action => "order" }) %>
- #
- # In the example, the action gets a "my_list" array parameter
- # containing the values of the ids of elements the sortable consists
- # of, in the current order.
- #
- # Important: For this to work, the sortable elements must have id
- # attributes in the form "string_identifier". For example, "item_1". Only
- # the identifier part of the id attribute will be serialized.
- #
- # Additional +options+ are:
- #
- # * <tt>:format</tt> - A regular expression to determine what to send as the
- # serialized id to the server (the default is <tt>/^[^_]*_(.*)$/</tt>).
- #
- # * <tt>:constraint</tt> - Whether to constrain the dragging to either
- # <tt>:horizontal</tt> or <tt>:vertical</tt> (or false to make it unconstrained).
- #
- # * <tt>:overlap</tt> - Calculate the item overlap in the <tt>:horizontal</tt>
- # or <tt>:vertical</tt> direction.
- #
- # * <tt>:tag</tt> - Which children of the container element to treat as
- # sortable (default is <tt>li</tt>).
- #
- # * <tt>:containment</tt> - Takes an element or array of elements to treat as
- # potential drop targets (defaults to the original target element).
- #
- # * <tt>:only</tt> - A CSS class name or array of class names used to filter
- # out child elements as candidates.
- #
- # * <tt>:scroll</tt> - Determines whether to scroll the list during drag
- # operations if the list runs past the visual border.
- #
- # * <tt>:tree</tt> - Determines whether to treat nested lists as part of the
- # main sortable list. This means that you can create multi-layer lists,
- # and not only sort items at the same level, but drag and sort items
- # between levels.
- #
- # * <tt>:hoverclass</tt> - If set, the Droppable will have this additional CSS class
- # when an accepted Draggable is hovered over it.
- #
- # * <tt>:handle</tt> - Sets whether the element should only be draggable by an
- # embedded handle. The value may be a string referencing a CSS class value
- # (as of script.aculo.us V1.5). The first child/grandchild/etc. element
- # found within the element that has this CSS class value will be used as
- # the handle.
- #
- # * <tt>:ghosting</tt> - Clones the element and drags the clone, leaving
- # the original in place until the clone is dropped (default is <tt>false</tt>).
- #
- # * <tt>:dropOnEmpty</tt> - If true the Sortable container will be made into
- # a Droppable, that can receive a Draggable (as according to the containment
- # rules) as a child element when there are no more elements inside (default
- # is <tt>false</tt>).
- #
- # * <tt>:onChange</tt> - Called whenever the sort order changes while dragging. When
- # dragging from one Sortable to another, the callback is called once on each
- # Sortable. Gets the affected element as its parameter.
- #
- # * <tt>:onUpdate</tt> - Called when the drag ends and the Sortable's order is
- # changed in any way. When dragging from one Sortable to another, the callback
- # is called once on each Sortable. Gets the container as its parameter.
- #
- # See http://script.aculo.us for more documentation.
- def sortable_element(element_id, options = {})
- javascript_tag(sortable_element_js(element_id, options).chop!)
- end
-
- def sortable_element_js(element_id, options = {}) #:nodoc:
- options[:with] ||= "Sortable.serialize(#{ActiveSupport::JSON.encode(element_id)})"
- options[:onUpdate] ||= "function(){" + remote_function(options) + "}"
- options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
-
- [:tag, :overlap, :constraint, :handle].each do |option|
- options[option] = "'#{options[option]}'" if options[option]
- end
-
- options[:containment] = array_or_string_for_javascript(options[:containment]) if options[:containment]
- options[:only] = array_or_string_for_javascript(options[:only]) if options[:only]
-
- %(Sortable.create(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
- end
-
- # Makes the element with the DOM ID specified by +element_id+ draggable.
- #
- # Example:
- # <%= draggable_element("my_image", :revert => true)
- #
- # You can change the behaviour with various options, see
- # http://script.aculo.us for more documentation.
- def draggable_element(element_id, options = {})
- javascript_tag(draggable_element_js(element_id, options).chop!)
- end
-
- def draggable_element_js(element_id, options = {}) #:nodoc:
- %(new Draggable(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
- end
-
- # Makes the element with the DOM ID specified by +element_id+ receive
- # dropped draggable elements (created by +draggable_element+).
- # and make an AJAX call. By default, the action called gets the DOM ID
- # of the element as parameter.
- #
- # Example:
- # <%= drop_receiving_element("my_cart", :url =>
- # { :controller => "cart", :action => "add" }) %>
- #
- # You can change the behaviour with various options, see
- # http://script.aculo.us for more documentation.
- #
- # Some of these +options+ include:
- # * <tt>:accept</tt> - Set this to a string or an array of strings describing the
- # allowable CSS classes that the +draggable_element+ must have in order
- # to be accepted by this +drop_receiving_element+.
- #
- # * <tt>:confirm</tt> - Adds a confirmation dialog. Example:
- #
- # :confirm => "Are you sure you want to do this?"
- #
- # * <tt>:hoverclass</tt> - If set, the +drop_receiving_element+ will have
- # this additional CSS class when an accepted +draggable_element+ is
- # hovered over it.
- #
- # * <tt>:onDrop</tt> - Called when a +draggable_element+ is dropped onto
- # this element. Override this callback with a JavaScript expression to
- # change the default drop behaviour. Example:
- #
- # :onDrop => "function(draggable_element, droppable_element, event) { alert('I like bananas') }"
- #
- # This callback gets three parameters: The Draggable element, the Droppable
- # element and the Event object. You can extract additional information about
- # the drop - like if the Ctrl or Shift keys were pressed - from the Event object.
- #
- # * <tt>:with</tt> - A JavaScript expression specifying the parameters for
- # the XMLHttpRequest. Any expressions should return a valid URL query string.
- def drop_receiving_element(element_id, options = {})
- javascript_tag(drop_receiving_element_js(element_id, options).chop!)
- end
-
- def drop_receiving_element_js(element_id, options = {}) #:nodoc:
- options[:with] ||= "'id=' + encodeURIComponent(element.id)"
- options[:onDrop] ||= "function(element){" + remote_function(options) + "}"
- options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
-
- options[:accept] = array_or_string_for_javascript(options[:accept]) if options[:accept]
- options[:hoverclass] = "'#{options[:hoverclass]}'" if options[:hoverclass]
-
- # Confirmation happens during the onDrop callback, so it can be removed from the options
- options.delete(:confirm) if options[:confirm]
-
- %(Droppables.add(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
- end
-
- protected
- def array_or_string_for_javascript(option)
- if option.kind_of?(Array)
- "['#{option.join('\',\'')}']"
- elsif !option.nil?
- "'#{option}'"
- end
- end
- end
-
- module PrototypeHelper
- class JavaScriptGenerator
- module GeneratorMethods
- # Starts a script.aculo.us visual effect. See
- # ActionView::Helpers::ScriptaculousHelper for more information.
- def visual_effect(name, id = nil, options = {})
- record @context.send(:visual_effect, name, id, options)
- end
-
- # Creates a script.aculo.us sortable element. Useful
- # to recreate sortable elements after items get added
- # or deleted.
- # See ActionView::Helpers::ScriptaculousHelper for more information.
- def sortable(id, options = {})
- record @context.send(:sortable_element_js, id, options)
- end
-
- # Creates a script.aculo.us draggable element.
- # See ActionView::Helpers::ScriptaculousHelper for more information.
- def draggable(id, options = {})
- record @context.send(:draggable_element_js, id, options)
- end
-
- # Creates a script.aculo.us drop receiving element.
- # See ActionView::Helpers::ScriptaculousHelper for more information.
- def drop_receiving(id, options = {})
- record @context.send(:drop_receiving_element_js, id, options)
- end
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/sprockets_helper.rb b/actionpack/lib/action_view/helpers/sprockets_helper.rb
new file mode 100644
index 0000000000..408a2030ab
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/sprockets_helper.rb
@@ -0,0 +1,85 @@
+require 'uri'
+
+module ActionView
+ module Helpers
+ module SprocketsHelper
+ def sprockets_javascript_path(source)
+ compute_sprockets_path source, 'assets', 'js'
+ end
+
+ def sprockets_javascript_include_tag(source, options = {})
+ options = {
+ 'type' => "text/javascript",
+ 'src' => sprockets_javascript_path(source)
+ }.merge(options.stringify_keys)
+
+ content_tag 'script', "", options
+ end
+
+ def sprockets_stylesheet_path(source)
+ compute_sprockets_path source, 'assets', 'css'
+ end
+
+ def sprockets_stylesheet_link_tag(source, options = {})
+ options = {
+ 'rel' => "stylesheet",
+ 'type' => "text/css",
+ 'media' => "screen",
+ 'href' => sprockets_stylesheet_path(source)
+ }.merge(options.stringify_keys)
+
+ tag 'link', options
+ end
+
+ private
+ def compute_sprockets_path(source, dir, default_ext)
+ source = source.to_s
+
+ return source if URI.parse(source).host
+
+ # Add /javscripts to relative paths
+ if source[0] != ?/
+ source = "/#{dir}/#{source}"
+ end
+
+ # Add default extension if there isn't one
+ if default_ext && File.extname(source).empty?
+ source = "#{source}.#{default_ext}"
+ end
+
+ # Fingerprint url
+ if source =~ /^\/#{dir}\/(.+)/
+ source = assets.path($1, config.perform_caching, dir)
+ end
+
+ host = compute_asset_host(source)
+
+ if controller.respond_to?(:request) && host && URI.parse(host).host
+ source = "#{controller.request.protocol}#{host}#{source}"
+ end
+
+ source
+ end
+
+ def compute_asset_host(source)
+ if host = config.asset_host
+ if host.is_a?(Proc) || host.respond_to?(:call)
+ case host.is_a?(Proc) ? host.arity : host.method(:call).arity
+ when 2
+ request = controller.respond_to?(:request) && controller.request
+ host.call(source, request)
+ else
+ host.call(source)
+ end
+ else
+ (host =~ /%d/) ? host % (source.hash % 4) : host
+ end
+ end
+ end
+
+ def assets
+ Rails.application.assets
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 2d3c5fe7e7..bdda1df437 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -303,7 +303,7 @@ module ActionView
# # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.myblog.com</a>.
# Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>."
def auto_link(text, *args, &block)#link = :all, html = {}, &block)
- return ''.html_safe if text.blank?
+ return '' if text.blank?
options = args.size == 2 ? {} : args.extract_options! # this is necessary because the old auto_link API has a Hash as its last parameter
unless args.empty?
@@ -507,7 +507,7 @@ module ActionView
end
content_tag(:a, link_text, link_attributes.merge('href' => href), !!options[:sanitize]) + punctuation.reverse.join('')
end
- end.html_safe
+ end
end
# Turns all email addresses into clickable links. If a block is given,
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 2cd2dca711..de75488e72 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -81,9 +81,12 @@ module ActionView
# # => /workshops
#
# <%= url_for(@workshop) %>
- # # calls @workshop.to_s
+ # # calls @workshop.to_param which by default returns the id
# # => /workshops/5
#
+ # # to_param can be re-defined in a model to provide different URL names:
+ # # => /workshops/1-workshop-name
+ #
# <%= url_for("http://www.example.com") %>
# # => http://www.example.com
#
@@ -183,7 +186,7 @@ module ActionView
# link_to "Profiles", :controller => "profiles"
# # => <a href="/profiles">Profiles</a>
#
- # You can use a block as well if your link target is hard to fit into the name parameter. ERb example:
+ # You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
#
# <%= link_to(@profile) do %>
# <strong><%= @profile.name %></strong> -- <span>Check it out!</span>
diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb
index e3de3e1eac..8b840a6463 100644
--- a/actionpack/lib/action_view/path_set.rb
+++ b/actionpack/lib/action_view/path_set.rb
@@ -15,6 +15,7 @@ module ActionView #:nodoc:
end
def find_all(path, prefixes = [], *args)
+ prefixes = [prefixes] if String === prefixes
prefixes.each do |prefix|
each do |resolver|
templates = resolver.find_all(path, prefix, *args)
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index 501ec07b09..f20ba7e6d3 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -6,7 +6,7 @@ module ActionView
class Railtie < Rails::Railtie
config.action_view = ActiveSupport::OrderedOptions.new
config.action_view.stylesheet_expansions = {}
- config.action_view.javascript_expansions = { :defaults => ['prototype', 'effects', 'dragdrop', 'controls', 'rails'] }
+ config.action_view.javascript_expansions = { :defaults => %w(jquery rails) }
initializer "action_view.cache_asset_ids" do |app|
unless app.config.cache_classes
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index 94c0a8a8fb..10cd37d56f 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -1,5 +1,3 @@
-require 'action_view/renderer/abstract_renderer'
-
module ActionView
class PartialRenderer < AbstractRenderer #:nodoc:
PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} }
@@ -79,7 +77,7 @@ module ActionView
locals[as] = object
content = @template.render(view, locals) do |*name|
- view._layout_for(*name, &block)
+ view._block_layout_for(*name, &block)
end
content = layout.render(view, locals){ content } if layout
diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
new file mode 100644
index 0000000000..52f0e9f5bd
--- /dev/null
+++ b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
@@ -0,0 +1,129 @@
+# 1.9 ships with Fibers but we need to require the extra
+# methods explicitly. We only load those extra methods if
+# Fiber is available in the first place.
+require 'fiber' if defined?(Fiber)
+
+module ActionView
+ # Consider the following layout:
+ #
+ # <%= yield :header %>
+ # 2
+ # <%= yield %>
+ # 5
+ # <%= yield :footer %>
+ #
+ # And template:
+ #
+ # <%= provide :header, "1" %>
+ # 3
+ # 4
+ # <%= provide :footer, "6" %>
+ #
+ # It will stream:
+ #
+ # "1\n", "2\n", "3\n4\n", "5\n", "6\n"
+ #
+ # Notice that once you <%= yield %>, it will render the whole template
+ # before streaming again. In the future, we can also support streaming
+ # from the template and not only the layout.
+ #
+ # Also, notice we use +provide+ instead of +content_for+, as +provide+
+ # gives the control back to the layout as soon as it is called.
+ # With +content_for+, it would render all the template to find all
+ # +content_for+ calls. For instance, consider this layout:
+ #
+ # <%= yield :header %>
+ #
+ # With this template:
+ #
+ # <%= content_for :header, "1" %>
+ # <%= provide :header, "2" %>
+ # <%= provide :header, "3" %>
+ #
+ # It will return "12\n" because +content_for+ continues rendering the
+ # template but it is returns back to the layout as soon as it sees the
+ # first +provide+.
+ #
+ # == TODO
+ #
+ # * Add streaming support in the controllers with no-cache settings
+ # * What should happen when an error happens?
+ # * Support streaming from child templates, partials and so on.
+ # * Support on sprockets async JS load?
+ #
+ class StreamingTemplateRenderer < TemplateRenderer #:nodoc:
+ # A valid Rack::Body (i.e. it responds to each).
+ # It is initialized with a block that, when called, starts
+ # rendering the template.
+ class Body #:nodoc:
+ def initialize(&start)
+ @start = start
+ end
+
+ def each(&block)
+ @start.call(block)
+ self
+ end
+ end
+
+ # For streaming, instead of rendering a given a template, we return a Body
+ # object that responds to each. This object is initialized with a block
+ # that knows how to render the template.
+ def render_template(template, layout_name = nil, locals = {}) #:nodoc:
+ return [super] unless layout_name && template.supports_streaming?
+
+ locals ||= {}
+ layout = layout_name && find_layout(layout_name, locals.keys)
+
+ Body.new do |buffer|
+ delayed_render(buffer, template, layout, @view, locals)
+ end
+ end
+
+ private
+
+ def delayed_render(buffer, template, layout, view, locals)
+ # Wrap the given buffer in the StreamingBuffer and pass it to the
+ # underlying template handler. Now, everytime something is concatenated
+ # to the buffer, it is not appended to an array, but streamed straight
+ # to the client.
+ output = ActionView::StreamingBuffer.new(buffer)
+ yielder = lambda { |*name| view._layout_for(*name) }
+
+ instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
+ fiber = Fiber.new do
+ if layout
+ layout.render(view, locals, output, &yielder)
+ else
+ # If you don't have a layout, just render the thing
+ # and concatenate the final result. This is the same
+ # as a layout with just <%= yield %>
+ output.safe_concat view._layout_for
+ end
+ end
+
+ # Set the view flow to support streaming. It will be aware
+ # when to stop rendering the layout because it needs to search
+ # something in the template and vice-versa.
+ view._view_flow = StreamingFlow.new(view, fiber)
+
+ # Yo! Start the fiber!
+ fiber.resume
+
+ # If the fiber is still alive, it means we need something
+ # from the template, so start rendering it. If not, it means
+ # the layout exited without requiring anything from the template.
+ if fiber.alive?
+ content = template.render(view, locals, &yielder)
+
+ # Once rendering the template is done, sets its content in the :layout key.
+ view._view_flow.set(:layout, content)
+
+ # In case the layout continues yielding, we need to resume
+ # the fiber until all yields are handled.
+ fiber.resume while fiber.alive?
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb
index 9ae1636131..6b5ead463f 100644
--- a/actionpack/lib/action_view/renderer/template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/template_renderer.rb
@@ -1,41 +1,16 @@
-require 'set'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/array/wrap'
-require 'action_view/renderer/abstract_renderer'
module ActionView
class TemplateRenderer < AbstractRenderer #:nodoc:
- attr_reader :rendered
-
- def initialize(view)
- super
- @rendered = Set.new
- end
-
def render(options)
wrap_formats(options[:template] || options[:file]) do
template = determine_template(options)
+ freeze_formats(template.formats, true)
render_template(template, options[:layout], options[:locals])
end
end
- def render_once(options)
- paths, locals = options[:once], options[:locals] || {}
- layout, keys = options[:layout], locals.keys
- prefixes = options.fetch(:prefixes, @view.controller_prefixes)
-
- raise "render :once expects a String or an Array to be given" unless paths
-
- render_with_layout(layout, locals) do
- contents = []
- Array.wrap(paths).each do |path|
- template = find_template(path, prefixes, false, keys)
- contents << render_template(template, nil, locals) if @rendered.add?(template)
- end
- contents.join("\n")
- end
- end
-
# Determine the template to be rendered using the given options.
def determine_template(options) #:nodoc:
keys = options[:locals].try(:keys) || []
@@ -56,7 +31,6 @@ module ActionView
# Renders the given template. An string representing the layout can be
# supplied as well.
def render_template(template, layout_name = nil, locals = {}) #:nodoc:
- freeze_formats(template.formats, true)
view, locals = @view, locals || {}
render_with_layout(layout_name, locals) do |layout|
@@ -72,7 +46,7 @@ module ActionView
if layout
view = @view
- view.store_content_for(:layout, content)
+ view._view_flow.set(:layout, content)
layout.render(view, locals){ |*name| view._layout_for(*name) }
else
content
diff --git a/actionpack/lib/action_view/rendering.rb b/actionpack/lib/action_view/rendering.rb
index baa5d2c3fd..2bce2fb045 100644
--- a/actionpack/lib/action_view/rendering.rb
+++ b/actionpack/lib/action_view/rendering.rb
@@ -6,11 +6,9 @@ module ActionView
# Returns the result of a render that's dictated by the options hash. The primary options are:
#
# * <tt>:partial</tt> - See ActionView::Partials.
- # * <tt>:update</tt> - Calls update_page with the block given.
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
# * <tt>:text</tt> - Renders the text passed in out.
- # * <tt>:once</tt> - Accepts a string or an array of strings and Rails will ensure they each of them are rendered just once.
#
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
# as the locals hash.
@@ -21,18 +19,27 @@ module ActionView
_render_partial(options.merge(:partial => options[:layout]), &block)
elsif options.key?(:partial)
_render_partial(options)
- elsif options.key?(:once)
- _render_once(options)
else
_render_template(options)
end
- when :update
- update_page(&block)
else
_render_partial(:partial => options, :locals => locals)
end
end
+ # Render but returns a valid Rack body. If fibers are defined, we return
+ # a streaming body that renders the template piece by piece.
+ #
+ # Note that partials are not supported to be rendered with streaming,
+ # so in such cases, we just wrap them in an array.
+ def render_body(options)
+ if options.key?(:partial)
+ [_render_partial(options)]
+ else
+ StreamingTemplateRenderer.new(self).render(options)
+ end
+ end
+
# Returns the contents that are yielded to a layout, given a name or a block.
#
# You can think of a layout as a method that is called with a block. If the user calls
@@ -79,22 +86,23 @@ module ActionView
# Hello David
# </html>
#
- def _layout_for(*args, &block)
+ def _layout_for(*args)
+ name = args.first
+ name = :layout unless name.is_a?(Symbol)
+ @_view_flow.get(name).html_safe
+ end
+
+ # Handle layout for calls from partials that supports blocks.
+ def _block_layout_for(*args, &block)
name = args.first
- if name.is_a?(Symbol)
- @_content_for[name].html_safe
- elsif block
+ if !name.is_a?(Symbol) && block
capture(*args, &block)
else
- @_content_for[:layout].html_safe
+ _layout_for(*args)
end
end
- def _render_once(options) #:nodoc:
- _template_renderer.render_once(options)
- end
-
def _render_template(options) #:nodoc:
_template_renderer.render(options)
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 96d506fac5..6dfc4f68ae 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -126,17 +126,23 @@ module ActionView
@formats = Array.wrap(format).map { |f| f.is_a?(Mime::Type) ? f.ref : f }
end
+ # Returns if the underlying handler supports streaming. If so,
+ # a streaming buffer *may* be passed when it start rendering.
+ def supports_streaming?
+ handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
+ end
+
# Render a template. If the template was not compiled yet, it is done
# exactly before rendering.
#
# This method is instrumented as "!render_template.action_view". Notice that
# we use a bang in this instrumentation because you don't want to
# consume this in production. This is only slow if it's being listened to.
- def render(view, locals, &block)
+ def render(view, locals, buffer=nil, &block)
old_template, view._template = view._template, self
ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do
compile!(view)
- view.send(method_name, locals, &block)
+ view.send(method_name, locals, buffer, &block)
end
rescue Exception => e
handle_render_error(view, e)
@@ -167,28 +173,6 @@ module ActionView
end
end
- # Expires this template by setting his updated_at date to Jan 1st, 1970.
- def expire!
- @updated_at = Time.utc(1970)
- end
-
- # Receives a view context and renders a template exactly like self by using
- # the @virtual_path. It raises an error if no @virtual_path was given.
- def rerender(view)
- raise "A template needs to have a virtual path in order to be rerendered" unless @virtual_path
- name = @virtual_path.dup
- if name.sub!(/(^|\/)_([^\/]*)$/, '\1\2')
- view.render :partial => name
- else
- view.render :template => @virtual_path
- end
- end
-
- # Used to store template data by template handlers.
- def data
- @data ||= {}
- end
-
def inspect
@inspect ||=
if defined?(Rails.root)
@@ -274,13 +258,12 @@ module ActionView
end
end
- arity = @handler.respond_to?(:arity) ? @handler.arity : @handler.method(:call).arity
- code = arity.abs == 1 ? @handler.call(self) : @handler.call(self, view)
+ code = @handler.call(self)
# Make sure that the resulting String to be evalled is in the
# encoding of the code
source = <<-end_src
- def #{method_name}(local_assigns)
+ def #{method_name}(local_assigns, output_buffer)
_old_output_buffer = @output_buffer;#{locals_code};#{code}
ensure
@output_buffer = _old_output_buffer
diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb
index 4438199497..959afa734e 100644
--- a/actionpack/lib/action_view/template/handlers.rb
+++ b/actionpack/lib/action_view/template/handlers.rb
@@ -3,12 +3,10 @@ module ActionView #:nodoc:
class Template
module Handlers #:nodoc:
autoload :ERB, 'action_view/template/handlers/erb'
- autoload :RJS, 'action_view/template/handlers/rjs'
autoload :Builder, 'action_view/template/handlers/builder'
def self.extended(base)
base.register_default_template_handler :erb, ERB.new
- base.register_template_handler :rjs, RJS.new
base.register_template_handler :builder, Builder.new
end
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index a36837afc8..7e9e4e518a 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -1,28 +1,14 @@
require 'active_support/core_ext/class/attribute_accessors'
-require 'active_support/core_ext/string/output_safety'
require 'action_view/template'
require 'action_view/template/handler'
require 'erubis'
module ActionView
- class OutputBuffer < ActiveSupport::SafeBuffer
- def initialize(*)
- super
- encode! if encoding_aware?
- end
-
- def <<(value)
- super(value.to_s)
- end
- alias :append= :<<
- alias :safe_append= :safe_concat
- end
-
class Template
module Handlers
class Erubis < ::Erubis::Eruby
def add_preamble(src)
- src << "@output_buffer = ActionView::OutputBuffer.new;"
+ src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
end
def add_text(src, text)
@@ -55,7 +41,7 @@ module ActionView
class ERB
# Specify trim mode for the ERB compiler. Defaults to '-'.
- # See ERb documentation for suitable values.
+ # See ERB documentation for suitable values.
class_attribute :erb_trim_mode
self.erb_trim_mode = '-'
@@ -73,6 +59,10 @@ module ActionView
new.call(template)
end
+ def supports_streaming?
+ true
+ end
+
def handles_encoding?
true
end
diff --git a/actionpack/lib/action_view/template/handlers/rjs.rb b/actionpack/lib/action_view/template/handlers/rjs.rb
deleted file mode 100644
index 9d71059134..0000000000
--- a/actionpack/lib/action_view/template/handlers/rjs.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module ActionView
- module Template::Handlers
- class RJS
- # Default format used by RJS.
- class_attribute :default_format
- self.default_format = Mime::JS
-
- def call(template)
- "update_page do |page|;#{template.source}\nend"
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 41c6310ae2..870897958a 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -186,7 +186,7 @@ module ActionView
# ==== Examples
#
# Default pattern, loads views the same way as previous versions of rails, eg. when you're
- # looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml,rjs},}`
+ # looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml},}`
#
# FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}")
#
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 3e2ddffa16..5c74bf843a 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -121,7 +121,7 @@ module ActionView
# Support the selector assertions
#
# Need to experiment if this priority is the best one: rendered => output_buffer
- def response_from_page_or_rjs
+ def response_from_page
HTML::Document.new(@rendered.blank? ? @output_buffer : @rendered).root
end
diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb
new file mode 100644
index 0000000000..fe3c8c9783
--- /dev/null
+++ b/actionpack/lib/sprockets/railtie.rb
@@ -0,0 +1,56 @@
+module Sprockets
+ class Railtie < Rails::Railtie
+ def self.using_coffee?
+ require 'coffee-script'
+ defined?(CoffeeScript)
+ rescue LoadError
+ false
+ end
+
+ def self.using_scss?
+ require 'sass'
+ defined?(Sass)
+ rescue LoadError
+ false
+ end
+
+ config.app_generators.javascript_engine :coffee if using_coffee?
+ config.app_generators.stylesheet_engine :scss if using_scss?
+
+ # Configure ActionController to use sprockets.
+ initializer "sprockets.set_configs", :after => "action_controller.set_configs" do |app|
+ ActiveSupport.on_load(:action_controller) do
+ self.use_sprockets = app.config.assets.enabled
+ end
+ end
+
+ # We need to configure this after initialization to ensure we collect
+ # paths from all engines. This hook is invoked exactly before routes
+ # are compiled.
+ config.after_initialize do |app|
+ assets = app.config.assets
+ next unless assets.enabled
+
+ app.assets = asset_environment(app)
+ app.routes.append do
+ mount app.assets => assets.prefix
+ end
+
+ if config.action_controller.perform_caching
+ app.assets = app.assets.index
+ end
+ end
+
+ protected
+
+ def asset_environment(app)
+ require "sprockets"
+ assets = app.config.assets
+ env = Sprockets::Environment.new(app.root.to_s)
+ env.static_root = File.join(app.root.join("public"), assets.prefix)
+ env.paths.concat assets.paths
+ env.logger = Rails.logger
+ env
+ end
+ end
+end \ No newline at end of file