Ruby on Rails content_tag is deprecated, but I can't replicate its nesting capabilities using tag

1.2k Views Asked by At

The project I'm working on requires some of the data table's column titles to be referenced/defined in a footnote below the table.

As it's dynamic content that will be looped over depending on how many of the column titles require footnotes, I need to handle most of this in via models/helpers. I have been able to replicate the html our US designer wants using content_tag, but rubocop is whining about the use of content tag and says I should be using tag instead. But I can't get the nesting of the html tags to work at all using the tag method.

The section html I'm trying to produce is this (which will be repeated for as many footnotes are needed):

<li id="id" class="p-2">
  <dl class="m-0"><dt class="d-inline">Unusual term: </dt>
    <dd class="d-inline">Text with the definition of the term.<a href="#id-ref" aria-label="Back to content">↵</a></dd>
  </dl>
</li>

And the content_tag based code that works to produce it is this:

content_tag(:li,
  content_tag(:dl, [
    content_tag(:dt, 'Unusual term: ', class: "d-inline"),
    content_tag(:dd, [
      'Text with the definition of the term.',
      content_tag(:a, '↵', href: '#id-ref', aria: { label: "Back to content" })
    ].join.html_safe, class: "d-inline")
  ].join.html_safe, class: "m-0"),
id: 'id',
class: "p-2")

When I switch to using tag, the problem I'm coming up against is getting both the <dt> and <dd> tags, which both have content, to nest inside the <dl> tag (and also the <a> tag, which also has content, to nest within the aforementioned <dd> tag). The tag method only seems to output the last piece of nested content and ignores any other content that precedes it.

This is the nesting of the tag method that I've tried:

tag.li id: 'id', class: 'p-2' do
  tag.dl class: 'm-0' do
    (tag.dt 'Unusual term: ', class: 'd-inline') +
      (tag.dd 'Text with the definition of the term.', class: 'd-inline' do
        (tag.a arrow, href: anchor, aria: { label: 'Back to content' })
      end)
  end
end

And this is the output it's giving me.

<li id="id" class="p-2">
  <dl class="m-0"><dt class="d-inline">Unusual term: </dt>
    <dd class="d-inline"><a href="#id-ref" aria-label="Back to content">↵</a></dd>
  </dl>
</li>

As you can see, it's close, but it's missing the text content of the <dd> tag, which should be output just before the <a> tag starts.

Can anyone help? Is there a way to nest tags without losing content? Or should I just give up and write a partial with the actual html code written out...?

3

There are 3 best solutions below

0
On BEST ANSWER

I have now worked out a way to nest these tag methods to achieve the desired html output, using the following code:

link = link_to ' ↵', '#id-ref', aria: { label: 'Back to content' }

tag.li(id: 'id', class: 'p-2') do
  tag.dl(class: 'm-0') do
    (tag.dt 'Unusual term: ', class: 'd-inline') +
      (tag.dd ('Text with the definition of the term.' + link).html_safe, class: 'd-inline')
  end
end

If anyone has an alternative solution, I'd love to hear it, but I think this will serve for now.

EDIT TO ADD NEW SOLUTION

Thanks to @max suggestion of using concat to rid me of #html_safe I now have a block of code that I, my ux designer and rubocop will all be happy with! Here it is:

tag.li(id: 'id', class: 'p-2') do
  tag.dl(class: 'm-0') do
    tag.dt('Unusual term:', class: 'd-inline') + ' ' +
    tag.dd(class: 'd-inline') do
      concat 'Text with the definition of the term.'
      concat tag.a(' ↵', href: '#id-ref', aria: { label: 'Back to content' })
    end
  end
end

Thanks again for everyone's input on my question. I really appreciate it.

4
On

It looks to me like content_tag and tag are part of rails 6.1.3.1. It looks like only what is being deprecated is the format of the tag helper defaulting to an XHMTL empty tag instead of an HTML 5 type of tag. There are reports of incorrect behavior by rubocop targeting content_tag when it is tag which should be targeted.

You might check on the arguments to content_tag for empty tags since it might not be defaulting the same way as before.

1
On
tag.li(id: 'id', class: 'p-2') do
  tag.dl(class: 'm-0') do
    concat tag.dt('Unusual term: ', class: 'd-inline')
    concat tag.dd(class: 'd-inline') do
       concat 'Text with the definition of the term. '
       concat tag.a(arrow, href: anchor, aria: { label: 'Back to content' })
    end
  end
end

Each content_tag or tag has its own string buffer - by using concat you output to that string buffer. That lets you output multiple times in the block without dealing with string concatenation and #html_safe. This string buffer is the return value of content_tag/tag.

This really works no matter if you are using content_tag or tag.

See: