diff options
Diffstat (limited to 'actionpack/lib/action_view/helpers')
12 files changed, 229 insertions, 1313 deletions
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index f6b2d4f3f4..e859b3ae49 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -274,7 +274,11 @@ module ActionView # The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and # plugin authors are encouraged to do so. def image_path(source) - asset_paths.compute_public_path(source, 'images') + if config.use_sprockets + asset_path(source) + else + asset_paths.compute_public_path(source, 'images') + end end alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route @@ -289,7 +293,11 @@ module ActionView # video_path("/trailers/hd.avi") # => /trailers/hd.avi # video_path("http://www.railsapplication.com/vid/hd.avi") # => http://www.railsapplication.com/vid/hd.avi def video_path(source) - asset_paths.compute_public_path(source, 'videos') + if config.use_sprockets + asset_path(source) + else + asset_paths.compute_public_path(source, 'videos') + end end alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route @@ -304,7 +312,11 @@ module ActionView # audio_path("/sounds/horse.wav") # => /sounds/horse.wav # audio_path("http://www.railsapplication.com/sounds/horse.wav") # => http://www.railsapplication.com/sounds/horse.wav def audio_path(source) - asset_paths.compute_public_path(source, 'audios') + if config.use_sprockets + asset_path(source) + else + asset_paths.compute_public_path(source, 'audios') + end end alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb index 52eb43a1cd..e4662a2919 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb @@ -8,9 +8,11 @@ module ActionView module AssetTagHelper class AssetIncludeTag - attr_reader :config, :asset_paths + include TagHelper + attr_reader :config, :asset_paths class_attribute :expansions + def self.inherited(base) base.expansions = { } end @@ -56,7 +58,6 @@ module ActionView end end - private def path_to_asset(source, include_host = true) 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..955634bb19 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 @@ -26,18 +26,18 @@ module ActionView # roots. Rewrite the asset path for cache-busting asset ids. Include # asset host, if configured, with the correct request protocol. def compute_public_path(source, dir, ext = nil, include_host = true) + source = source.to_s return source if is_uri?(source) 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) + source = rewrite_asset_path(source, dir) - has_request = controller.respond_to?(:request) - source = rewrite_relative_url_root(source, controller.config.relative_url_root) if has_request && include_host - source = rewrite_host_and_protocol(source, has_request) if include_host + if controller && include_host + has_request = controller.respond_to?(:request) + source = rewrite_relative_url_root(source, controller.config.relative_url_root) if has_request + source = rewrite_host_and_protocol(source, has_request) + end source end @@ -73,6 +73,8 @@ module ActionView # Break out the asset path rewrite in case plugins wish to put the asset id # someplace other than the query string. def rewrite_asset_path(source, path = nil) + path = config.asset_path + if path && path.respond_to?(:call) return path.call(source) elsif path && path.is_a?(String) 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..07ff49659a 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 @@ -1,6 +1,5 @@ require 'active_support/concern' require 'active_support/core_ext/file' -require 'action_view/helpers/tag_helper' require 'action_view/helpers/asset_tag_helpers/asset_include_tag' module ActionView @@ -8,8 +7,6 @@ module ActionView module AssetTagHelper class JavascriptIncludeTag < AssetIncludeTag - include TagHelper - def asset_name 'javascript' end @@ -86,99 +83,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 + asset_path(source, 'js') + 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..c3dcd410bb 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 @@ -1,6 +1,5 @@ require 'active_support/concern' require 'active_support/core_ext/file' -require 'action_view/helpers/tag_helper' require 'action_view/helpers/asset_tag_helpers/asset_include_tag' module ActionView @@ -8,8 +7,6 @@ module ActionView module AssetTagHelper class StylesheetIncludeTag < AssetIncludeTag - include TagHelper - def asset_name 'stylesheet' end @@ -63,7 +60,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 + asset_path(source, 'css') + 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 +137,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/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 9ac7dff1ec..0139714240 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -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/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 6dbd2e3e43..9277359d5c 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 { - # $("details").visualEffect("toggle_blind"); - # $("more_link").update("Show me less"); - # } - # catch (e) { - # alert('RJS error:\n\n' + e.toString()); - # alert('$(\"details\").visualEffect(\"toggle_blind\"); - # \n$(\"more_link\").update(\"Show me less\");'); - # throw e - # }; - # return false;">Show me more</a> - # - def link_to_function(name, *args, &block) - html_options = args.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 506db24dc2..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..947c827f3c --- /dev/null +++ b/actionpack/lib/action_view/helpers/sprockets_helper.rb @@ -0,0 +1,69 @@ +require 'uri' +require 'action_view/helpers/asset_tag_helpers/asset_paths' + +module ActionView + module Helpers + module SprocketsHelper + def asset_path(source, default_ext = nil) + sprockets_asset_paths.compute_public_path(source, 'assets', default_ext, true) + end + + def sprockets_javascript_include_tag(source, options = {}) + options = { + 'type' => "text/javascript", + 'src' => asset_path(source, 'js') + }.merge(options.stringify_keys) + + content_tag 'script', "", options + end + + def sprockets_stylesheet_link_tag(source, options = {}) + options = { + 'rel' => "stylesheet", + 'type' => "text/css", + 'media' => "screen", + 'href' => asset_path(source, 'css') + }.merge(options.stringify_keys) + + tag 'link', options + end + + private + + def sprockets_asset_paths + @sprockets_asset_paths ||= begin + config = self.config if respond_to?(:config) + controller = self.controller if respond_to?(:controller) + SprocketsHelper::AssetPaths.new(config, controller) + end + end + + class AssetPaths < ActionView::Helpers::AssetTagHelper::AssetPaths + def rewrite_asset_path(source, dir) + if source =~ /^\/#{dir}\/(.+)/ + assets.path($1, performing_caching?, dir) + else + source + end + end + + def rewrite_extension(source, dir, ext) + if ext && File.extname(source).empty? + "#{source}.#{ext}" + else + source + end + end + + def assets + Rails.application.assets + end + + # When included in Sprockets::Context, we need to ask the top-level config as the controller is not available + def performing_caching? + @config ? @config.perform_caching : Rails.application.config.action_controller.perform_caching + end + end + end + end +end
\ No newline at end of file |