Spis treści:

Kategoria:HTMLJavaScript


Załączniki plikowe - Drag, Drop, HTML

Amerykanie wysłali szpiega do ZSRR. Ten w pierwszy dzień przenocował u pewnego starca na wsi. Rano wstając, słyszy:
- Ty mówisz jak nasi, pijesz jak nasi, nawet zachowujesz się jak nasi - ale Ty nie od nas.
- Czemu tak myślisz - pyta Amerykanin.
- Bo u nas Murzynów nie ma.

Przeciąganie plików do okna przeglądarki

Pisałem niedawno o myszce, mechanizmie Drag&Drop w HTML i zadowoleniu użytkowników, którzy dzięki przeciąganiu różnych elementów mogą wykonywać mniej pracy. Po to w końcu są komputeryNiektórzy mówią, że dzięki komputerom możemy ułatwić sobie pracę, której bez komputerów nigdy by nie było.. Pisałem wtedy, że operacja przeciągnij i upuść może być wykorzystana do uproszczenia procesu dodawania załączników. Tradycyjne podejście polegało na:

  • naciśnięciu jakiegoś przycisku i wywołanie okna dialogowego,
  • wyszukaniu w oknie dialogowym pożądanego załącznika,
  • zaakceptowanie wyboru.

Gdy załączników jest więcej, powyższe czynności należało powtarzać dla każdego z plików. W systemie operacyjnym Windows od dawna można zaznaczyć kilka plików i przenieść je do innego katalogu jednym ruchem. Czy nie dałoby się zrobić tego samego w aplikacji sieciowej, w przeglądarce? Oczywiście, że się da. Popatrzmy na przykład.

Przyjmowanie plików przez element HTML

We wspomnianym wcześniej artykule przeciągane były elementy strony. Tym razem będą to elementy spoza przeglądarki. Wyjście poza przeglądarkę wiąże się z pewnym ryzykiem i niebezpieczeństwem udostępnienia swoich danych reszcie świata. Ma to swoje przełożenia w API, które zostało zaprojektowane do odczytu tych plików. Nie ma możliwości programowego przeglądania struktury plików i katalogów jak to ma miejsce w tradycyjnych, nieinternetowych aplikacjach. Dostęp mamy tylko do tego, co nam użytkownik przekaże. Nie mamy również dostępu do fizycznej ścieżki plików na dysku, bo to także zdradza jakieś informacje o naszym systemie. API jest jednak wystarczające do zrealizowania postawionego w tytule zadania. Popatrzmy, czym będziemy się dzisiaj zajmować:

Wrzuć tutaj plik...

Zachęcam do przetestowania. Warto zwrócić uwagę na możliwość przenoszenia jednocześnie kilku (kilkunastu) plików. Wtedy wychodzi prawdziwa moc operacji Drag&Drop. Gdy przeciąganie i upuszczanie plików już się znudzi, można przejść do konkretów.

Pełny kod strony

Kod jest na tyle prosty, że można go bez problemu umieścić w całości. Listing poniżej:

<html>
<head>
<title>Załączniki plikowe - Drag, Drop, HTML</title>
</head>
<body>
<div id="dropArea">Wrzuć tutaj plik...</div>
<div id="droppedFiles"></div>
<script>
window.addEventListener("load", function () {
    var self = this;
    self.handleIncomingFiles = function(e) {
        var files = e.dataTransfer.files;
        for (var i=0; i<files.length; i++) {
            var file = files[i];
            var reader = new FileReader();
            reader.addEventListener("loadend", function(file, e) {
                var container = document.getElementById("droppedFiles");
                var div = document.createElement("div");
                div.innerHTML = "<b>"
                    + file.name+" ("
                    + file.size+"B)</b><br/><i>"
                    + this.result.substr(0, 100)+"</i>";
                container.appendChild(div);
            }.bind(reader, file));
            reader.readAsDataURL(file);
        }
    };
    var dropArea = document.getElementById("dropArea");
    dropArea.addEventListener("dragover", function(e) {
        e.preventDefault();
    });
    dropArea.addEventListener("drop", function(e) {
        e.preventDefault();
        self.handleIncomingFiles(e);
        dropArea.style.backgroundColor = "";
    });
    dropArea.addEventListener("dragenter", function(e) {
        dropArea.style.backgroundColor = "#ADF";
    });
    dropArea.addEventListener("dragleave", function(e) {
        dropArea.style.backgroundColor = "";
    });
}, false);
</script>
<style>
#dropArea {
  width:100px;
  height:100px;
  background-color:#DDD;
  text-align: center;
}
#droppedFiles{
  border:solid 1px;
  overflow:hidden;
}
</style>
</body>
</html>

