aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
authorPratik Naik <pratiknaik@gmail.com>2010-01-28 19:46:17 +0000
committerPratik Naik <pratiknaik@gmail.com>2010-01-28 19:46:17 +0000
commit285361d1589002fcdd1584c07e6eb295f13c9f37 (patch)
tree2d50a69b3b59b6fb3cb7577b990fe3b1aaf58f4f /actionpack/lib
parentdfa19408651ecc82e2aeba95d93db871ba8a6e41 (diff)
parentd58398c2b5e98aad18dc72790230f338c10d145c (diff)
downloadrails-285361d1589002fcdd1584c07e6eb295f13c9f37.tar.gz
rails-285361d1589002fcdd1584c07e6eb295f13c9f37.tar.bz2
rails-285361d1589002fcdd1584c07e6eb295f13c9f37.zip
Merge remote branch 'mainstream/master'
Conflicts: railties/lib/rails/railtie.rb
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/abstract_controller.rb1
-rw-r--r--actionpack/lib/abstract_controller/collector.rb30
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb3
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb4
-rw-r--r--actionpack/lib/action_controller/base.rb2
-rw-r--r--actionpack/lib/action_controller/deprecated.rb1
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb76
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb29
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb4
-rw-r--r--actionpack/lib/action_controller/railtie.rb28
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb14
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb15
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb5
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb28
-rw-r--r--actionpack/lib/action_view/base.rb6
-rw-r--r--actionpack/lib/action_view/helpers.rb8
-rw-r--r--actionpack/lib/action_view/helpers/ajax_helper.rb735
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb36
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb20
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb44
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb450
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb8
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb54
-rw-r--r--actionpack/lib/action_view/railtie.rb2
-rw-r--r--actionpack/lib/action_view/template.rb4
-rw-r--r--actionpack/lib/action_view/template/resolver.rb13
27 files changed, 907 insertions, 715 deletions
diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb
index 725d8fb8fc..2c2ef16622 100644
--- a/actionpack/lib/abstract_controller.rb
+++ b/actionpack/lib/abstract_controller.rb
@@ -10,6 +10,7 @@ module AbstractController
autoload :Base
autoload :Callbacks
+ autoload :Collector
autoload :Helpers
autoload :Layouts
autoload :LocalizedCache
diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb
new file mode 100644
index 0000000000..d429333661
--- /dev/null
+++ b/actionpack/lib/abstract_controller/collector.rb
@@ -0,0 +1,30 @@
+module AbstractController
+ module Collector
+ def self.generate_method_for_mime(mime)
+ sym = mime.is_a?(Symbol) ? mime : mime.to_sym
+ const = sym.to_s.upcase
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{sym}(*args, &block) # def html(*args, &block)
+ custom(Mime::#{const}, *args, &block) # custom(Mime::HTML, *args, &block)
+ end # end
+ RUBY
+ end
+
+ Mime::SET.each do |mime|
+ generate_method_for_mime(mime)
+ end
+
+ protected
+
+ def method_missing(symbol, &block)
+ mime_constant = Mime.const_get(symbol.to_s.upcase)
+
+ if Mime::SET.include?(mime_constant)
+ AbstractController::Collector.generate_method_for_mime(mime_constant)
+ send(symbol, &block)
+ else
+ super
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 6fbf6bc392..56ddf9bf01 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -270,6 +270,9 @@ module AbstractController
end
end
ruby_eval
+ when Proc
+ define_method :_layout_from_proc, &@_layout
+ self.class_eval %{def _layout(details) _layout_from_proc(self) end}
when false
self.class_eval %{def _layout(details) end}
when true
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index a168b1b4c5..1dec3f2c3e 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -41,10 +41,6 @@ module AbstractController
# Mostly abstracts the fact that calling render twice is a DoubleRenderError.
# Delegates render_to_body and sticks the result in self.response_body.
def render(*args, &block)
- if response_body
- raise AbstractController::DoubleRenderError
- end
-
options = _normalize_options(*args, &block)
self.response_body = render_to_body(options)
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index f46231d72b..215b70734c 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -7,7 +7,6 @@ module ActionController
include AbstractController::Translation
include ActionController::Helpers
- helper :all # By default, all helpers should be included
include ActionController::HideActions
include ActionController::UrlFor
@@ -67,6 +66,7 @@ module ActionController
def self.inherited(klass)
::ActionController::Base.subclasses << klass.to_s
super
+ klass.helper :all
end
def self.subclasses
diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb
index 589061e77c..edc0e5b3fe 100644
--- a/actionpack/lib/action_controller/deprecated.rb
+++ b/actionpack/lib/action_controller/deprecated.rb
@@ -2,3 +2,4 @@ ActionController::AbstractRequest = ActionController::Request = ActionDispatch::
ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response
ActionController::Routing = ActionDispatch::Routing
ActionController::Routing::Routes = ActionDispatch::Routing::RouteSet.new
+ActionController::UrlWriter = AbstractController::UrlFor
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index cdd14560e1..03ba4b3f83 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -50,13 +50,19 @@ module ActionController
include AbstractController::Helpers
included do
- # Set the default directory for helpers
- extlib_inheritable_accessor(:helpers_dir) do
- defined?(Rails.root) ? "#{Rails.root}/app/helpers" : "app/helpers"
- end
+ extlib_inheritable_accessor(:helpers_path)
+ self.helpers_path = []
end
module ClassMethods
+ def helpers_dir
+ self.helpers_path
+ end
+
+ def helpers_dir=(value)
+ self.helpers_path = Array(value)
+ end
+
def inherited(klass)
klass.class_eval { default_helper_module! unless name.blank? }
super
@@ -80,36 +86,42 @@ module ActionController
@helper_proxy ||= ActionView::Base.new.extend(_helpers)
end
- private
- # Overwrite _modules_for_helpers to accept :all as argument, which loads
- # all helpers in helpers_dir.
- #
- # ==== Parameters
- # args<Array[String, Symbol, Module, all]>:: A list of helpers
- #
- # ==== Returns
- # Array[Module]:: A normalized list of modules for the list of
- # helpers provided.
- def _modules_for_helpers(args)
- args += all_application_helpers if args.delete(:all)
- super(args)
- end
+ private
+ # Overwrite _modules_for_helpers to accept :all as argument, which loads
+ # all helpers in helpers_dir.
+ #
+ # ==== Parameters
+ # args<Array[String, Symbol, Module, all]>:: A list of helpers
+ #
+ # ==== Returns
+ # Array[Module]:: A normalized list of modules for the list of
+ # helpers provided.
+ def _modules_for_helpers(args)
+ args += all_application_helpers if args.delete(:all)
+ super(args)
+ end
- def default_helper_module!
- module_name = name.sub(/Controller$/, '')
- module_path = module_name.underscore
- helper module_path
- rescue MissingSourceFile => e
- raise e unless e.is_missing? "helpers/#{module_path}_helper"
- rescue NameError => e
- raise e unless e.missing_name? "#{module_name}Helper"
- end
+ def default_helper_module!
+ module_name = name.sub(/Controller$/, '')
+ module_path = module_name.underscore
+ helper module_path
+ rescue MissingSourceFile => e
+ raise e unless e.is_missing? "helpers/#{module_path}_helper"
+ rescue NameError => e
+ raise e unless e.missing_name? "#{module_name}Helper"
+ end
- # Extract helper names from files in app/helpers/**/*.rb
- def all_application_helpers
- extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/
- Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' }
- end
+ # Extract helper names from files in app/helpers/**/*_helper.rb
+ def all_application_helpers
+ helpers = []
+ helpers_path.each do |path|
+ extract = /^#{Regexp.quote(path)}\/?(.*)_helper.rb$/
+ helpers += Dir["#{path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
+ end
+ helpers.sort!
+ helpers.uniq!
+ helpers
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 4c02677729..08599d660e 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -1,3 +1,5 @@
+require 'abstract_controller/collector'
+
module ActionController #:nodoc:
module MimeResponds #:nodoc:
extend ActiveSupport::Concern
@@ -265,6 +267,7 @@ module ActionController #:nodoc:
end
class Collector #:nodoc:
+ include AbstractController::Collector
attr_accessor :order
def initialize(&block)
@@ -289,32 +292,6 @@ module ActionController #:nodoc:
def response_for(mime)
@responses[mime] || @responses[Mime::ALL] || @default_response
end
-
- def self.generate_method_for_mime(mime)
- sym = mime.is_a?(Symbol) ? mime : mime.to_sym
- const = sym.to_s.upcase
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{sym}(&block) # def html(&block)
- custom(Mime::#{const}, &block) # custom(Mime::HTML, &block)
- end # end
- RUBY
- end
-
- Mime::SET.each do |mime|
- generate_method_for_mime(mime)
- end
-
- def method_missing(symbol, &block)
- mime_constant = Mime.const_get(symbol.to_s.upcase)
-
- if Mime::SET.include?(mime_constant)
- self.class.generate_method_for_mime(mime_constant)
- send(symbol, &block)
- else
- super
- end
- end
-
end
end
end
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 72e2bbd00e..8f03035b2b 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -13,6 +13,10 @@ module ActionController
end
def render(*args)
+ if response_body
+ raise ::AbstractController::DoubleRenderError
+ end
+
args << {} unless args.last.is_a?(Hash)
super(*args)
self.content_type ||= args.last[:_template].mime_type.to_s
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index 7ea64c1923..9151de4462 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -3,41 +3,27 @@ require "rails"
module ActionController
class Railtie < Rails::Railtie
- plugin_name :action_controller
+ railtie_name :action_controller
require "action_controller/railties/subscriber"
subscriber ActionController::Railties::Subscriber.new
- initializer "action_controller.set_configs" do |app|
- app.config.action_controller.each do |k,v|
- ActionController::Base.send "#{k}=", v
- end
- end
-
- # TODO: ActionController::Base.logger should delegate to its own config.logger
initializer "action_controller.logger" do
ActionController::Base.logger ||= Rails.logger
end
- # Routing must be initialized after plugins to allow the former to extend the routes
- initializer "action_controller.initialize_routing" do |app|
- app.route_configuration_files << app.config.routes_configuration_file
- app.route_configuration_files << app.config.builtin_routes_configuration_file
+ initializer "action_controller.set_configs" do |app|
+ app.config.action_controller.each do |k,v|
+ ActionController::Base.send "#{k}=", v
+ end
end
initializer "action_controller.initialize_framework_caches" do
ActionController::Base.cache_store ||= RAILS_CACHE
end
- # Sets +ActionController::Base#view_paths+ and +ActionMailer::Base#template_root+
- # (but only for those frameworks that are to be loaded). If the framework's
- # paths have already been set, it is not changed, otherwise it is
- # set to use Configuration#view_path.
- initializer "action_controller.initialize_framework_views" do |app|
- # TODO: this should be combined with the logic for default config.action_controller.view_paths
- view_path = ActionView::PathSet.type_cast(app.config.view_path, app.config.cache_classes)
- ActionController::Base.view_paths = view_path if ActionController::Base.view_paths.blank?
+ initializer "action_controller.set_helpers_path" do |app|
+ ActionController::Base.helpers_path = app.config.paths.app.helpers.to_a
end
-
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index d07841218a..7cf75ffe63 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -37,7 +37,7 @@ module ActionDispatch
def initialize(app, prepare_each_request = false)
@app, @prepare_each_request = app, prepare_each_request
- run_callbacks(:prepare)
+ run_callbacks(:prepare) unless @prepare_each_request
end
def call(env)
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index 0dc1d70e37..18a2922fa7 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -55,7 +55,11 @@ module ActionDispatch
when Class
klass == middleware
else
- klass == ActiveSupport::Inflector.constantize(middleware.to_s)
+ if lazy_compare?(@klass) && lazy_compare?(middleware)
+ normalize(@klass) == normalize(middleware)
+ else
+ klass == ActiveSupport::Inflector.constantize(middleware.to_s)
+ end
end
end
@@ -72,6 +76,14 @@ module ActionDispatch
end
private
+ def lazy_compare?(object)
+ object.is_a?(String) || object.is_a?(Symbol)
+ end
+
+ def normalize(object)
+ object.to_s.strip.sub(/^::/, '')
+ end
+
def build_args
Array(args).map { |arg| arg.respond_to?(:call) ? arg.call : arg }
end
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index e4bd143e78..335daafc01 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -3,24 +3,13 @@ require "rails"
module ActionDispatch
class Railtie < Rails::Railtie
- plugin_name :action_dispatch
+ railtie_name :action_dispatch
# Prepare dispatcher callbacks and run 'prepare' callbacks
initializer "action_dispatch.prepare_dispatcher" do |app|
# TODO: This used to say unless defined?(Dispatcher). Find out why and fix.
require 'rails/dispatcher'
-
- unless app.config.cache_classes
- # Setup dev mode route reloading
- routes_last_modified = app.routes_changed_at
- reload_routes = lambda do
- unless app.routes_changed_at == routes_last_modified
- routes_last_modified = app.routes_changed_at
- app.reload_routes!
- end
- end
- ActionDispatch::Callbacks.before { |callbacks| reload_routes.call }
- end
+ ActionDispatch::Callbacks.to_prepare { app.routes_reloader.reload_if_changed }
end
end
end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index fcbb70749f..5199984814 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -157,10 +157,11 @@ module ActionDispatch
end
# Invokes Rack::Mount::Utils.normalize path and ensure that
- # (:locale) becomes (/:locale) instead of /(:locale).
+ # (:locale) becomes (/:locale) instead of /(:locale). Except
+ # for root cases, where the latter is the correct one.
def self.normalize_path(path)
path = Rack::Mount::Utils.normalize_path(path)
- path.sub!(%r{/\(+/?:}, '(/:')
+ path.sub!(%r{/(\(+)/?:}, '\1/:') unless path =~ %r{^/\(+:.*\)$}
path
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 660d28dbec..c49ac70562 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -222,19 +222,18 @@ module ActionDispatch
end
end
- attr_accessor :routes, :named_routes
- attr_accessor :disable_clear_and_finalize
+ attr_accessor :routes, :named_routes, :controller_namespaces
+ attr_accessor :disable_clear_and_finalize, :resources_path_names
def self.default_resources_path_names
{ :new => 'new', :edit => 'edit' }
end
- attr_accessor :resources_path_names
-
def initialize
self.routes = []
self.named_routes = NamedRouteCollection.new
- self.resources_path_names = self.class.default_resources_path_names
+ self.resources_path_names = self.class.default_resources_path_names.dup
+ self.controller_namespaces = Set.new
@disable_clear_and_finalize = false
end
@@ -281,32 +280,19 @@ module ActionDispatch
def controller_constraints
@controller_constraints ||= begin
- source = controller_namespaces.map { |ns| "#{Regexp.escape(ns)}/#{CONTROLLER_REGEXP.source}" }
+ namespaces = controller_namespaces + in_memory_controller_namespaces
+ source = namespaces.map { |ns| "#{Regexp.escape(ns)}/#{CONTROLLER_REGEXP.source}" }
source << CONTROLLER_REGEXP.source
Regexp.compile(source.sort.reverse.join('|'))
end
end
- def controller_namespaces
+ def in_memory_controller_namespaces
namespaces = Set.new
-
- # Find any nested controllers already in memory
ActionController::Base.subclasses.each do |klass|
controller_name = klass.underscore
namespaces << controller_name.split('/')[0...-1].join('/')
end
-
- # TODO: Move this into Railties
- if defined?(Rails.application)
- # Find namespaces in controllers/ directory
- Rails.application.config.controller_paths.each do |load_path|
- load_path = File.expand_path(load_path)
- Dir["#{load_path}/**/*_controller.rb"].collect do |path|
- namespaces << File.dirname(path).sub(/#{load_path}\/?/, '')
- end
- end
- end
-
namespaces.delete('')
namespaces
end
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index c4b0455c2a..af13f2cd3e 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -276,9 +276,11 @@ module ActionView #:nodoc:
@config = nil
@formats = formats
@assigns = assigns_for_first_render.each { |key, value| instance_variable_set("@#{key}", value) }
- @_controller = controller
@helpers = self.class.helpers || Module.new
- @_content_for = Hash.new {|h,k| h[k] = ActionView::SafeBuffer.new }
+
+ @_controller = controller
+ @_content_for = Hash.new {|h,k| h[k] = ActionView::SafeBuffer.new }
+ @_virtual_path = nil
self.view_paths = view_paths
end
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
index 3d088678fc..080eb87445 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -3,7 +3,7 @@ require 'active_support/benchmarkable'
module ActionView #:nodoc:
module Helpers #:nodoc:
autoload :ActiveModelHelper, 'action_view/helpers/active_model_helper'
- autoload :AjaxHelper, 'action_view/helpers/ajax_helper'
+ autoload :AjaxHelperCompat, 'action_view/helpers/ajax_helper'
autoload :AssetTagHelper, 'action_view/helpers/asset_tag_helper'
autoload :AtomFeedHelper, 'action_view/helpers/atom_feed_helper'
autoload :CacheHelper, 'action_view/helpers/cache_helper'
@@ -15,12 +15,11 @@ module ActionView #:nodoc:
autoload :FormTagHelper, 'action_view/helpers/form_tag_helper'
autoload :JavaScriptHelper, 'action_view/helpers/javascript_helper'
autoload :NumberHelper, 'action_view/helpers/number_helper'
- autoload :PrototypeHelper, 'action_view/helpers/prototype_helper'
+ autoload :AjaxHelper, 'action_view/helpers/ajax_helper'
autoload :RawOutputHelper, 'action_view/helpers/raw_output_helper'
autoload :RecordIdentificationHelper, 'action_view/helpers/record_identification_helper'
autoload :RecordTagHelper, 'action_view/helpers/record_tag_helper'
autoload :SanitizeHelper, 'action_view/helpers/sanitize_helper'
- autoload :ScriptaculousHelper, 'action_view/helpers/scriptaculous_helper'
autoload :TagHelper, 'action_view/helpers/tag_helper'
autoload :TextHelper, 'action_view/helpers/text_helper'
autoload :TranslationHelper, 'action_view/helpers/translation_helper'
@@ -48,12 +47,11 @@ module ActionView #:nodoc:
include FormTagHelper
include JavaScriptHelper
include NumberHelper
- include PrototypeHelper
+ include AjaxHelperCompat
include RawOutputHelper
include RecordIdentificationHelper
include RecordTagHelper
include SanitizeHelper
- include ScriptaculousHelper
include TagHelper
include TextHelper
include TranslationHelper
diff --git a/actionpack/lib/action_view/helpers/ajax_helper.rb b/actionpack/lib/action_view/helpers/ajax_helper.rb
index 9cc2acc239..169a803848 100644
--- a/actionpack/lib/action_view/helpers/ajax_helper.rb
+++ b/actionpack/lib/action_view/helpers/ajax_helper.rb
@@ -1,68 +1,713 @@
module ActionView
module Helpers
module AjaxHelper
- include UrlHelper
-
- def link_to_remote(name, url, options = {})
- html = options.delete(:html) || {}
-
- update = options.delete(:update)
- if update.is_a?(Hash)
- html["data-update-success"] = update[:success]
- html["data-update-failure"] = update[:failure]
- else
- html["data-update-success"] = update
+ # Included for backwards compatibility / RJS functionality
+ # Rails classes should not be aware of individual JS frameworks
+ include PrototypeHelper
+
+ # Returns a form that will allow the unobtrusive JavaScript drivers to submit the
+ # form dynamically. The default driver behaviour is an XMLHttpRequest in the background
+ # instead of the regular POST arrangement. Even though it's using JavaScript to serialize
+ # the form elements, the form submission will work just like a regular submission as
+ # viewed by the receiving side (all elements available in <tt>params</tt>). The options
+ # for specifying the target with <tt>:url</tt> anddefining callbacks is the same as +link_to_remote+.
+ #
+ # === Resource
+ #
+ # Example:
+ #
+ # # Generates:
+ # # <form action='/authors'
+ # # data-remote='true'
+ # # class='new_author'
+ # # id='create-author'
+ # # method='post'> ... </form>
+ # #
+ # <% remote_form_for(@record, {:html => { :id => 'create-author' }}) do |f| %>
+ # ...
+ # <% end %>
+ #
+ # This will expand to be the same as:
+ #
+ # <% remote_form_for :post, @post, :url => post_path(@post),
+ # :html => { :method => :put,
+ # :class => "edit_post",
+ # :id => "edit_post_45" } do |f| %>
+ # ...
+ # <% end %>
+ #
+ # === Nested Resource
+ #
+ # Example:
+ # # Generates:
+ # # <form action='/authors/1/articles'
+ # # data-remote="true"
+ # # class='new_article'
+ # # method='post'
+ # # id='new_article'></form>
+ # #
+ # <% remote_form_for([@author, @article]) do |f| %>
+ # ...
+ # <% end %>
+ #
+ # This will expand to be the same as:
+ #
+ # <% remote_form_for :article, @article, :url => author_article_path(@author, @article),
+ # :html => { :method => :put,
+ # :class => "new_article",
+ # :id => "new_comment" } do |f| %>
+ # ...
+ # <% end %>
+ #
+ # If you don't need to attach a form to a resource, then check out form_remote_tag.
+ #
+ # See FormHelper#form_for for additional semantics.
+ def remote_form_for(record_or_name_or_array, *args, &proc)
+ options = args.extract_options!
+
+ if confirm = options.delete(:confirm)
+ add_confirm_to_attributes!(options, confirm)
end
- html["data-update-position"] = options.delete(:position)
- html["data-method"] = options.delete(:method)
- html["data-remote"] = "true"
-
- html.merge!(options)
+ object_name = extract_object_name_for_form!(args, options, record_or_name_or_array)
+
+ concat(form_remote_tag(options))
+ fields_for(object_name, *(args << options), &proc)
+ concat('</form>'.html_safe!)
+ end
+ alias_method :form_remote_for, :remote_form_for
+
+ # Returns a form tag that will allow the unobtrusive JavaScript drivers to submit the
+ # form dynamically. The default JavaScript driver behaviour is an XMLHttpRequest
+ # in the background instead of the regular POST arrangement. Even though it's using
+ # JavaScript to serialize the form elements, the form submission will work just like
+ # a regular submission as viewed by the receiving side (all elements available in
+ # <tt>params</tt>). The options for specifying the target with <tt>:url</tt> and
+ # defining callbacks is the same as +link_to_remote+.
+ #
+ # A "fall-through" target for browsers that doesn't do JavaScript can be
+ # specified with the <tt>:action</tt>/<tt>:method</tt> options on <tt>:html</tt>.
+ #
+ # Example:
+ #
+ # # Generates:
+ # # <form action="http://www.example.com/fast"
+ # # method="post"
+ # # data-remote="true"
+ # # data-update-success="glass_of_beer"></form>
+ # #
+ # form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }) {}
+ #
+ # The Hash passed to the <tt>:html</tt> key is equivalent to the options (2nd)
+ # argument in the FormTagHelper.form_tag method.
+ #
+ # By default the fall-through action is the same as the one specified in
+ # the <tt>:url</tt> (and the default method is <tt>:post</tt>).
+ #
+ # form_remote_tag also takes a block, like form_tag:
+ # # Generates:
+ # # <form action='/posts'
+ # # method='post'
+ # # data-remote='true'>
+ # # <input name="commit" type="submit" value="Save" />
+ # # </form>
+ # #
+ # <% form_remote_tag :url => '/posts' do -%>
+ # <%= submit_tag 'Save' %>
+ # <% end -%>
+ #
+ # # Generates:
+ # # <form action="http://www.example.com/fast"
+ # # method="post"
+ # # data-remote="true"
+ # # data-update-success="glass_of_beer">Hello world!</form>
+ # #
+ # <% form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }) do -%>
+ # "Hello world!"
+ # <% end -%>
+ #
+ def form_remote_tag(options = {}, &block)
+ html_options = options.delete(:callbacks)
+
+ attributes = {}
+ attributes.merge!(extract_remote_attributes!(options))
+ attributes.merge!(html_options) if html_options
+ attributes.merge!(options)
+ attributes.delete(:builder)
+
+ form_tag(attributes.delete(:action) || attributes.delete("data-url"), attributes, &block)
+ end
+
+ # Returns a link that will allow unobtrusive JavaScript to dynamical adjust its
+ # behaviour. The default behaviour is an XMLHttpRequest in the background instead
+ # of the regular GET arrangement. The result of that request can then be inserted
+ # into a DOM object whose id can be specified with <tt>options[:update]</tt>. Usually,
+ # the result would be a partial prepared by the controller with render :partial.
+ #
+ # Examples:
+ #
+ # # Generates:
+ # # <a href="#"
+ # # data-remote="true"
+ # # data-url="http://www.example.com/whatnot"
+ # # data-method="delete"
+ # # rel="nofollow">Remove Author</a>
+ # #
+ # link_to_remote("Remove Author", { :url => { :action => "whatnot" },
+ # :method => "delete"})
+ #
+ #
+ # You can override the generated HTML options by specifying a hash in
+ # <tt>options[:html]</tt>.
+ #
+ # # Generates:
+ # # <a class="fine"
+ # # href="#"
+ # # data-remote="true"
+ # # data-url="http://www.example.com/whatnot"
+ # # data-method="delete"
+ # # rel="nofollow">Remove Author</a>
+ # #
+ # link_to_remote("Remove Author", { :url => { :action => "whatnot" },
+ # :method => "delete",
+ # :html => { :class => "fine" }})
+ #
+ #
+ # You can also specify a hash for <tt>options[:update]</tt> to allow for
+ # easy redirection of output to an other DOM element if a server-side
+ # error occurs:
+ #
+ # Example:
+ # # Generates:
+ # #
+ # # <a href="#"
+ # # data-url="http://www.example.com/destroy"
+ # # data-update-success="posts"
+ # # data-update-failure="error"
+ # # data-remote="true">Delete this Post</a>'
+ # #
+ # link_to_remote "Delete this post",
+ # :url => { :action => "destroy"},
+ # :update => { :success => "posts", :failure => "error" }
+ #
+ # Optionally, you can use the <tt>options[:position]</tt> parameter to
+ # influence how the target DOM element is updated. It must be one of
+ # <tt>:before</tt>, <tt>:top</tt>, <tt>:bottom</tt>, or <tt>:after</tt>.
+ #
+ # Example:
+ # # Generates:
+ # # <a href="#"
+ # # data-remote="true"
+ # # data-url="http://www.example.com/whatnot"
+ # # data-update-position="bottom">Remove Author</a>
+ # #
+ # link_to_remote("Remove Author", :url => { :action => "whatnot" }, :position => :bottom)
+ #
+ #
+ # The method used is by default POST. You can also specify GET or you
+ # can simulate PUT or DELETE over POST. All specified with <tt>options[:method]</tt>
+ #
+ # Example:
+ # # Generates:
+ # # <a href='#'
+ # # data-url='/person/4'
+ # # rel='nofollow'
+ # # data-remote='true'
+ # # data-method='delete'>Destroy</a>
+ # #
+ # link_to_remote "Destroy", :url => person_url(:id => person), :method => :delete
+ #
+ # By default, these remote requests are processed asynchronous during
+ # which various JavaScript callbacks can be triggered (for progress
+ # indicators and the likes). All callbacks get access to the
+ # <tt>request</tt> object, which holds the underlying XMLHttpRequest.
+ #
+ # To access the server response, use <tt>request.responseText</tt>, to
+ # find out the HTTP status, use <tt>request.status</tt>.
+ #
+ # Example:
+ # # Generates:
+ # #
+ # # <a href='#'
+ # # data-url='http://www.example.com/undo?n=5'
+ # # data-oncomplete='undoRequestCompleted(request)'
+ # # data-remote='true'>undo</a>
+ # #
+ # link_to_remote "undo",
+ # :url => { :controller => "words", :action => "undo", :n => word_counter },
+ # :complete => "undoRequestCompleted(request)"
+ #
+ # The callbacks that may be specified are (in order):
+ #
+ # <tt>:loading</tt>:: Called when the remote document is being
+ # loaded with data by the browser.
+ # <tt>:loaded</tt>:: Called when the browser has finished loading
+ # the remote document.
+ # <tt>:interactive</tt>:: Called when the user can interact with the
+ # remote document, even though it has not
+ # finished loading.
+ # <tt>:success</tt>:: Called when the XMLHttpRequest is completed,
+ # and the HTTP status code is in the 2XX range.
+ # <tt>:failure</tt>:: Called when the XMLHttpRequest is completed,
+ # and the HTTP status code is not in the 2XX
+ # range.
+ # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete
+ # (fires after success/failure if they are
+ # present).
+ #
+ # You can further refine <tt>:success</tt> and <tt>:failure</tt> by
+ # adding additional callbacks for specific status codes.
+ #
+ # Example:
+ #
+ # # Generates:
+ # # <a href='/testing/action'
+ # # date-remote='true'
+ # # data-failure="function(request){alert('HTTP Error '+ request.status +'+!');return false}"
+ # # data-404="function(request){alert('Not found...? Wrong URL...?')}"> Hello</a>
+ # #
+ # link_to_remote word,
+ # :url => { :action => "action" },
+ # 404 => "alert('Not found...? Wrong URL...?')",
+ # :failure => "alert('HTTP Error ' + request.status + '!')"
+ #
+ # A status code callback overrides the success/failure handlers if
+ # present.
+ #
+ # If you for some reason or another need synchronous processing (that'll
+ # block the browser while the request is happening), you can specify
+ # <tt>options[:type] = :synchronous</tt>.
+ #
+ # You can customize further browser side call logic by passing in
+ # JavaScript code snippets via some optional parameters. In their order
+ # of use these are:
+ #
+ # <tt>:confirm</tt>:: Adds confirmation dialog.
+ # <tt>:condition</tt>:: Perform remote request conditionally
+ # by this expression. Use this to
+ # describe browser-side conditions when
+ # request should not be initiated.
+ # <tt>:before</tt>:: Called before request is initiated.
+ # <tt>:after</tt>:: Called immediately after request was
+ # initiated and before <tt>:loading</tt>.
+ # <tt>:submit</tt>:: Specifies the DOM element ID that's used
+ # as the parent of the form elements. By
+ # default this is the current form, but
+ # it could just as well be the ID of a
+ # table row or any other DOM element.
+ # <tt>:with</tt>:: A JavaScript expression specifying
+ # the parameters for the XMLHttpRequest.
+ # Any expressions should return a valid
+ # URL query string.
+ #
+ # Example:
+ #
+ # :with => "'name=' + $('name').value"
+ #
+ # You can generate a link that uses the UJS drivers in the general case, while
+ # degrading gracefully to plain link behavior in the absence of
+ # JavaScript by setting <tt>html_options[:href]</tt> to an alternate URL.
+ # Note the extra curly braces around the <tt>options</tt> hash separate
+ # it as the second parameter from <tt>html_options</tt>, the third.
+ #
+ # Example:
+ #
+ # # Generates:
+ # # <a href='/posts/1'
+ # # rel='nofollow'
+ # # data-remote='true'
+ # # data-method='delete'> Delete this post</a>
+ # #
+ # link_to_remote "Delete this post",
+ # { :update => "posts", :url => { :action => "destroy", :id => post.id } }
+ #
+ def link_to_remote(name, options, html_options = {})
+ attributes = {}
+
+ attributes.merge!(:rel => "nofollow") if options[:method] && options[:method].to_s.downcase == "delete"
+ attributes.merge!(extract_remote_attributes!(options))
- url = url_for(url) if url.is_a?(Hash)
- link_to(name, url, html)
+ if confirm = options.delete(:confirm)
+ add_confirm_to_attributes!(attributes, confirm)
+ end
+
+ attributes.merge!(html_options)
+ href = html_options[:href].nil? ? "#" : html_options[:href]
+ attributes.merge!(:href => href)
+
+ content_tag(:a, name, attributes)
+ end
+
+ # Returns an input of type button, which allows the unobtrusive JavaScript driver
+ # to dynamically adjust its behaviour. The default driver behaviour is to call a
+ # remote action via XMLHttpRequest in the background.
+ # The options for specifying the target with :url and defining callbacks is the same
+ # as link_to_remote.
+ #
+ # Example:
+ #
+ # # Generates:
+ # # <input class="fine"
+ # # type="button"
+ # # value="Remote outpost"
+ # # data-remote="true"
+ # # data-url="http://www.example.com/whatnot" />
+ # #
+ # button_to_remote("Remote outpost", { :url => { :action => "whatnot" }}, { :class => "fine" })
+ #
+ def button_to_remote(name, options = {}, html_options = {})
+ attributes = html_options.merge!(:type => "button", :value => name)
+
+ if confirm = options.delete(:confirm)
+ add_confirm_to_attributes!(attributes, confirm)
+ end
+
+ if disable_with = options.delete(:disable_with)
+ add_disable_with_to_attributes!(attributes, disable_with)
+ end
+
+ attributes.merge!(extract_remote_attributes!(options))
+
+ tag(:input, attributes)
+ end
+
+ # Returns an input tag of type button, with the element name of +name+ and a value (i.e., display text)
+ # of +value+ which will allow the unobtrusive JavaScript driver to dynamically adjust its behaviour
+ # The default behaviour is to call a remote action via XMLHttpRequest in the background.
+ #
+ # request that reloads the page.
+ #
+ # # Create a button that submits to the create action
+ # #
+ # # Generates:
+ # # <input name='create_btn'
+ # # type='button'
+ # # value='Create'
+ # # data-remote='true'
+ # # data-url='/create' />
+ # #
+ # <%= submit_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %>
+ #
+ # # Submit to the remote action update and update the DIV succeed or fail based
+ # # on the success or failure of the request
+ # #
+ # # Generates:
+ # # <input name='update_btn'
+ # # type='button'
+ # # value='Update'
+ # # date-remote-submit='true'
+ # # data-url='/testing/update'
+ # # data-success='succeed'
+ # # data-failure='fail' />
+ # #
+ # <%= submit_to_remote 'update_btn', 'Update', :url => { :action => 'update' },
+ # :update => { :success => "succeed", :failure => "fail" }
+ #
+ # <tt>options</tt> argument is the same as in form_remote_tag.
+ def submit_to_remote(name, value, options = {})
+ html_options = options.delete(:html) || {}
+ html_options.merge!(:name => name, :value => value, :type => "button")
+
+ attributes = extract_remote_attributes!(options)
+ attributes.merge!(html_options)
+ attributes["data-remote-submit"] = true
+ attributes.delete("data-remote")
+
+ tag(:input, attributes)
+ end
+
+ # Periodically provides the UJS driver with the information to call the specified
+ # url (<tt>options[:url]</tt>) every <tt>options[:frequency]</tt> seconds (default is 10). Usually used to
+ # update a specified div (<tt>options[:update]</tt>) with the results
+ # of the remote call. The options for specifying the target with <tt>:url</tt>
+ # and defining callbacks is the same as link_to_remote.
+ # Examples:
+ # # Call get_averages and put its results in 'avg' every 10 seconds
+ # # Generates:
+ # # <script data-periodical='true'
+ # # data-url='/get_averages'
+ # # type='application/json'
+ # # data-update-success='avg'
+ # # data-frequency='10'></script>
+ # #
+ # periodically_call_remote(:url => { :action => 'get_averages' }, :update => 'avg')
+ #
+ # # Call invoice every 10 seconds with the id of the customer
+ # # If it succeeds, update the invoice DIV; if it fails, update the error DIV
+ # # Generates:
+ # # <script data-periodical='true'
+ # # data-url='/invoice/1'
+ # # type='application/json'
+ # # data-update-success='invoice'
+ # # data-update-failure='error'
+ # # data-frequency='10'></script>"
+ # #
+ # periodically_call_remote(:url => { :action => 'invoice', :id => 1 },
+ # :update => { :success => "invoice", :failure => "error" }
+ #
+ # # Call update every 20 seconds and update the new_block DIV
+ # # Generates:
+ # # <script data-periodical='true'
+ # # data-url='update'
+ # # type='application/json'
+ # # data-update-success='news_block'
+ # # data-frequency='20'></script>
+ # #
+ # periodically_call_remote(:url => 'update', :frequency => '20', :update => 'news_block')
+ #
+ def periodically_call_remote(options = {})
+ attributes = extract_observer_attributes!(options)
+ attributes["data-periodical"] = true
+ attributes["data-frequency"] ||= 10
+
+ # periodically_call_remote does not need data-observe=true
+ attributes.delete('data-observe')
+
+ script_decorator(attributes).html_safe!
+ end
+
+ # Observes the field with the DOM ID specified by +field_id+ and calls a
+ # callback when its contents have changed. The default callback is an
+ # Ajax call. By default the value of the observed field is sent as a
+ # parameter with the Ajax call.
+ #
+ # Example:
+ # # Generates:
+ # # "<script type='text/javascript'
+ # # data-observe='true'
+ # # data-observed='suggest'
+ # # data-frequency='0.25'
+ # # type='application/json'
+ # # data-url='/find_suggestion'
+ # # data-update-success='suggest'
+ # # data-with='q'></script>"
+ # #
+ # <%= observe_field :suggest, :url => { :action => :find_suggestion },
+ # :frequency => 0.25,
+ # :update => :suggest,
+ # :with => 'q'
+ # %>
+ #
+ # Required +options+ are either of:
+ # <tt>:url</tt>:: +url_for+-style options for the action to call
+ # when the field has changed.
+ # <tt>:function</tt>:: Instead of making a remote call to a URL, you
+ # can specify javascript code to be called instead.
+ # Note that the value of this option is used as the
+ # *body* of the javascript function, a function definition
+ # with parameters named element and value will be generated for you
+ # for example:
+ # observe_field("glass", :frequency => 1, :function => "alert('Element changed')")
+ # will generate:
+ # new Form.Element.Observer('glass', 1, function(element, value) {alert('Element changed')})
+ # The element parameter is the DOM element being observed, and the value is its value at the
+ # time the observer is triggered.
+ #
+ # Additional options are:
+ # <tt>:frequency</tt>:: The frequency (in seconds) at which changes to
+ # this field will be detected. Not setting this
+ # option at all or to a value equal to or less than
+ # zero will use event based observation instead of
+ # time based observation.
+ # <tt>:update</tt>:: Specifies the DOM ID of the element whose
+ # innerHTML should be updated with the
+ # XMLHttpRequest response text.
+ # <tt>:with</tt>:: A JavaScript expression specifying the parameters
+ # for the XMLHttpRequest. The default is to send the
+ # key and value of the observed field. Any custom
+ # expressions should return a valid URL query string.
+ # The value of the field is stored in the JavaScript
+ # variable +value+.
+ #
+ # Examples
+ #
+ # :with => "'my_custom_key=' + value"
+ # :with => "'person[name]=' + prompt('New name')"
+ # :with => "Form.Element.serialize('other-field')"
+ #
+ # Finally
+ # :with => 'name'
+ # is shorthand for
+ # :with => "'name=' + value"
+ # This essentially just changes the key of the parameter.
+ #
+ # Additionally, you may specify any of the options documented in the
+ # <em>Common options</em> section at the top of this document.
+ #
+ # Example:
+ #
+ # # Sends params: {:title => 'Title of the book'} when the book_title input
+ # # field is changed.
+ # observe_field 'book_title',
+ # :url => 'http://example.com/books/edit/1',
+ # :with => 'title'
+ #
+ #
+ def observe_field(name, options = {})
+ html_options = options.delete(:callbacks)
+
+ options[:observed] = name
+ attributes = extract_observer_attributes!(options)
+ attributes.merge!(html_options) if html_options
+
+ script_decorator(attributes).html_safe!
+ end
+
+ # Observes the form with the DOM ID specified by +form_id+ and calls a
+ # callback when its contents have changed. The default callback is an
+ # Ajax call. By default all fields of the observed field are sent as
+ # parameters with the Ajax call.
+ #
+ # The +options+ for +observe_form+ are the same as the options for
+ # +observe_field+. The JavaScript variable +value+ available to the
+ # <tt>:with</tt> option is set to the serialized form by default.
+ def observe_form(name, options = {})
+ html_options = options.delete(:callbacks)
+
+ options[:observed] = name
+ attributes = extract_observer_attributes!(options)
+ attributes.merge!(html_options) if html_options
+
+ script_decorator(attributes).html_safe!
+ end
+
+ def script_decorator(options)
+ attributes = %w(type="application/json")
+ attributes += options.map{|k, v| k + '="' + v.to_s + '"'}
+ "<script " + attributes.join(" ") + "></script>"
+ end
+
+ private
+
+ def extract_remote_attributes!(options)
+ attributes = options.delete(:html) || {}
+
+ attributes.merge!(extract_update_attributes!(options))
+ attributes.merge!(extract_request_attributes!(options))
+ attributes["data-remote"] = true
+
+ if submit = options.delete(:submit)
+ attributes["data-submit"] = submit
+ end
+
+ attributes
+ end
+
+ def extract_request_attributes!(options)
+ attributes = {}
+ if method = options.delete(:method)
+ attributes["data-method"] = method.to_s
+ end
+
+ if type = options.delete(:type)
+ attributes["data-remote-type"] = type.to_s
+ end
+
+ url_options = options.delete(:url)
+ url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash)
+ attributes["data-url"] = escape_javascript(url_for(url_options)) if url_options
+
+ purge_unused_attributes!(attributes)
+ end
+
+ def extract_update_attributes!(options)
+ attributes = {}
+ update = options.delete(:update)
+ if update.is_a?(Hash)
+ attributes["data-update-success"] = update[:success]
+ attributes["data-update-failure"] = update[:failure]
+ else
+ attributes["data-update-success"] = update
+ end
+
+ if position = options.delete(:position)
+ attributes["data-update-position"] = position.to_s
+ end
+
+ purge_unused_attributes!(attributes)
+ end
+
+ def extract_observer_attributes!(options)
+ callback = options.delete(:function)
+ frequency = options.delete(:frequency) || 10
+
+
+ attributes = extract_remote_attributes!(options)
+ attributes["data-observe"] = true
+ attributes["data-observed"] = options.delete(:observed)
+ attributes["data-onobserve"] = callback if callback
+ attributes["data-frequency"] = frequency if frequency && frequency.to_f != 0
+ attributes.delete("data-remote")
+
+ purge_unused_attributes!(attributes)
+ end
+
+ def purge_unused_attributes!(attributes)
+ attributes.delete_if {|key, value| value.nil? }
+ attributes
+ end
+ end
+
+ # TODO: All evaled goes here per wycat
+ module AjaxHelperCompat
+ include AjaxHelper
+
+ def link_to_remote(name, options, html_options = {})
+ set_callbacks(options, html_options)
+ set_with_and_condition_attributes(options, html_options)
+ super
end
def button_to_remote(name, options = {}, html_options = {})
- url = options.delete(:url)
- url = url_for(url) if url.is_a?(Hash)
-
- html_options.merge!(:type => "button", :value => name,
- :"data-url" => url)
-
- tag(:input, html_options)
+ set_callbacks(options, html_options)
+ set_with_and_condition_attributes(options, html_options)
+ super
+ end
+
+ def form_remote_tag(options, &block)
+ html = {}
+ set_callbacks(options, html)
+ set_with_and_condition_attributes(options, html)
+ options.merge!(:callbacks => html)
+ super
+ end
+
+ def observe_field(name, options = {})
+ html = {}
+ set_with_and_condition_attributes(options, html)
+ options.merge!(:callbacks => html)
+ super
end
- module Rails2Compatibility
+ def observe_form(name, options = {})
+ html = {}
+ set_with_and_condition_attributes(options, html)
+ options.merge!(:callbacks => html)
+ super
+ end
+
+ private
def set_callbacks(options, html)
- [:complete, :failure, :success, :interactive, :loaded, :loading].each do |type|
- html["data-#{type}-code"] = options.delete(type.to_sym)
+ [:before, :after, :uninitialized, :complete, :failure, :success, :interactive, :loaded, :loading].each do |type|
+ html["data-on#{type}"] = options.delete(type.to_sym)
end
options.each do |option, value|
if option.is_a?(Integer)
- html["data-#{option}-code"] = options.delete(option)
+ html["data-on#{option}"] = options.delete(option)
end
end
end
-
- def link_to_remote(name, url, options = nil)
- if !options && url.is_a?(Hash) && url.key?(:url)
- url, options = url.delete(:url), url
+
+ def set_with_and_condition_attributes(options, html)
+ if with = options.delete(:with)
+ html["data-with"] = with
+ end
+
+ if condition = options.delete(:condition)
+ html["data-condition"] = condition
end
-
- set_callbacks(options, options[:html] ||= {})
-
- super
- end
-
- def button_to_remote(name, options = {}, html_options = {})
- set_callbacks(options, html_options)
- super
end
- end
-
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 157deebe40..b81925c7e7 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -262,23 +262,8 @@ module ActionView
# FormTagHelper#form_tag.
def form_for(record_or_name_or_array, *args, &proc)
raise ArgumentError, "Missing block" unless block_given?
-
options = args.extract_options!
-
- case record_or_name_or_array
- when String, Symbol
- object_name = record_or_name_or_array
- when Array
- object = record_or_name_or_array.last
- object_name = ActionController::RecordIdentifier.singular_class_name(object)
- apply_form_for_options!(record_or_name_or_array, options)
- args.unshift object
- else
- object = record_or_name_or_array
- object_name = ActionController::RecordIdentifier.singular_class_name(object)
- apply_form_for_options!([object], options)
- args.unshift object
- end
+ object_name = extract_object_name_for_form!(args, options, record_or_name_or_array)
concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}))
fields_for(object_name, *(args << options), &proc)
@@ -742,6 +727,25 @@ module ActionView
def radio_button(object_name, method, tag_value, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
end
+
+ private
+ def extract_object_name_for_form!(args, options, record_or_name_or_array)
+ case record_or_name_or_array
+ when String, Symbol
+ object_name = record_or_name_or_array
+ when Array
+ object = record_or_name_or_array.last
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
+ apply_form_for_options!(record_or_name_or_array, options)
+ args.unshift object
+ else
+ object = record_or_name_or_array
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
+ apply_form_for_options!([object], options)
+ args.unshift object
+ end
+ object_name
+ end
end
module InstanceTagMethods #:nodoc:
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 048bedc7ba..ebce5c1513 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -352,33 +352,24 @@ module ActionView
# # => <input disabled="disabled" name="commit" type="submit" value="Save edits" />
#
# submit_tag "Complete sale", :disable_with => "Please wait..."
- # # => <input name="commit" onclick="this.disabled=true;this.value='Please wait...';this.form.submit();"
+ # # => <input name="commit" data-disable-with="Please wait..."
# # type="submit" value="Complete sale" />
#
# submit_tag nil, :class => "form_submit"
# # => <input class="form_submit" name="commit" type="submit" />
#
# submit_tag "Edit", :disable_with => "Editing...", :class => "edit-button"
- # # => <input class="edit-button" onclick="this.disabled=true;this.value='Editing...';this.form.submit();"
+ # # => <input class="edit-button" data-disable-with="Editing..."
# # name="commit" type="submit" value="Edit" />
def submit_tag(value = "Save changes", options = {})
options.stringify_keys!
if disable_with = options.delete("disable_with")
- disable_with = "this.value='#{disable_with}'"
- disable_with << ";#{options.delete('onclick')}" if options['onclick']
-
- options["onclick"] = "if (window.hiddenCommit) { window.hiddenCommit.setAttribute('value', this.value); }"
- options["onclick"] << "else { hiddenCommit = document.createElement('input');hiddenCommit.type = 'hidden';"
- options["onclick"] << "hiddenCommit.value = this.value;hiddenCommit.name = this.name;this.form.appendChild(hiddenCommit); }"
- options["onclick"] << "this.setAttribute('originalValue', this.value);this.disabled = true;#{disable_with};"
- options["onclick"] << "result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());"
- options["onclick"] << "if (result == false) { this.value = this.getAttribute('originalValue');this.disabled = false; }return result;"
+ add_disable_with_to_attributes!(options, disable_with)
end
if confirm = options.delete("confirm")
- options["onclick"] ||= 'return true;'
- options["onclick"] = "if (!#{confirm_javascript_function(confirm)}) return false; #{options['onclick']}"
+ add_confirm_to_attributes!(options, confirm)
end
tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
@@ -411,8 +402,7 @@ module ActionView
options.stringify_keys!
if confirm = options.delete("confirm")
- options["onclick"] ||= ''
- options["onclick"] += "return #{confirm_javascript_function(confirm)};"
+ add_confirm_to_attributes!(options, confirm)
end
tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options.stringify_keys)
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index 8f64acf102..ee6481b86d 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -39,7 +39,7 @@ module ActionView
JAVASCRIPT_PATH = File.join(File.dirname(__FILE__), 'javascripts')
end
- include PrototypeHelper
+ include AjaxHelperCompat
# Returns a link of the given +name+ that will trigger a JavaScript +function+ using the
# onclick handler and return false after the fact.
@@ -188,6 +188,48 @@ module ActionView
end
protected
+ def convert_options_to_javascript!(html_options, url = '')
+ confirm, popup = html_options.delete("confirm"), html_options.delete("popup")
+
+ method, href = html_options.delete("method"), html_options['href']
+
+ if popup && method
+ raise ActionView::ActionViewError, "You can't use :popup and :method in the same link"
+ elsif confirm && popup
+ add_confirm_to_attributes!(html_options, confirm)
+ html_options["data-popup"] = popup_attributes(popup)
+ elsif confirm && method
+ add_confirm_to_attributes!(html_options, confirm)
+ add_method_to_attributes!(html_options, method, url)
+ elsif confirm
+ add_confirm_to_attributes!(html_options, confirm)
+ elsif method
+ add_method_to_attributes!(html_options, method, url)
+ elsif popup
+ html_options["data-popup"] = popup_attributes(popup)
+ end
+ end
+
+ def add_confirm_to_attributes!(html_options, confirm)
+ html_options["data-confirm"] = confirm if confirm
+ end
+
+ def add_method_to_attributes!(html_options, method, url = nil)
+ html_options["rel"] = "nofollow" if method.to_s.downcase == "delete"
+ html_options["data-method"] = method
+ if url.size > 0
+ html_options["data-url"] = url
+ end
+ end
+
+ def add_disable_with_to_attributes!(html_options, disable_with)
+ html_options["data-disable-with"] = disable_with if disable_with
+ end
+
+ def popup_attributes(popup)
+ popup.is_a?(Array) ? "{title: '#{popup.first}', options: '#{popup.last}'}" : "true"
+ end
+
def options_for_javascript(options)
if options.empty?
'{}'
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
index ff7bc3b34e..d861810f19 100644
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ b/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -1,7 +1,7 @@
require 'set'
require 'active_support/json'
-require 'active_support/core_ext/object/extending'
require 'active_support/core_ext/object/returning'
+require 'action_view/helpers/scriptaculous_helper'
module ActionView
module Helpers
@@ -28,40 +28,6 @@ module ActionView
# ActionView::Helpers::JavaScriptHelper for more information on including
# this and other JavaScript files in your Rails templates.)
#
- # Now you're ready to call a remote action either through a link...
- #
- # link_to_remote "Add to cart",
- # :url => { :action => "add", :id => product.id },
- # :update => { :success => "cart", :failure => "error" }
- #
- # ...through a form...
- #
- # <% form_remote_tag :url => '/shipping' do -%>
- # <div><%= submit_tag 'Recalculate Shipping' %></div>
- # <% end -%>
- #
- # ...periodically...
- #
- # periodically_call_remote(:url => 'update', :frequency => '5', :update => 'ticker')
- #
- # ...or through an observer (i.e., a form or field that is observed and calls a remote
- # action when changed).
- #
- # <%= observe_field(:searchbox,
- # :url => { :action => :live_search }),
- # :frequency => 0.5,
- # :update => :hits,
- # :with => 'query'
- # %>
- #
- # 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
@@ -108,6 +74,8 @@ module ActionView
# See JavaScriptGenerator for information on updating multiple elements
# on the page in an Ajax response.
module PrototypeHelper
+ include ScriptaculousHelper
+
unless const_defined? :CALLBACKS
CALLBACKS = Set.new([ :create, :uninitialized, :loading, :loaded,
:interactive, :complete, :failure, :success ] +
@@ -117,325 +85,6 @@ module ActionView
:form, :with, :update, :script, :type ]).merge(CALLBACKS)
end
- # Returns a link to a remote action defined by <tt>options[:url]</tt>
- # (using the url_for format) that's called in the background using
- # XMLHttpRequest. The result of that request can then be inserted into a
- # DOM object whose id can be specified with <tt>options[:update]</tt>.
- # Usually, the result would be a partial prepared by the controller with
- # render :partial.
- #
- # Examples:
- # # Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true});
- # # return false;">Delete this post</a>
- # link_to_remote "Delete this post", :update => "posts",
- # :url => { :action => "destroy", :id => post.id }
- #
- # # Generates: <a href="#" onclick="new Ajax.Updater('emails', '/mail/list_emails', {asynchronous:true, evalScripts:true});
- # # return false;"><img alt="Refresh" src="/images/refresh.png?" /></a>
- # link_to_remote(image_tag("refresh"), :update => "emails",
- # :url => { :action => "list_emails" })
- #
- # You can override the generated HTML options by specifying a hash in
- # <tt>options[:html]</tt>.
- #
- # link_to_remote "Delete this post", :update => "posts",
- # :url => post_url(@post), :method => :delete,
- # :html => { :class => "destructive" }
- #
- # You can also specify a hash for <tt>options[:update]</tt> to allow for
- # easy redirection of output to an other DOM element if a server-side
- # error occurs:
- #
- # Example:
- # # Generates: <a href="#" onclick="new Ajax.Updater({success:'posts',failure:'error'}, '/blog/destroy/5',
- # # {asynchronous:true, evalScripts:true}); return false;">Delete this post</a>
- # link_to_remote "Delete this post",
- # :url => { :action => "destroy", :id => post.id },
- # :update => { :success => "posts", :failure => "error" }
- #
- # Optionally, you can use the <tt>options[:position]</tt> parameter to
- # influence how the target DOM element is updated. It must be one of
- # <tt>:before</tt>, <tt>:top</tt>, <tt>:bottom</tt>, or <tt>:after</tt>.
- #
- # The method used is by default POST. You can also specify GET or you
- # can simulate PUT or DELETE over POST. All specified with <tt>options[:method]</tt>
- #
- # Example:
- # # Generates: <a href="#" onclick="new Ajax.Request('/person/4', {asynchronous:true, evalScripts:true, method:'delete'});
- # # return false;">Destroy</a>
- # link_to_remote "Destroy", :url => person_url(:id => person), :method => :delete
- #
- # By default, these remote requests are processed asynchronous during
- # which various JavaScript callbacks can be triggered (for progress
- # indicators and the likes). All callbacks get access to the
- # <tt>request</tt> object, which holds the underlying XMLHttpRequest.
- #
- # To access the server response, use <tt>request.responseText</tt>, to
- # find out the HTTP status, use <tt>request.status</tt>.
- #
- # Example:
- # # Generates: <a href="#" onclick="new Ajax.Request('/words/undo?n=33', {asynchronous:true, evalScripts:true,
- # # onComplete:function(request){undoRequestCompleted(request)}}); return false;">hello</a>
- # word = 'hello'
- # link_to_remote word,
- # :url => { :action => "undo", :n => word_counter },
- # :complete => "undoRequestCompleted(request)"
- #
- # The callbacks that may be specified are (in order):
- #
- # <tt>:loading</tt>:: Called when the remote document is being
- # loaded with data by the browser.
- # <tt>:loaded</tt>:: Called when the browser has finished loading
- # the remote document.
- # <tt>:interactive</tt>:: Called when the user can interact with the
- # remote document, even though it has not
- # finished loading.
- # <tt>:success</tt>:: Called when the XMLHttpRequest is completed,
- # and the HTTP status code is in the 2XX range.
- # <tt>:failure</tt>:: Called when the XMLHttpRequest is completed,
- # and the HTTP status code is not in the 2XX
- # range.
- # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete
- # (fires after success/failure if they are
- # present).
- #
- # You can further refine <tt>:success</tt> and <tt>:failure</tt> by
- # adding additional callbacks for specific status codes.
- #
- # Example:
- # # Generates: <a href="#" onclick="new Ajax.Request('/testing/action', {asynchronous:true, evalScripts:true,
- # # on404:function(request){alert('Not found...? Wrong URL...?')},
- # # onFailure:function(request){alert('HTTP Error ' + request.status + '!')}}); return false;">hello</a>
- # link_to_remote word,
- # :url => { :action => "action" },
- # 404 => "alert('Not found...? Wrong URL...?')",
- # :failure => "alert('HTTP Error ' + request.status + '!')"
- #
- # A status code callback overrides the success/failure handlers if
- # present.
- #
- # If you for some reason or another need synchronous processing (that'll
- # block the browser while the request is happening), you can specify
- # <tt>options[:type] = :synchronous</tt>.
- #
- # You can customize further browser side call logic by passing in
- # JavaScript code snippets via some optional parameters. In their order
- # of use these are:
- #
- # <tt>:confirm</tt>:: Adds confirmation dialog.
- # <tt>:condition</tt>:: Perform remote request conditionally
- # by this expression. Use this to
- # describe browser-side conditions when
- # request should not be initiated.
- # <tt>:before</tt>:: Called before request is initiated.
- # <tt>:after</tt>:: Called immediately after request was
- # initiated and before <tt>:loading</tt>.
- # <tt>:submit</tt>:: Specifies the DOM element ID that's used
- # as the parent of the form elements. By
- # default this is the current form, but
- # it could just as well be the ID of a
- # table row or any other DOM element.
- # <tt>:with</tt>:: A JavaScript expression specifying
- # the parameters for the XMLHttpRequest.
- # Any expressions should return a valid
- # URL query string.
- #
- # Example:
- #
- # :with => "'name=' + $('name').value"
- #
- # You can generate a link that uses AJAX in the general case, while
- # degrading gracefully to plain link behavior in the absence of
- # JavaScript by setting <tt>html_options[:href]</tt> to an alternate URL.
- # Note the extra curly braces around the <tt>options</tt> hash separate
- # it as the second parameter from <tt>html_options</tt>, the third.
- #
- # Example:
- # link_to_remote "Delete this post",
- # { :update => "posts", :url => { :action => "destroy", :id => post.id } },
- # :href => url_for(:action => "destroy", :id => post.id)
- def link_to_remote(name, options = {}, html_options = nil)
- link_to_function(name, remote_function(options), html_options || options.delete(:html))
- end
-
- # Creates a button with an onclick event which calls a remote action
- # via XMLHttpRequest
- # The options for specifying the target with :url
- # and defining callbacks is the same as link_to_remote.
- def button_to_remote(name, options = {}, html_options = {})
- button_to_function(name, remote_function(options), html_options)
- end
-
- # Periodically calls the specified url (<tt>options[:url]</tt>) every
- # <tt>options[:frequency]</tt> seconds (default is 10). Usually used to
- # update a specified div (<tt>options[:update]</tt>) with the results
- # of the remote call. The options for specifying the target with <tt>:url</tt>
- # and defining callbacks is the same as link_to_remote.
- # Examples:
- # # Call get_averages and put its results in 'avg' every 10 seconds
- # # Generates:
- # # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages',
- # # {asynchronous:true, evalScripts:true})}, 10)
- # periodically_call_remote(:url => { :action => 'get_averages' }, :update => 'avg')
- #
- # # Call invoice every 10 seconds with the id of the customer
- # # If it succeeds, update the invoice DIV; if it fails, update the error DIV
- # # Generates:
- # # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'},
- # # '/testing/invoice/16', {asynchronous:true, evalScripts:true})}, 10)
- # periodically_call_remote(:url => { :action => 'invoice', :id => customer.id },
- # :update => { :success => "invoice", :failure => "error" }
- #
- # # Call update every 20 seconds and update the new_block DIV
- # # Generates:
- # # new PeriodicalExecuter(function() {new Ajax.Updater('news_block', 'update', {asynchronous:true, evalScripts:true})}, 20)
- # periodically_call_remote(:url => 'update', :frequency => '20', :update => 'news_block')
- #
- def periodically_call_remote(options = {})
- frequency = options[:frequency] || 10 # every ten seconds by default
- code = "new PeriodicalExecuter(function() {#{remote_function(options)}}, #{frequency})"
- javascript_tag(code)
- end
-
- # Returns a form tag that will submit using XMLHttpRequest in the
- # background instead of the regular reloading POST arrangement. Even
- # though it's using JavaScript to serialize the form elements, the form
- # submission will work just like a regular submission as viewed by the
- # receiving side (all elements available in <tt>params</tt>). The options for
- # specifying the target with <tt>:url</tt> and defining callbacks is the same as
- # +link_to_remote+.
- #
- # A "fall-through" target for browsers that doesn't do JavaScript can be
- # specified with the <tt>:action</tt>/<tt>:method</tt> options on <tt>:html</tt>.
- #
- # Example:
- # # Generates:
- # # <form action="/some/place" method="post" onsubmit="new Ajax.Request('',
- # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">
- # form_remote_tag :html => { :action =>
- # url_for(:controller => "some", :action => "place") }
- #
- # The Hash passed to the <tt>:html</tt> key is equivalent to the options (2nd)
- # argument in the FormTagHelper.form_tag method.
- #
- # By default the fall-through action is the same as the one specified in
- # the <tt>:url</tt> (and the default method is <tt>:post</tt>).
- #
- # form_remote_tag also takes a block, like form_tag:
- # # Generates:
- # # <form action="/" method="post" onsubmit="new Ajax.Request('/',
- # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)});
- # # return false;"> <div><input name="commit" type="submit" value="Save" /></div>
- # # </form>
- # <% form_remote_tag :url => '/posts' do -%>
- # <div><%= submit_tag 'Save' %></div>
- # <% end -%>
- def form_remote_tag(options = {}, &block)
- options[:form] = true
-
- options[:html] ||= {}
- options[:html][:onsubmit] =
- (options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") +
- "#{remote_function(options)}; return false;"
-
- form_tag(options[:html].delete(:action) || url_for(options[:url]), options[:html], &block)
- end
-
- # Creates a form that will submit using XMLHttpRequest in the background
- # instead of the regular reloading POST arrangement and a scope around a
- # specific resource that is used as a base for questioning about
- # values for the fields.
- #
- # === Resource
- #
- # Example:
- # <% remote_form_for(@post) do |f| %>
- # ...
- # <% end %>
- #
- # This will expand to be the same as:
- #
- # <% remote_form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
- # ...
- # <% end %>
- #
- # === Nested Resource
- #
- # Example:
- # <% remote_form_for([@post, @comment]) do |f| %>
- # ...
- # <% end %>
- #
- # This will expand to be the same as:
- #
- # <% remote_form_for :comment, @comment, :url => post_comment_path(@post, @comment), :html => { :method => :put, :class => "edit_comment", :id => "edit_comment_45" } do |f| %>
- # ...
- # <% end %>
- #
- # If you don't need to attach a form to a resource, then check out form_remote_tag.
- #
- # See FormHelper#form_for for additional semantics.
- def remote_form_for(record_or_name_or_array, *args, &proc)
- options = args.extract_options!
-
- case record_or_name_or_array
- when String, Symbol
- object_name = record_or_name_or_array
- when Array
- object = record_or_name_or_array.last
- object_name = ActionController::RecordIdentifier.singular_class_name(object)
- apply_form_for_options!(record_or_name_or_array, options)
- args.unshift object
- else
- object = record_or_name_or_array
- object_name = ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array)
- apply_form_for_options!(object, options)
- args.unshift object
- end
-
- concat(form_remote_tag(options))
- fields_for(object_name, *(args << options), &proc)
- concat('</form>'.html_safe!)
- end
- alias_method :form_remote_for, :remote_form_for
-
- # Returns a button input tag with the element name of +name+ and a value (i.e., display text) of +value+
- # that will submit form using XMLHttpRequest in the background instead of a regular POST request that
- # reloads the page.
- #
- # # Create a button that submits to the create action
- # #
- # # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create',
- # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
- # # return false;" type="button" value="Create" />
- # <%= submit_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %>
- #
- # # Submit to the remote action update and update the DIV succeed or fail based
- # # on the success or failure of the request
- # #
- # # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'},
- # # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
- # # return false;" type="button" value="Update" />
- # <%= submit_to_remote 'update_btn', 'Update', :url => { :action => 'update' },
- # :update => { :success => "succeed", :failure => "fail" }
- #
- # <tt>options</tt> argument is the same as in form_remote_tag.
- def submit_to_remote(name, value, options = {})
- options[:with] ||= 'Form.serialize(this.form)'
-
- html_options = options.delete(:html) || {}
- html_options[:name] = name
-
- button_to_remote(value, options, html_options)
- end
-
- # Returns '<tt>eval(request.responseText)</tt>' which is the JavaScript function
- # that +form_remote_tag+ can call in <tt>:complete</tt> to evaluate a multiple
- # update return document using +update_element_function+ calls.
- def evaluate_remote_response
- "eval(request.responseText)"
- end
-
# Returns the JavaScript needed for a remote function.
# Takes the same arguments as link_to_remote.
#
@@ -477,99 +126,6 @@ module ActionView
return function
end
- # Observes the field with the DOM ID specified by +field_id+ and calls a
- # callback when its contents have changed. The default callback is an
- # Ajax call. By default the value of the observed field is sent as a
- # parameter with the Ajax call.
- #
- # Example:
- # # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest',
- # # '/testing/find_suggestion', {asynchronous:true, evalScripts:true, parameters:'q=' + value})})
- # <%= observe_field :suggest, :url => { :action => :find_suggestion },
- # :frequency => 0.25,
- # :update => :suggest,
- # :with => 'q'
- # %>
- #
- # Required +options+ are either of:
- # <tt>:url</tt>:: +url_for+-style options for the action to call
- # when the field has changed.
- # <tt>:function</tt>:: Instead of making a remote call to a URL, you
- # can specify javascript code to be called instead.
- # Note that the value of this option is used as the
- # *body* of the javascript function, a function definition
- # with parameters named element and value will be generated for you
- # for example:
- # observe_field("glass", :frequency => 1, :function => "alert('Element changed')")
- # will generate:
- # new Form.Element.Observer('glass', 1, function(element, value) {alert('Element changed')})
- # The element parameter is the DOM element being observed, and the value is its value at the
- # time the observer is triggered.
- #
- # Additional options are:
- # <tt>:frequency</tt>:: The frequency (in seconds) at which changes to
- # this field will be detected. Not setting this
- # option at all or to a value equal to or less than
- # zero will use event based observation instead of
- # time based observation.
- # <tt>:update</tt>:: Specifies the DOM ID of the element whose
- # innerHTML should be updated with the
- # XMLHttpRequest response text.
- # <tt>:with</tt>:: A JavaScript expression specifying the parameters
- # for the XMLHttpRequest. The default is to send the
- # key and value of the observed field. Any custom
- # expressions should return a valid URL query string.
- # The value of the field is stored in the JavaScript
- # variable +value+.
- #
- # Examples
- #
- # :with => "'my_custom_key=' + value"
- # :with => "'person[name]=' + prompt('New name')"
- # :with => "Form.Element.serialize('other-field')"
- #
- # Finally
- # :with => 'name'
- # is shorthand for
- # :with => "'name=' + value"
- # This essentially just changes the key of the parameter.
- #
- # Additionally, you may specify any of the options documented in the
- # <em>Common options</em> section at the top of this document.
- #
- # Example:
- #
- # # Sends params: {:title => 'Title of the book'} when the book_title input
- # # field is changed.
- # observe_field 'book_title',
- # :url => 'http://example.com/books/edit/1',
- # :with => 'title'
- #
- #
- def observe_field(field_id, options = {})
- if options[:frequency] && options[:frequency] > 0
- build_observer('Form.Element.Observer', field_id, options)
- else
- build_observer('Form.Element.EventObserver', field_id, options)
- end
- end
-
- # Observes the form with the DOM ID specified by +form_id+ and calls a
- # callback when its contents have changed. The default callback is an
- # Ajax call. By default all fields of the observed field are sent as
- # parameters with the Ajax call.
- #
- # The +options+ for +observe_form+ are the same as the options for
- # +observe_field+. The JavaScript variable +value+ available to the
- # <tt>:with</tt> option is set to the serialized form by default.
- def observe_form(form_id, options = {})
- if options[:frequency]
- build_observer('Form.Observer', form_id, options)
- else
- build_observer('Form.EventObserver', form_id, options)
- end
- end
-
# All the methods were moved to GeneratorMethods so that
# #include_helpers_from_context has nothing to overwrite.
class JavaScriptGenerator #:nodoc:
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index 35c431d78d..ad18339c60 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -25,11 +25,15 @@ module ActionView
end
alias :l :localize
-
private
+
def scope_key_by_partial(key)
if key.to_s.first == "."
- template.path_without_format_and_extension.gsub(%r{/_?}, ".") + key.to_s
+ if @_virtual_path
+ @_virtual_path.gsub(%r{/_?}, ".") + key.to_s
+ else
+ raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
+ end
else
key
end
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 511386fede..4424dbba42 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -305,12 +305,11 @@ module ActionView
request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
end
- if confirm = html_options.delete("confirm")
- html_options["onclick"] = "return #{confirm_javascript_function(confirm)};"
- end
url = options.is_a?(String) ? options : self.url_for(options)
name ||= url
+
+ convert_options_to_javascript!(html_options, url)
html_options.merge!("type" => "submit", "value" => name)
@@ -563,55 +562,6 @@ module ActionView
end
private
- def convert_options_to_javascript!(html_options, url = '')
- confirm, popup = html_options.delete("confirm"), html_options.delete("popup")
-
- method, href = html_options.delete("method"), html_options['href']
-
- html_options["onclick"] = case
- when popup && method
- raise ActionView::ActionViewError, "You can't use :popup and :method in the same link"
- when confirm && popup
- "if (#{confirm_javascript_function(confirm)}) { #{popup_javascript_function(popup)} };return false;"
- when confirm && method
- "if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method, url, href)} };return false;"
- when confirm
- "return #{confirm_javascript_function(confirm)};"
- when method
- "#{method_javascript_function(method, url, href)}return false;"
- when popup
- "#{popup_javascript_function(popup)}return false;"
- else
- html_options["onclick"]
- end
- end
-
- def confirm_javascript_function(confirm)
- "confirm('#{escape_javascript(confirm)}')"
- end
-
- def popup_javascript_function(popup)
- popup.is_a?(Array) ? "window.open(this.href,'#{popup.first}','#{popup.last}');" : "window.open(this.href);"
- end
-
- def method_javascript_function(method, url = '', href = nil)
- action = (href && url.size > 0) ? "'#{url}'" : 'this.href'
- submit_function =
- "var f = document.createElement('form'); f.style.display = 'none'; " +
- "this.parentNode.appendChild(f); f.method = 'POST'; f.action = #{action};"
-
- unless method == :post
- submit_function << "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); "
- submit_function << "m.setAttribute('name', '_method'); m.setAttribute('value', '#{method}'); f.appendChild(m);"
- end
-
- if protect_against_forgery?
- submit_function << "var s = document.createElement('input'); s.setAttribute('type', 'hidden'); "
- submit_function << "s.setAttribute('name', '#{request_forgery_protection_token}'); s.setAttribute('value', '#{escape_javascript form_authenticity_token}'); f.appendChild(s);"
- end
- submit_function << "f.submit();"
- end
-
# Processes the +html_options+ hash, converting the boolean
# attributes from true/false form into the form required by
# HTML/XHTML. (An attribute is considered to be boolean if
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index 968dc7b25e..d9e2557d89 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -3,7 +3,7 @@ require "rails"
module ActionView
class Railtie < Rails::Railtie
- plugin_name :action_view
+ railtie_name :action_view
require "action_view/railties/subscriber"
subscriber ActionView::Railties::Subscriber.new
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index adaf6544a7..cd6b1930a1 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -87,9 +87,9 @@ module ActionView
source = <<-end_src
def #{method_name}(local_assigns)
- old_output_buffer = output_buffer;#{locals_code};#{code}
+ _old_virtual_path, @_virtual_path = @_virtual_path, #{@details[:virtual_path].inspect};_old_output_buffer = output_buffer;#{locals_code};#{code}
ensure
- self.output_buffer = old_output_buffer
+ @_virtual_path, self.output_buffer = _old_virtual_path, _old_output_buffer
end
end_src
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index c6a17907ff..340a6afe5e 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -117,15 +117,18 @@ module ActionView
# # :api: plugin
def path_to_details(path)
# [:erb, :format => :html, :locale => :en, :partial => true/false]
- if m = path.match(%r'(?:^|/)(_)?[\w-]+((?:\.[\w-]+)*)\.(\w+)$')
- partial = m[1] == '_'
- details = (m[2]||"").split('.').reject { |e| e.empty? }
- handler = Template.handler_class_for_extension(m[3])
+ if m = path.match(%r'((^|.*/)(_)?[\w-]+)((?:\.[\w-]+)*)\.(\w+)$')
+ partial = m[3] == '_'
+ details = (m[4]||"").split('.').reject { |e| e.empty? }
+ handler = Template.handler_class_for_extension(m[5])
format = Mime[details.last] && details.pop.to_sym
locale = details.last && details.pop.to_sym
- return handler, :format => format, :locale => locale, :partial => partial
+ virtual_path = (m[1].gsub("#{@path}/", "") << details.join("."))
+
+ return handler, :format => format, :locale => locale, :partial => partial,
+ :virtual_path => virtual_path
end
end
end