Copiando sem recarregar a página
Um botão que não muda nada na página além da área de transferência
O gerenciador de imagens de uma aula mostra uma URL de imagem gerada ao lado de um pequeno ícone de copiar. Clicar no ícone coloca a URL na área de transferência, um toast confirma, e nada mais na página se mexe. Nenhum formulário é enviado, nenhuma requisição vai ao servidor e nenhuma navegação do Turbo acontece. O recurso inteiro mora num arquivo pequeno, o app/javascript/controllers/ui/clipboard_controller.js deste app:
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["source"]
static values = { message: String }
copy(event) {
if (event) {
event.preventDefault()
}
const text = this.sourceTarget.value || this.sourceTarget.textContent
if (this.sourceTarget.select) {
this.sourceTarget.select()
}
// navigator.clipboard is undefined outside a secure context and in
// browsers without the async Clipboard API. Calling writeText on it
// would throw before any promise exists, skipping the fallback below,
// so turn that case into a rejection the .catch() can handle.
const write = navigator.clipboard?.writeText
? navigator.clipboard.writeText(text)
: Promise.reject(new Error("Clipboard API unavailable"))
write.then(() => {
this.showToast()
}).catch(err => {
console.error('Failed to copy text: ', err)
try {
// execCommand reports failure by returning false, not by throwing.
if (document.execCommand('copy')) this.showToast()
} catch (err) {
console.error('Fallback: Oops, unable to copy', err)
}
})
}
showToast() {
// builds and appends a toast element; lesson 6 covers this method in full
}
}
Tudo acima bate com o arquivo deste app, exceto o corpo do showToast, cortado aqui (o comentário marca o corte) porque a aula 6 o cita completo. Este curso lê o resto linha por linha e depois segue o controller até as três páginas que o ligam ao HTML.
Por que isso precisa de JavaScript
Um link <a href="..."> comum muda a página, e um submit de formulário também. Escrever na área de transferência é diferente: é um recurso do navegador sem elemento HTML próprio, não existe tag <clipboard>. Algo precisa rodar no navegador em resposta a um clique e chamar uma API do navegador. É esse o trabalho do Stimulus aqui: prender uma classe pequena a um pedaço do DOM e dar a ela uma coisa para fazer no clique.
O que é um controller do Stimulus, nos termos deste arquivo
export default class extends Controller é o formato inteiro. Controller, importado de @hotwired/stimulus, é a classe base que todo controller do Stimulus estende. static targets = ["source"] diz que o controller espera um elemento com nome dentro dele, chamado source: o elemento cujo texto é copiado. static values = { message: String } lê da página o texto do toast, para que cada página passe uma mensagem traduzida (aula 6). copy(event) é um método de instância comum, chamado quando a ligação da aula 3 dispara um clique. Nada aqui é específico do Rails nem JavaScript incomum. É uma classe pequena com um método público, importada e registrada como todo controller em app/javascript/controllers/.
O que este curso cobre
A aula 2 abre o HTML que liga este controller a uma página: os atributos data-controller e data-*-target. A aula 3 trata do data-action, incluindo uma diferença entre dois elementos da mesma marcação que vale entender. A aula 4 trata do navigator.clipboard.writeText(), a API moderna de Clipboard que este controller chama primeiro. A aula 5 trata do fallback document.execCommand('copy'): por que ele está obsoleto, por que o código ainda o chama e como o código garante que ele tenha chance de rodar. A aula 6 abre o showToast() completo, um método que monta um toast em HTML e reaproveita o comportamento de dispensa de outro controller. A aula 7 lê as outras páginas que usam este controller e fecha o curso.
Continue com a aula 2
Targets: conectando o elemento source
Mostra como data-controller e data-ui--clipboard-target prendem o controller e geram o this.sourceTarget do input.
Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.