Większość kodu to obsługa zdarzeń, które są już znane z przykładu Drag&Drop dla elementów wewnątrz strony. Są jednak nowe, nieznane do tej pory konstrukcje.

Wstawienie plików do schowka

Gdy sami zarządzamy operacją przeciągnij i upuść musimy do kontenera Drag&Drop coś wstawić. Służy do tego funkcja dataTransfer.setData dostępna poprzez obiekt zdarzenia. Gdy pliki przenoszone są z jakiegoś katalogu w systemie operacyjnym, kontener uzupełniany jest przez przeglądarkę. Wcześniej, gdy dane wstawialiśmy sami, pobieraliśmy je funkcją dataTransfer.getData. Tym razem będzie nas interesowała właściwość files obiektu dataTransfer. Właściwość files, jak sama nazwa wskazuje, może zawierać wiele plików. Na tym etapie pod słowem plik należy rozumieć obiekt z podstawowymi atrybutami fizycznego pliku na dysku. Te atrybuty to:

  • nazwa pliku,
  • rozmiar pliku w bajtach,
  • typ pliku w postaci łańcucha znaków oznaczającego typ MIME,
  • data modyfikacji.

Co istotne, nie ma tam żadnej informacji o zawartości. Dlaczego? Gdy pliki są małe, odczyt szybki, nie byłoby problemu. Pomyślmy jednak co stałoby się, gdybyśmy zechcieli wrzucić do przeglądarki kilkusetmegabajtowy plik. Przeglądarka zajęłaby się na dłuższy czas kosztownymi operacjami dyskowymi zamiast obsługą tego, co jest istotne. Nie ma przecież gwarancji, że te dane będą nam w ogóle potrzebne. To my decydujemy co i jak chcemy z tym plikiem zrobić. Dostęp do rozmiaru pliku może być dobrze wykorzystany - pozwala zablokować zbyt duże załączniki zanim cokolwiek zaczniemy z nimi robić.

Asynchroniczne wczytywanie pliku

Obsługa plików została celowa przeniesiona do oddzielnej funkcji. Pozwoli to zająć się tylko tym, co jest tutaj najistotniejsze. A najistotniejszy jest tutaj obiekt FileReader. Jego podstawowe metody przyjmują w postaci parametru plikDokumentacja definiuje to znacznie precyzyjniej. W dokumentacji mamy obiekt BLOB (ang. binary large object - duży obiekt binarny), z którego dziedziczy obiekt pliku. Metody obiektu FileReader przyjmują BLOB. Nie zamierzam przedstawiać wszystkich dostępnych metod i zdarzeń - skupię się na razie na wykorzystanych w przykładzie, przedstawionych w kolejności logicznej.

