aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_view/helpers
diff options
context:
space:
mode:
authorJosh Kalderimis <josh.kalderimis@gmail.com>2010-11-14 22:13:17 +0100
committerJosé Valim <jose.valim@gmail.com>2010-11-16 00:04:36 +0100
commit1c129f8f8ea5b10605ad77af6064c983584d463d (patch)
tree718d76b22c52fa863e61fcc39e4e8a4c22fce982 /actionpack/lib/action_view/helpers
parent91b0b6583428edaec1507c8dbfccfb2056bdedb4 (diff)
downloadrails-1c129f8f8ea5b10605ad77af6064c983584d463d.tar.gz
rails-1c129f8f8ea5b10605ad77af6064c983584d463d.tar.bz2
rails-1c129f8f8ea5b10605ad77af6064c983584d463d.zip
separated the asset id methods to a separate module, removed some dupliation with the various path methods, and moved the base asset tag methods to a base module so the asset id module can play nice with the path generation
Diffstat (limited to 'actionpack/lib/action_view/helpers')
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb409
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_id_caching.rb67
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/base_asset_helpers.rb330
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/helper_methods.rb67
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb13
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb13
6 files changed, 486 insertions, 413 deletions
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 858fff2882..065df849a4 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -2,8 +2,10 @@ require 'thread'
require 'cgi'
require 'action_view/helpers/url_helper'
require 'action_view/helpers/tag_helper'
+require 'action_view/helpers/asset_tag_helpers/base_asset_helpers'
require 'action_view/helpers/asset_tag_helpers/javascript_tag_helpers'
require 'action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers'
+require 'action_view/helpers/asset_tag_helpers/asset_id_caching'
require 'active_support/core_ext/file'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
@@ -197,413 +199,10 @@ module ActionView
# RewriteEngine On
# RewriteRule ^/release-\d+/(images|javascripts|stylesheets)/(.*)$ /$1/$2 [L]
module AssetTagHelper
+ include BaseAssetHelpers
include JavascriptTagHelpers
include StylesheetTagHelpers
-
- # You can enable or disable the asset tag timestamps cache.
- # With the cache enabled, the asset tag helper methods will make fewer
- # expensive file system calls. However this prevents you from modifying
- # any asset files while the server is running.
- #
- # ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
- mattr_accessor :cache_asset_timestamps
-
- # Returns a link tag that browsers and news readers can use to auto-detect
- # an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
- # <tt>:atom</tt>. Control the link options in url_for format using the
- # +url_options+. You can modify the LINK tag itself in +tag_options+.
- #
- # ==== Options
- # * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate"
- # * <tt>:type</tt> - Override the auto-generated mime type
- # * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
- #
- # ==== Examples
- # auto_discovery_link_tag # =>
- # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
- # auto_discovery_link_tag(:atom) # =>
- # <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
- # auto_discovery_link_tag(:rss, {:action => "feed"}) # =>
- # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
- # auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # =>
- # <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" />
- # auto_discovery_link_tag(:rss, {:controller => "news", :action => "feed"}) # =>
- # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" />
- # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "Example RSS"}) # =>
- # <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" />
- def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
- tag(
- "link",
- "rel" => tag_options[:rel] || "alternate",
- "type" => tag_options[:type] || Mime::Type.lookup_by_extension(type.to_s).to_s,
- "title" => tag_options[:title] || type.to_s.upcase,
- "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options
- )
- end
-
- # Web browsers cache favicons. If you just throw a <tt>favicon.ico</tt> into the document
- # root of your application and it changes later, clients that have it in their cache
- # won't see the update. Using this helper prevents that because it appends an asset ID:
- #
- # <%= favicon_link_tag %>
- #
- # generates
- #
- # <link href="/favicon.ico?4649789979" rel="shortcut icon" type="image/vnd.microsoft.icon" />
- #
- # You may specify a different file in the first argument:
- #
- # <%= favicon_link_tag 'favicon.ico' %>
- #
- # That's passed to +path_to_image+ as is, so it gives
- #
- # <link href="/images/favicon.ico?4649789979" rel="shortcut icon" type="image/vnd.microsoft.icon" />
- #
- # The helper accepts an additional options hash where you can override "rel" and "type".
- #
- # For example, Mobile Safari looks for a different LINK tag, pointing to an image that
- # will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad.
- # The following call would generate such a tag:
- #
- # <%= favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png' %>
- #
- def favicon_link_tag(source='/favicon.ico', options={})
- tag('link', {
- :rel => 'shortcut icon',
- :type => 'image/vnd.microsoft.icon',
- :href => path_to_image(source)
- }.merge(options.symbolize_keys))
- end
-
- # Computes the path to an image asset in the public images directory.
- # Full paths from the document root will be passed through.
- # Used internally by +image_tag+ to build the image path:
- #
- # image_path("edit") # => "/images/edit"
- # image_path("edit.png") # => "/images/edit.png"
- # image_path("icons/edit.png") # => "/images/icons/edit.png"
- # image_path("/icons/edit.png") # => "/icons/edit.png"
- # image_path("http://www.railsapplication.com/img/edit.png") # => "http://www.railsapplication.com/img/edit.png"
- #
- # If you have images as application resources this method may conflict with their named routes.
- # 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)
- compute_public_path(source, 'images')
- end
- alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
-
- # Computes the path to a video asset in the public videos directory.
- # Full paths from the document root will be passed through.
- # Used internally by +video_tag+ to build the video path.
- #
- # ==== Examples
- # video_path("hd") # => /videos/hd
- # video_path("hd.avi") # => /videos/hd.avi
- # video_path("trailers/hd.avi") # => /videos/trailers/hd.avi
- # 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)
- compute_public_path(source, 'videos')
- end
- alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route
-
- # Computes the path to an audio asset in the public audios directory.
- # Full paths from the document root will be passed through.
- # Used internally by +audio_tag+ to build the audio path.
- #
- # ==== Examples
- # audio_path("horse") # => /audios/horse
- # audio_path("horse.wav") # => /audios/horse.avi
- # audio_path("sounds/horse.wav") # => /audios/sounds/horse.avi
- # audio_path("/sounds/horse.wav") # => /sounds/horse.avi
- # audio_path("http://www.railsapplication.com/sounds/horse.wav") # => http://www.railsapplication.com/sounds/horse.wav
- def audio_path(source)
- compute_public_path(source, 'audios')
- end
- alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
-
- # Returns an html image tag for the +source+. The +source+ can be a full
- # path or a file that exists in your public images directory.
- #
- # ==== Options
- # You can add HTML attributes using the +options+. The +options+ supports
- # three additional keys for convenience and conformance:
- #
- # * <tt>:alt</tt> - If no alt text is given, the file name part of the
- # +source+ is used (capitalized and without the extension)
- # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
- # width="30" and height="45". <tt>:size</tt> will be ignored if the
- # value is not in the correct format.
- # * <tt>:mouseover</tt> - Set an alternate image to be used when the onmouseover
- # event is fired, and sets the original image to be replaced onmouseout.
- # This can be used to implement an easy image toggle that fires on onmouseover.
- #
- # ==== Examples
- # image_tag("icon") # =>
- # <img src="/images/icon" alt="Icon" />
- # image_tag("icon.png") # =>
- # <img src="/images/icon.png" alt="Icon" />
- # image_tag("icon.png", :size => "16x10", :alt => "Edit Entry") # =>
- # <img src="/images/icon.png" width="16" height="10" alt="Edit Entry" />
- # image_tag("/icons/icon.gif", :size => "16x16") # =>
- # <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
- # image_tag("/icons/icon.gif", :height => '32', :width => '32') # =>
- # <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
- # image_tag("/icons/icon.gif", :class => "menu_icon") # =>
- # <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
- # image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
- # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
- # image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
- # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
- def image_tag(source, options = {})
- options.symbolize_keys!
-
- src = options[:src] = path_to_image(source)
-
- unless src =~ /^cid:/
- options[:alt] = options.fetch(:alt){ File.basename(src, '.*').capitalize }
- end
-
- if size = options.delete(:size)
- options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
- end
-
- if mouseover = options.delete(:mouseover)
- options[:onmouseover] = "this.src='#{path_to_image(mouseover)}'"
- options[:onmouseout] = "this.src='#{src}'"
- end
-
- tag("img", options)
- end
-
- # Returns an html video tag for the +sources+. If +sources+ is a string,
- # a single video tag will be returned. If +sources+ is an array, a video
- # tag with nested source tags for each source will be returned. The
- # +sources+ can be full paths or files that exists in your public videos
- # directory.
- #
- # ==== Options
- # You can add HTML attributes using the +options+. The +options+ supports
- # two additional keys for convenience and conformance:
- #
- # * <tt>:poster</tt> - Set an image (like a screenshot) to be shown
- # before the video loads. The path is calculated like the +src+ of +image_tag+.
- # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
- # width="30" and height="45". <tt>:size</tt> will be ignored if the
- # value is not in the correct format.
- #
- # ==== Examples
- # video_tag("trailer") # =>
- # <video src="/videos/trailer" />
- # video_tag("trailer.ogg") # =>
- # <video src="/videos/trailer.ogg" />
- # video_tag("trailer.ogg", :controls => true, :autobuffer => true) # =>
- # <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" />
- # video_tag("trailer.m4v", :size => "16x10", :poster => "screenshot.png") # =>
- # <video src="/videos/trailer.m4v" width="16" height="10" poster="/images/screenshot.png" />
- # video_tag("/trailers/hd.avi", :size => "16x16") # =>
- # <video src="/trailers/hd.avi" width="16" height="16" />
- # video_tag("/trailers/hd.avi", :height => '32', :width => '32') # =>
- # <video height="32" src="/trailers/hd.avi" width="32" />
- # video_tag(["trailer.ogg", "trailer.flv"]) # =>
- # <video><source src="trailer.ogg" /><source src="trailer.ogg" /><source src="trailer.flv" /></video>
- # video_tag(["trailer.ogg", "trailer.flv"] :size => "160x120") # =>
- # <video height="120" width="160"><source src="trailer.ogg" /><source src="trailer.flv" /></video>
- def video_tag(sources, options = {})
- options.symbolize_keys!
-
- options[:poster] = path_to_image(options[:poster]) if options[:poster]
-
- if size = options.delete(:size)
- options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
- end
-
- if sources.is_a?(Array)
- content_tag("video", options) do
- sources.map { |source| tag("source", :src => source) }.join.html_safe
- end
- else
- options[:src] = path_to_video(sources)
- tag("video", options)
- end
- end
-
- # Returns an html audio tag for the +source+.
- # The +source+ can be full path or file that exists in
- # your public audios directory.
- #
- # ==== Examples
- # audio_tag("sound") # =>
- # <audio src="/audios/sound" />
- # audio_tag("sound.wav") # =>
- # <audio src="/audios/sound.wav" />
- # audio_tag("sound.wav", :autoplay => true, :controls => true) # =>
- # <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
- def audio_tag(source, options = {})
- options.symbolize_keys!
- options[:src] = path_to_audio(source)
- tag("audio", options)
- end
-
- private
-
- def rewrite_extension(source, dir, ext)
- source_ext = File.extname(source)
-
- if source_ext.empty?
- "#{source}.#{ext}"
- elsif ext != source_ext[1..-1]
- with_ext = "#{source}.#{ext}"
- with_ext if File.exist?(File.join(config.assets_dir, dir, with_ext))
- end || source
- end
-
- def rewrite_host_and_protocol(source, has_request)
- host = compute_asset_host(source)
- if has_request && host && !is_uri?(host)
- host = "#{controller.request.protocol}#{host}"
- end
- "#{host}#{source}"
- end
-
- def rewrite_relative_url_root(source, relative_url_root)
- relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source
- end
-
- # Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
- # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
- # 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)
- 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)
-
- 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
-
- source
- end
-
- def is_uri?(path)
- path =~ %r{^[-a-z]+://|^cid:}
- end
-
- # Pick an asset host for this source. Returns +nil+ if no host is set,
- # the host if no wildcard is set, the host interpolated with the
- # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
- # or the value returned from invoking the proc if it's a proc or the value from
- # invoking call if it's an object responding to call.
- def compute_asset_host(source)
- if host = config.asset_host
- if host.is_a?(Proc) || host.respond_to?(:call)
- case host.is_a?(Proc) ? host.arity : host.method(:call).arity
- when 2
- request = controller.respond_to?(:request) && controller.request
- host.call(source, request)
- else
- host.call(source)
- end
- else
- (host =~ /%d/) ? host % (source.hash % 4) : host
- end
- end
- end
-
- @@asset_timestamps_cache = {}
- @@asset_timestamps_cache_guard = Mutex.new
-
- # Use the RAILS_ASSET_ID environment variable or the source's
- # modification time as its cache-busting asset id.
- def rails_asset_id(source)
- if asset_id = ENV["RAILS_ASSET_ID"]
- asset_id
- else
- if @@cache_asset_timestamps && (asset_id = @@asset_timestamps_cache[source])
- asset_id
- else
- path = File.join(config.assets_dir, source)
- asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : ''
-
- if @@cache_asset_timestamps
- @@asset_timestamps_cache_guard.synchronize do
- @@asset_timestamps_cache[source] = asset_id
- end
- end
-
- asset_id
- end
- end
- end
-
- # 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)
- if path && path.respond_to?(:call)
- return path.call(source)
- elsif path && path.is_a?(String)
- return path % [source]
- end
-
- asset_id = rails_asset_id(source)
- if asset_id.empty?
- source
- else
- "#{source}?#{asset_id}"
- end
- end
-
- def determine_source(source, collection)
- case source
- when Symbol
- collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
- else
- source
- end
- end
-
- def join_asset_file_contents(paths)
- paths.collect { |path| File.read(asset_file_path!(path, true)) }.join("\n\n")
- end
-
- def write_asset_file_contents(joined_asset_path, asset_paths)
- FileUtils.mkdir_p(File.dirname(joined_asset_path))
- File.atomic_write(joined_asset_path) { |cache| cache.write(join_asset_file_contents(asset_paths)) }
-
- # Set mtime to the latest of the combined files to allow for
- # consistent ETag without a shared filesystem.
- mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max
- File.utime(mt, mt, joined_asset_path)
- end
-
- def asset_file_path(path)
- File.join(config.assets_dir, path.split('?').first)
- end
-
- def asset_file_path!(path, error_if_file_is_uri = false)
- if is_uri?(path)
- raise(Errno::ENOENT, "Asset file #{path} is uri and cannot be merged into single file") if error_if_file_is_uri
- else
- absolute_path = asset_file_path(path)
- raise(Errno::ENOENT, "Asset file not found at '#{absolute_path}'" ) unless File.exist?(absolute_path)
- return absolute_path
- end
- end
-
- def collect_asset_files(*path)
- dir = path.first
-
- Dir[File.join(*path.compact)].collect do |file|
- file[-(file.size - dir.size - 1)..-1].sub(/\.\w+$/, '')
- end.sort
- end
+ include AssetIdCaching
end
end
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_id_caching.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_id_caching.rb
new file mode 100644
index 0000000000..36fe961a72
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_id_caching.rb
@@ -0,0 +1,67 @@
+require 'active_support/concern'
+
+module ActionView
+ module Helpers
+ module AssetTagHelper
+
+ module AssetIdCaching
+ extend ActiveSupport::Concern
+
+ included do
+ # You can enable or disable the asset tag timestamps cache.
+ # With the cache enabled, the asset tag helper methods will make fewer
+ # expensive file system calls. However this prevents you from modifying
+ # any asset files while the server is running.
+ #
+ # ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
+ mattr_accessor :cache_asset_timestamps
+
+ private
+ mattr_accessor :asset_timestamps_cache
+ self.asset_timestamps_cache = {}
+
+ mattr_accessor :asset_timestamps_cache_guard
+ self.asset_timestamps_cache_guard = Mutex.new
+ end
+
+ private
+
+ # Use the RAILS_ASSET_ID environment variable or the source's
+ # modification time as its cache-busting asset id.
+ def rails_asset_id(source)
+ if asset_id = ENV["RAILS_ASSET_ID"]
+ asset_id
+ else
+ if self.cache_asset_timestamps && (asset_id = self.asset_timestamps_cache[source])
+ asset_id
+ else
+ path = File.join(config.assets_dir, source)
+ asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : ''
+
+ if self.cache_asset_timestamps
+ self.asset_timestamps_cache_guard.synchronize do
+ self.asset_timestamps_cache[source] = asset_id
+ end
+ end
+
+ asset_id
+ end
+ end
+ end
+
+ # Break out the asset path rewrite in case plugins wish to put the asset id
+ # someplace other than the query string.
+ # This is the default implementation
+ def handle_asset_id(source)
+ asset_id = rails_asset_id(source)
+ if asset_id.empty?
+ source
+ else
+ "#{source}?#{asset_id}"
+ end
+ end
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/base_asset_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/base_asset_helpers.rb
new file mode 100644
index 0000000000..7dbb0fa70f
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/base_asset_helpers.rb
@@ -0,0 +1,330 @@
+require 'active_support/concern'
+require 'action_view/helpers/asset_tag_helpers/helper_methods'
+
+module ActionView
+ module Helpers
+ module AssetTagHelper
+
+ module BaseAssetHelpers
+ extend HelperMethods
+ # Returns a link tag that browsers and news readers can use to auto-detect
+ # an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
+ # <tt>:atom</tt>. Control the link options in url_for format using the
+ # +url_options+. You can modify the LINK tag itself in +tag_options+.
+ #
+ # ==== Options
+ # * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate"
+ # * <tt>:type</tt> - Override the auto-generated mime type
+ # * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
+ #
+ # ==== Examples
+ # auto_discovery_link_tag # =>
+ # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
+ # auto_discovery_link_tag(:atom) # =>
+ # <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
+ # auto_discovery_link_tag(:rss, {:action => "feed"}) # =>
+ # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
+ # auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # =>
+ # <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" />
+ # auto_discovery_link_tag(:rss, {:controller => "news", :action => "feed"}) # =>
+ # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" />
+ # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "Example RSS"}) # =>
+ # <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" />
+ def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
+ tag(
+ "link",
+ "rel" => tag_options[:rel] || "alternate",
+ "type" => tag_options[:type] || Mime::Type.lookup_by_extension(type.to_s).to_s,
+ "title" => tag_options[:title] || type.to_s.upcase,
+ "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options
+ )
+ end
+
+ # Web browsers cache favicons. If you just throw a <tt>favicon.ico</tt> into the document
+ # root of your application and it changes later, clients that have it in their cache
+ # won't see the update. Using this helper prevents that because it appends an asset ID:
+ #
+ # <%= favicon_link_tag %>
+ #
+ # generates
+ #
+ # <link href="/favicon.ico?4649789979" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+ #
+ # You may specify a different file in the first argument:
+ #
+ # <%= favicon_link_tag 'favicon.ico' %>
+ #
+ # That's passed to +path_to_image+ as is, so it gives
+ #
+ # <link href="/images/favicon.ico?4649789979" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+ #
+ # The helper accepts an additional options hash where you can override "rel" and "type".
+ #
+ # For example, Mobile Safari looks for a different LINK tag, pointing to an image that
+ # will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad.
+ # The following call would generate such a tag:
+ #
+ # <%= favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png' %>
+ #
+ def favicon_link_tag(source='/favicon.ico', options={})
+ tag('link', {
+ :rel => 'shortcut icon',
+ :type => 'image/vnd.microsoft.icon',
+ :href => path_to_image(source)
+ }.merge(options.symbolize_keys))
+ end
+
+ # Computes the path to an image asset in the public images directory.
+ # Full paths from the document root will be passed through.
+ # Used internally by +image_tag+ to build the image path:
+ #
+ # image_path("edit") # => "/images/edit"
+ # image_path("edit.png") # => "/images/edit.png"
+ # image_path("icons/edit.png") # => "/images/icons/edit.png"
+ # image_path("/icons/edit.png") # => "/icons/edit.png"
+ # image_path("http://www.railsapplication.com/img/edit.png") # => "http://www.railsapplication.com/img/edit.png"
+ #
+ # If you have images as application resources this method may conflict with their named routes.
+ # The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and
+ # plugin authors are encouraged to do so.
+ asset_path :image
+
+ # Computes the path to a video asset in the public videos directory.
+ # Full paths from the document root will be passed through.
+ # Used internally by +video_tag+ to build the video path.
+ #
+ # ==== Examples
+ # video_path("hd") # => /videos/hd
+ # video_path("hd.avi") # => /videos/hd.avi
+ # video_path("trailers/hd.avi") # => /videos/trailers/hd.avi
+ # video_path("/trailers/hd.avi") # => /trailers/hd.avi
+ # video_path("http://www.railsapplication.com/vid/hd.avi") # => http://www.railsapplication.com/vid/hd.avi
+ asset_path :video
+
+ # Computes the path to an audio asset in the public audios directory.
+ # Full paths from the document root will be passed through.
+ # Used internally by +audio_tag+ to build the audio path.
+ #
+ # ==== Examples
+ # audio_path("horse") # => /audios/horse
+ # audio_path("horse.wav") # => /audios/horse.wav
+ # audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav
+ # audio_path("/sounds/horse.wav") # => /sounds/horse.wav
+ # audio_path("http://www.railsapplication.com/sounds/horse.wav") # => http://www.railsapplication.com/sounds/horse.wav
+ asset_path :audio
+
+ # Returns an html image tag for the +source+. The +source+ can be a full
+ # path or a file that exists in your public images directory.
+ #
+ # ==== Options
+ # You can add HTML attributes using the +options+. The +options+ supports
+ # three additional keys for convenience and conformance:
+ #
+ # * <tt>:alt</tt> - If no alt text is given, the file name part of the
+ # +source+ is used (capitalized and without the extension)
+ # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
+ # width="30" and height="45". <tt>:size</tt> will be ignored if the
+ # value is not in the correct format.
+ # * <tt>:mouseover</tt> - Set an alternate image to be used when the onmouseover
+ # event is fired, and sets the original image to be replaced onmouseout.
+ # This can be used to implement an easy image toggle that fires on onmouseover.
+ #
+ # ==== Examples
+ # image_tag("icon") # =>
+ # <img src="/images/icon" alt="Icon" />
+ # image_tag("icon.png") # =>
+ # <img src="/images/icon.png" alt="Icon" />
+ # image_tag("icon.png", :size => "16x10", :alt => "Edit Entry") # =>
+ # <img src="/images/icon.png" width="16" height="10" alt="Edit Entry" />
+ # image_tag("/icons/icon.gif", :size => "16x16") # =>
+ # <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
+ # image_tag("/icons/icon.gif", :height => '32', :width => '32') # =>
+ # <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
+ # image_tag("/icons/icon.gif", :class => "menu_icon") # =>
+ # <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
+ # image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
+ # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
+ # image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
+ # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
+ def image_tag(source, options = {})
+ options.symbolize_keys!
+
+ src = options[:src] = path_to_image(source)
+
+ unless src =~ /^cid:/
+ options[:alt] = options.fetch(:alt){ File.basename(src, '.*').capitalize }
+ end
+
+ if size = options.delete(:size)
+ options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
+ end
+
+ if mouseover = options.delete(:mouseover)
+ options[:onmouseover] = "this.src='#{path_to_image(mouseover)}'"
+ options[:onmouseout] = "this.src='#{src}'"
+ end
+
+ tag("img", options)
+ end
+
+ # Returns an html video tag for the +sources+. If +sources+ is a string,
+ # a single video tag will be returned. If +sources+ is an array, a video
+ # tag with nested source tags for each source will be returned. The
+ # +sources+ can be full paths or files that exists in your public videos
+ # directory.
+ #
+ # ==== Options
+ # You can add HTML attributes using the +options+. The +options+ supports
+ # two additional keys for convenience and conformance:
+ #
+ # * <tt>:poster</tt> - Set an image (like a screenshot) to be shown
+ # before the video loads. The path is calculated like the +src+ of +image_tag+.
+ # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
+ # width="30" and height="45". <tt>:size</tt> will be ignored if the
+ # value is not in the correct format.
+ #
+ # ==== Examples
+ # video_tag("trailer") # =>
+ # <video src="/videos/trailer" />
+ # video_tag("trailer.ogg") # =>
+ # <video src="/videos/trailer.ogg" />
+ # video_tag("trailer.ogg", :controls => true, :autobuffer => true) # =>
+ # <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" />
+ # video_tag("trailer.m4v", :size => "16x10", :poster => "screenshot.png") # =>
+ # <video src="/videos/trailer.m4v" width="16" height="10" poster="/images/screenshot.png" />
+ # video_tag("/trailers/hd.avi", :size => "16x16") # =>
+ # <video src="/trailers/hd.avi" width="16" height="16" />
+ # video_tag("/trailers/hd.avi", :height => '32', :width => '32') # =>
+ # <video height="32" src="/trailers/hd.avi" width="32" />
+ # video_tag(["trailer.ogg", "trailer.flv"]) # =>
+ # <video><source src="trailer.ogg" /><source src="trailer.ogg" /><source src="trailer.flv" /></video>
+ # video_tag(["trailer.ogg", "trailer.flv"] :size => "160x120") # =>
+ # <video height="120" width="160"><source src="trailer.ogg" /><source src="trailer.flv" /></video>
+ def video_tag(sources, options = {})
+ options.symbolize_keys!
+
+ options[:poster] = path_to_image(options[:poster]) if options[:poster]
+
+ if size = options.delete(:size)
+ options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
+ end
+
+ if sources.is_a?(Array)
+ content_tag("video", options) do
+ sources.map { |source| tag("source", :src => source) }.join.html_safe
+ end
+ else
+ options[:src] = path_to_video(sources)
+ tag("video", options)
+ end
+ end
+
+ # Returns an html audio tag for the +source+.
+ # The +source+ can be full path or file that exists in
+ # your public audios directory.
+ #
+ # ==== Examples
+ # audio_tag("sound") # =>
+ # <audio src="/audios/sound" />
+ # audio_tag("sound.wav") # =>
+ # <audio src="/audios/sound.wav" />
+ # audio_tag("sound.wav", :autoplay => true, :controls => true) # =>
+ # <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
+ def audio_tag(source, options = {})
+ options.symbolize_keys!
+ options[:src] = path_to_audio(source)
+ tag("audio", options)
+ end
+
+ private
+
+ def rewrite_extension(source, dir, ext)
+ source_ext = File.extname(source)
+
+ if source_ext.empty?
+ "#{source}.#{ext}"
+ elsif ext != source_ext[1..-1]
+ with_ext = "#{source}.#{ext}"
+ with_ext if File.exist?(File.join(config.assets_dir, dir, with_ext))
+ end || source
+ end
+
+ def rewrite_host_and_protocol(source, has_request)
+ host = compute_asset_host(source)
+ if has_request && host && !is_uri?(host)
+ host = "#{controller.request.protocol}#{host}"
+ end
+ "#{host}#{source}"
+ end
+
+ def rewrite_relative_url_root(source, relative_url_root)
+ relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source
+ end
+
+ # Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
+ # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
+ # 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)
+ 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)
+
+ 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
+
+ source
+ end
+
+ def is_uri?(path)
+ path =~ %r{^[-a-z]+://|^cid:}
+ end
+
+ # Pick an asset host for this source. Returns +nil+ if no host is set,
+ # the host if no wildcard is set, the host interpolated with the
+ # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
+ # or the value returned from invoking the proc if it's a proc or the value from
+ # invoking call if it's an object responding to call.
+ def compute_asset_host(source)
+ if host = config.asset_host
+ if host.is_a?(Proc) || host.respond_to?(:call)
+ case host.is_a?(Proc) ? host.arity : host.method(:call).arity
+ when 2
+ request = controller.respond_to?(:request) && controller.request
+ host.call(source, request)
+ else
+ host.call(source)
+ end
+ else
+ (host =~ /%d/) ? host % (source.hash % 4) : host
+ end
+ end
+ end
+
+ # 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)
+ if path && path.respond_to?(:call)
+ return path.call(source)
+ elsif path && path.is_a?(String)
+ return path % [source]
+ else
+ handle_asset_id(source)
+ end
+ end
+
+ # This is the default implementation
+ def handle_asset_id(source)
+ source
+ end
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/helper_methods.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/helper_methods.rb
new file mode 100644
index 0000000000..348e33d535
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/helper_methods.rb
@@ -0,0 +1,67 @@
+require 'active_support/concern'
+require 'active_support/inflector'
+
+module ActionView
+ module Helpers
+ module AssetTagHelper
+
+ module HelperMethods
+ private
+ def asset_path(asset_type, extension = nil)
+ define_method("#{asset_type}_path") do |source|
+ compute_public_path(source, asset_type.to_s.pluralize, extension)
+ end
+ alias_method :"path_to_#{asset_type}", :"#{asset_type}_path" # aliased to avoid conflicts with a *_path named route
+ end
+ end
+
+ module SharedHelpers
+ def determine_source(source, collection)
+ case source
+ when Symbol
+ collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
+ else
+ source
+ end
+ end
+
+ def join_asset_file_contents(paths)
+ paths.collect { |path| File.read(asset_file_path!(path, true)) }.join("\n\n")
+ end
+
+ def write_asset_file_contents(joined_asset_path, asset_paths)
+ FileUtils.mkdir_p(File.dirname(joined_asset_path))
+ File.atomic_write(joined_asset_path) { |cache| cache.write(join_asset_file_contents(asset_paths)) }
+
+ # Set mtime to the latest of the combined files to allow for
+ # consistent ETag without a shared filesystem.
+ mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max
+ File.utime(mt, mt, joined_asset_path)
+ end
+
+ def asset_file_path(path)
+ File.join(config.assets_dir, path.split('?').first)
+ end
+
+ def asset_file_path!(path, error_if_file_is_uri = false)
+ if is_uri?(path)
+ raise(Errno::ENOENT, "Asset file #{path} is uri and cannot be merged into single file") if error_if_file_is_uri
+ else
+ absolute_path = asset_file_path(path)
+ raise(Errno::ENOENT, "Asset file not found at '#{absolute_path}'" ) unless File.exist?(absolute_path)
+ return absolute_path
+ end
+ end
+
+ def collect_asset_files(*path)
+ dir = path.first
+
+ Dir[File.join(*path.compact)].collect do |file|
+ file[-(file.size - dir.size - 1)..-1].sub(/\.\w+$/, '')
+ end.sort
+ end
+ end
+
+ end
+ end
+end \ No newline at end of file
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 ea8ac3f9a1..668a6136bc 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,8 +1,14 @@
+require 'active_support/concern'
+require 'action_view/helpers/asset_tag_helpers/helper_methods'
+
module ActionView
module Helpers
module AssetTagHelper
+
module JavascriptTagHelpers
extend ActiveSupport::Concern
+ extend HelperMethods
+ include SharedHelpers
included do
mattr_accessor :javascript_expansions
@@ -37,10 +43,7 @@ module ActionView
# javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
# 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)
- compute_public_path(source, 'javascripts', 'js')
- end
- alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
+ asset_path :javascript, 'js'
# Returns an HTML script tag for each of the +sources+ provided. You
# can pass in the filename (.js extension is optional) of JavaScript files
@@ -174,7 +177,9 @@ module ActionView
end
return sources
end
+
end
+
end
end
end \ No newline at end of file
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 6ae4792b70..1f18e9dfce 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,8 +1,14 @@
+require 'active_support/concern'
+require 'action_view/helpers/asset_tag_helpers/helper_methods'
+
module ActionView
module Helpers
module AssetTagHelper
+
module StylesheetTagHelpers
extend ActiveSupport::Concern
+ extend HelperMethods
+ include SharedHelpers
included do
mattr_accessor :stylesheet_expansions
@@ -37,10 +43,8 @@ module ActionView
# stylesheet_path "/dir/style.css" # => /dir/style.css
# 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)
- compute_public_path(source, 'stylesheets', 'css')
- end
- alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
+ asset_path :stylesheet, 'css'
+
# Returns a stylesheet link tag for the sources specified as arguments. If
# you don't specify an extension, <tt>.css</tt> will be appended automatically.
@@ -159,6 +163,7 @@ module ActionView
end
end
+
end
end
end \ No newline at end of file