O que é a ElementComponent

ElementComponent é uma gem Ruby leve e flexível para construir HTML de forma orientada a objetos. Em vez de concatenar strings ou depender de um template engine, você monta a árvore de elementos usando objetos Ruby, com atributos dinâmicos, aninhamento de conteúdo, hooks de renderização e — de brinde — um conjunto completo de 17 componentes Bootstrap 5 prontos para uso.

Esta é a primeira de uma série de posts cobrindo a gem por completo. Aqui vamos focar na API central: criação de elementos, a DSL de blocos, tipos de conteúdo, gerenciamento de atributos e tags autofechadas.

Instalação

bundle add element_component

Ou, sem Bundler:

gem install element_component

Criando elementos

O bloco básico de construção é ElementComponent::Element:

p = ElementComponent::Element.new("p", class: "text-bold")
p.add_content("Hello, World!")
puts p.render
# => <p class="text-bold">Hello, World!</p>

DSL de blocos

Elementos podem receber um bloco no construtor para adicionar conteúdo de forma inline, o que deixa a árvore de HTML legível mesmo quando aninhada:

div = ElementComponent::Element.new("div", class: "container") do |e|
  e.add_content("Welcome")
  e.add_content(ElementComponent::Element.new("h1") { |h| h.add_content("Title") })
end
puts div.render
# => <div class="container">Welcome<h1>Title</h1></div>

O helper new_element

Dentro de um bloco, new_element é um atalho para não precisar repetir ElementComponent::Element.new toda hora:

div = ElementComponent::Element.new("div") do |e|
  e.add_content(e.new_element("h1") { |h| h.add_content("Hello") })
  e.add_content(e.new_element("p", class: "lead") { |p| p.add_content("World") })
end
puts div.render
# => <div><h1>Hello</h1><p class="lead">World</p></div>

Tipos de conteúdo aceitos

add_content aceita string, uma instância de Element, um bloco (avaliado no momento da renderização) ou um array combinando qualquer um desses tipos:

div = ElementComponent::Element.new("div")

div.add_content("plain text")
div.add_content(ElementComponent::Element.new("span") { |s| s.add_content("nested") })
div.add_content { |e| e.new_element("em") { |em| em.add_content("deferred") } }
div.add_content(["Hello ", ElementComponent::Element.new("strong") { |s| s.add_content("World") }, "!"])

puts div.render
# => <div>plain text<span>nested</span><em>deferred</em>Hello <strong>World</strong>!</div>

O suporte a blocos como conteúdo é interessante porque adia a avaliação para o momento do render, permitindo conteúdo dinâmico ou dependente de estado que só existe naquele momento.

Gerenciamento de atributos

A API de atributos é encadeável e dá controle fino sobre valores individuais — não só sobre o atributo inteiro:

btn = ElementComponent::Element.new("button", class: "btn", type: "button")

btn.add_attribute(class: "btn-primary")        # acrescenta valor
btn.add_attribute!(id: "submit-btn", type: "submit")  # reseta e define novos
btn.remove_attribute(:type)                     # remove o atributo inteiro
btn.remove_attribute_value(:class, "btn-primary") # remove só um valor da lista

Isso é particularmente útil para atributos multivalorados como class, onde normalmente você quer adicionar ou remover uma classe sem tocar nas demais.

Tags autofechadas (void elements)

Para elementos como <img>, <input> e <br>, basta passar closing_tag: false:

img = ElementComponent::Element.new("img", closing_tag: false, src: "image.png", alt: "Logo")
puts img.render
# => <img src="image.png" alt="Logo">

O que vem a seguir

Todos os métodos add_* retornam self, o que permite encadeamento fluente — um padrão que se repete em toda a gem, inclusive nos componentes Bootstrap. No próximo post da série, vamos ver os recursos que tornam a ElementComponent pronta para produção: hooks de renderização (before_render, after_render, around_render), proteção automática contra XSS, cache e integração com helpers do Rails.