Zabawa zaczyna się od utworzenia nowego obiektu FileReader i wywołania jednej z metod pobierających dane - w tym przypadku readAsDataURL. Metoda ta pobiera plik w postaci zakodowanej, gotowej do przesłania na serwer, najprawdopodobniej w postaci Base64Postać danych musi spełniać warunki określone w specyfikacji RFC2397., z dodatkowymi znacznikami określającymi typ MIME. Gdy proces pobierania pliku zakończy się, wywoływane jest zdarzenie loadend. W momencie nadejścia zdarzenia zawartość pliku będzie dostępna w polu result obiektu FileReader - o ile nie pojawił się jakiś błąd. Podpięcie zdarzenia loadend musi być oczywiście wykonane przed rozpoczęciem pobierania (readAsDataURL). W przeciwnym razie moglibyśmy nie zdążyć - taka jest natura wywołań asynchronicznych.

Warto zwrócić uwagę na jeszcze jeden aspekt. Parametry funkcji obsługi zdarzenia są zmodyfikowane w taki sposób, aby jako wskaźnik this przyszedł obiekt FileReader, a jako pierwszy parametr obiekt pliku. Pozwala to uzyskać pełną kontrolę nad tym, co przetwarzamy. Realizowane jest to poprzez metodę bind. Trzeba wiedzieć, że nie ma żadnej gwarancji zachowania kolejności pobierania plików, dlatego lepiej polegać na jawnie wskazanych parametrach.

Warto też mieć na uwadze wielowątkowość i wszystkie niebezpieczeństwa, które przez tę wielowątkowość mogą się pojawić. Wątek ten uruchamia się z chwilą rozpoczęcia pobierania pliku. Pierwszy uruchomiony wątek może się skończyć przed drugim, ale może się skończyć po nim. Wystarczy, że pierwszy plik będzie znacznie większy. Wróćmy jednak do kodu i zastosowanych w nim rozwiązań. Pomimo tego, że po zakończeniu pobierania mamy dostęp do całego pliku, uznałem, że nie będę go umieszczał na stronie w pełnej krasie. Wyświetlę tylko jego nazwę, rozmiar i pierwsze 100 znaków. W zwykłych, niedemonstracyjnych rozwiązaniach dane będą najprawdopodobniej zapisane w jakimś obiekcie lub elemencie i przesłane na serwer w całości.

Co dalej

Mam nadzieję, że w głowach już pojawiają się pomysły na zastosowanie tego wygodnego mechanizmu. Zachęcam przy okazji do zapoznania się z pozostałymi metodami i zdarzeniami obiektu FileReader. Z metod będą to:

  • readAsText - do odczytywania tekstu,
  • readAsArrayBuffer - do odczytywania danych w postaci binarnej.

Metody jako parametr przyjmują plik. Ze zdarzeń warto wymienić dwa, które będą, oprócz zdarzenia loadend stosowane najczęściej. Są to:

  • error - wywoływane przy błędzie odczytu,
  • progress - wywoływane w celu poinformowania o postępie w odczytywaniu pliku.

Pełną listę zdarzeń i metod można znaleźć w dokumentacji. Będą one stosowane raczej sporadycznie, ale warto się z nimi zapoznać.

Kategoria:HTMLJavaScript

, 2014-02-10

Brak komentarzy - bądź pierwszy

Dodaj komentarz
Wyślij
Ostatnie komentarze
chcę dodać kolumnę, która będzie połączeniem dwóch innych istniejących już kolumn, jak powinien wyglądać scrypt?
Przydałyby się jeszcze 2 rzeczy do cz. 3 i byłoby superanckie.
1. Na starcie sortuje wg jakiejś kolumny i tam jest już strzałeczka. Widok takiej strzałeczki daje znać użytkownikowi, że taką tabele można sortować, a na razie pojawia się ona tylko po kliknięciu.
2. Uwzględnienie polskich znaków, bo np. przy sortowaniu Nazwisk i Imion jest to bardzo uciążliwe.
Ogólnie bardzo fajnie i prosto.
PS. Jest ten artykuł z jQuery już dostępny.
bardo ciekawe , można dzięki takim wpisom dostrzec wędkę..
Bardzo dziękuję za jasne tłumaczenie z dobrze dobranym przykładem!