Rails 7 niosą ze sobą nie tyle powiew świeżości w budowaniu aplikacji webowych, co prawdziwą rewolucję. jQuery już dawno zostało wykurzone, teraz pora na mocne uniezależnienie się od frameworków frontendowych i miliona linii Javascript do ogarnięcia widoków.
Implementowałem ostatnio upload w aplikacji korzystającej z Hotwire i Stimulusa. Jestem more than happy, że aplikacja ma już dość złożony UI, a wciąż jest tam praktycznie kilkanaście linii JS.
Stanąłem przed problemem zrobienia podglądu uploadowanego obrazka jeszcze przed zapisaniem formularza. Scenariusz wygląd tak, że użytkownik klika Choose file, wybiera obrazek, który automatycznie wyświetla się w miejscu przeznaczonym na podgląd.
A tak to ogarnąłem z wykorzystaniem Stimulusa, którego zaczynam coraz bardziej lubić, ze względu na jego banalność.
Na potrzeby przykładu stworzyłem taki mega uproszczony widok.
<%= content_tag :main, class: "container mx-auto pt-6 h-screen" do %>
<div class="flex flex-col">
<label class="mb-4">Załącz obrazek</label>
<%= file_field_tag :image %>
</div>
<div class="flex flex-col items-center justify-center border mt-12 h-4/5">
<%= image_tag '', alt: "Tu będzie Image preview",
class: 'h-5/6 w-5/6 object-contain object-center' %>
</div>
<% end%>
Następnie utworzyłem Stimulus controller ImageUploadPreview
.
Ustaliłem dwa targety:
- input – czyli file input
- preview – tag
img
, gdzie wyświetlam podgląd wybranego obrazka
Następnie w funkcji setImagePreview
zawarłem całą logikę.
export default class ImageUploadPreview extends Controller {
static targets = [ "input", "preview" ]
setImagePreview() {
const input = this.inputTarget
const preview = this.previewTarget
if (input.files && input.files[0]) {
const reader = new FileReader()
const image = input.files[0]
reader.onload = () => {
preview.src = reader.result
}
reader.readAsDataURL(image)
}
}
}
Po kolei:
- kod sprawdza, czy jakikolwiek plik został wybrany
- tworzy instancję FileReadera, dzięki której mogę pobrać lokalizację wybranego obrazka
- tworzę callback, który podpinam do
reader.onload
– eventu, wywołanego, kiedy wybrany obrazek zostanie załadowany w readerze - w callbacku ustawiam
src
podglądu jako resultat z readera - wczytuję obrazek za pomocą readera, żeby dostać jego URL
Tyle, koniec magii.
Teraz tylko trzeba podpiąć controller, targety i akcję do elementów w widoku.
<%= content_tag :main, class: "container mx-auto pt-6 h-screen",
data: { controller: 'image-upload-preview' } do %>
<div class="flex flex-col">
<label class="mb-4">Załącz obrazek</label>
<%= file_field_tag :image,
data: {
target: 'image-upload-preview.input', action: 'image-upload-preview#setImagePreview'
} %>
</div>
<div class="flex flex-col items-center justify-center border mt-12 h-4/5">
<%= image_tag '', alt: "Tu będzie Image preview",
class: 'h-5/6 w-5/6 object-contain object-center',
data: { target: 'image-upload-preview.preview' } %>
</div>
<% end%>
data-controller
jest ustawiony w kontenerze dla inputa i podglądufile_field_tag
iimage_tag
są oznaczone jako targetyinput
ipreview
- do inputa podpięta jest akcja
setImagePreview
Końcowa funkcjonalność wygląda tak: