From fac9218f785414a1167e7a8007fba880d9af4362 Mon Sep 17 00:00:00 2001 From: Juan Pablo Balarini Date: Tue, 25 Apr 2023 14:08:43 +0000 Subject: [PATCH] Add a picture_tag helper --- actionview/CHANGELOG.md | 48 +++++++++++ .../action_view/helpers/asset_tag_helper.rb | 57 +++++++++++++ .../test/template/asset_tag_helper_test.rb | 82 +++++++++++++++++++ 3 files changed, 187 insertions(+) diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 3bfff1e5e0..b984203ef6 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,51 @@ +* Add support for the HTML picture tag. It supports passing a String, an Array or a Block. + Supports passing properties directly to the img tag via the `:image` key. + Since the picture tag requires an img tag, the last element you provide will be used for the img tag. + For complete control over the picture tag, a block can be passed, which will populate the contents of the tag accordingly. + + Can be used like this for a single source: + ```erb + <%= picture_tag("picture.webp") %> + ``` + which will generate the following: + ```html + + + + ``` + + For multiple sources: + ```erb + <%= picture_tag("picture.webp", "picture.png", :class => "mt-2", :image => { alt: "Image", class: "responsive-img" }) %> + ``` + will generate: + ```html + + + + Image + + ``` + + Full control via a block: + ```erb + <%= picture_tag(:class => "my-class") do %> + <%= tag(:source, :srcset => image_path("picture.webp")) %> + <%= tag(:source, :srcset => image_path("picture.png")) %> + <%= image_tag("picture.png", :alt => "Image") %> + <% end %> + ``` + will generate: + ```html + + + + Image + + ``` + + *Juan Pablo Balarini* + * Remove deprecated support to passing instance variables as locals to partials. *Rafael Mendonça França* diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index e42177386d..1acf3c0a7f 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -435,6 +435,63 @@ def image_tag(source, options = {}) tag("img", options) end + # Returns an HTML picture tag for the +sources+. If +sources+ is a string, + # a single picture tag will be returned. If +sources+ is an array, a picture + # tag with nested source tags for each source will be returned. The + # +sources+ can be full paths, files that exist in your public images + # directory, or Active Storage attachments. Since the picture tag requires + # an img tag, the last element you provide will be used for the img tag. + # For complete control over the picture tag, a block can be passed, which + # will populate the contents of the tag accordingly. + # + # ==== Options + # + # When the last parameter is a hash you can add HTML attributes using that + # parameter. Apart from all the HTML supported options, the following are supported: + # + # * :image - Hash of options that are passed directly to the +image_tag+ helper. + # + # ==== Examples + # + # picture_tag("picture.webp") + # # => + # picture_tag("gold.png", :image => { :size => "20" } + # # => + # picture_tag("gold.png", :image => { :size => "45x70" }) + # # => + # picture_tag("picture.webp", "picture.png") + # # => + # picture_tag("picture.webp", "picture.png", :image => { alt: "Image" }) + # # => Image + # picture_tag(["picture.webp", "picture.png"], :image => { alt: "Image" }) + # # => Image + # picture_tag(:class => "my-class") { tag(:source, :srcset => image_path("picture.webp")) + image_tag("picture.png", :alt => "Image") } + # # => Image + # picture_tag { tag(:source, :srcset => image_path("picture-small.webp"), :media => "(min-width: 600px)") + tag(:source, :srcset => image_path("picture-big.webp")) + image_tag("picture.png", :alt => "Image") } + # # => Image + # + # Active Storage blobs (images that are uploaded by the users of your app): + # + # picture_tag(user.profile_picture) + # # => + def picture_tag(*sources, &block) + sources.flatten! + options = sources.extract_options!.symbolize_keys + picture_options = options.except(:image) + image_options = options.fetch(:image, {}) + skip_pipeline = options.delete(:skip_pipeline) + source_tags = [] + + content_tag(:picture, picture_options) do + if block.present? + capture(&block).html_safe + else + source_tags = sources.map { |source| tag("source", srcset: resolve_asset_source("image", source, skip_pipeline)) } if sources.size > 1 + safe_join(source_tags << image_tag(sources.last, image_options)) + end + end + 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 diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index 11fe27869f..7760fa6d45 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -247,6 +247,68 @@ def content_security_policy_nonce %(image_tag("rss.gif", srcset: [["pic_640.jpg", "640w"], ["pic_1024.jpg", "1024w"]])) => %() } + PicturePathToTag = { + %(image_path("xml")) => %(/images/xml), + %(image_path("xml.webp")) => %(/images/xml.webp), + %(image_path("dir/xml.webp")) => %(/images/dir/xml.webp), + %(image_path("/dir/xml.webp")) => %(/dir/xml.webp) + } + + PathToPictureToTag = { + %(path_to_image("xml")) => %(/images/xml), + %(path_to_image("xml.webp")) => %(/images/xml.webp), + %(path_to_image("dir/xml.webp")) => %(/images/dir/xml.webp), + %(path_to_image("/dir/xml.webp")) => %(/dir/xml.webp) + } + + PictureUrlToTag = { + %(image_url("xml")) => %(http://www.example.com/images/xml), + %(image_url("xml.webp")) => %(http://www.example.com/images/xml.webp), + %(image_url("dir/xml.webp")) => %(http://www.example.com/images/dir/xml.webp), + %(image_url("/dir/xml.webp")) => %(http://www.example.com/dir/xml.webp) + } + + UrlToPictureToTag = { + %(url_to_image("xml")) => %(http://www.example.com/images/xml), + %(url_to_image("xml.webp")) => %(http://www.example.com/images/xml.webp), + %(url_to_image("dir/xml.webp")) => %(http://www.example.com/images/dir/xml.webp), + %(url_to_image("/dir/xml.webp")) => %(http://www.example.com/dir/xml.webp) + } + + PictureLinkToTag = { + %(picture_tag("picture.webp")) => %(), + %(picture_tag("gold.png", :image => { :size => "20" })) => %(), + %(picture_tag("gold.png", :image => { :size => 20 })) => %(), + %(picture_tag("silver.png", :image => { :size => "90.9" })) => %(), + %(picture_tag("silver.png", :image => { :size => 90.9 })) => %(), + %(picture_tag("gold.png", :image => { :size => "45x70" })) => %(), + %(picture_tag("gold.png", :image => { "size" => "45x70" })) => %(), + %(picture_tag("silver.png", :image => { :size => "67.12x74.09" })) => %(), + %(picture_tag("silver.png", :image => { "size" => "67.12x74.09" })) => %(), + %(picture_tag("bronze.png", :image => { :size => "10x15.7" })) => %(), + %(picture_tag("bronze.png", :image => { "size" => "10x15.7" })) => %(), + %(picture_tag("platinum.png", :image => { :size => "4.9x20" })) => %(), + %(picture_tag("platinum.png", :image => { "size" => "4.9x20" })) => %(), + %(picture_tag("error.png", :image => { "size" => "45 x 70" })) => %(), + %(picture_tag("error.png", :image => { "size" => "1,024x768" })) => %(), + %(picture_tag("error.png", :image => { "size" => "768x1,024" })) => %(), + %(picture_tag("error.png", :image => { "size" => "x" })) => %(), + %(picture_tag("google.com.png")) => %(), + %(picture_tag("slash..png")) => %(), + %(picture_tag(".pdf.png")) => %(), + %(picture_tag("http://www.rubyonrails.com/images/rails.png")) => %(), + %(picture_tag("//www.rubyonrails.com/images/rails.png")) => %(), + %(picture_tag("mouse.png", :image => { :alt => nil })) => %(), + %(picture_tag("data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", :image => { :alt => nil })) => %(), + %(picture_tag("")) => %(), + %(picture_tag("picture.webp", "picture.png")) => %(), + %(picture_tag("picture.webp", "picture.png", :class => "my-class")) => %(), + %(picture_tag("picture.webp", "picture.png", :image => { alt: "Image" })) => %(Image), + %(picture_tag(["picture.webp", "picture.png"], :image => { alt: "Image" })) => %(Image), + %(picture_tag(:class => "my-class") { tag(:source, :srcset => image_path("picture.webp")) + image_tag("picture.png", :alt => "Image") }) => %(Image), + %(picture_tag { tag(:source, :srcset => image_path("picture-small.webp"), :media => "(min-width: 600px)") + tag(:source, :srcset => image_path("picture-big.webp")) + image_tag("picture.png", :alt => "Image") }) => %(Image), + } + FaviconLinkToTag = { %(favicon_link_tag) => %(), %(favicon_link_tag 'favicon.ico') => %(), @@ -711,6 +773,26 @@ def test_preload_link_tag PreloadLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end + def test_picture_path + PicturePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + + def test_path_to_picture_alias_for_picture_path + PathToPictureToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + + def test_picture_url + PictureUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + + def test_url_to_picture_alias_for_picture_url + UrlToPictureToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + + def test_picture_tag + PictureLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + def test_video_path VideoPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end