Co zrobić, jeśli za pomocą formularza chcemy przesłać kilka plików, ale nie chcemy mnożyć w nieskończoność pola <input type="file">? Wystarczy parę linijek kodu z użyciem biblioteki jQuery. Poniżej zamieszczam mój autorski skrypt wraz z wyjaśnieniem działania.

Słowo uwagi na sam początek wyjaśniający ogólną zasadę działania skryptu. Otóż cała sztuczka sprowadza się do stworzenia stosu pól <input type="file"> i chowaniu ich po wybraniu ścieżki do pliku.

Skrypt najpierw tworzy określoną w zmiennej ilość kopii jednego pola <input type="file"> nadając każdej kopii unikatowy atrybut id oraz właściwość z-index dla każdej kopii pomniejszaną o 1. Po wybraniu ścieżki do pliku, nazwa pliku wyświetla się w osobnym kontenerze, a polu input zostaje nadana właściwość display:none. Proste, prawda?

Formularz

Zacznijmy od skonstruowania prostego formularza, który będzie zawierać jedno pole <input type="file"> oraz kontenera, w którym będziemy wyświetlać nazwy dodanych plików:

<!-- formularz -->
<form method="post" action="">
	<input type="file" name="zalacznik_1" id="zalacznik_1" class="zal"/>
</form>

<!-- kontener -->
<div>
    <p>Te pliki zostaną załączone do wiadomości:</p>
    <ul id="zalPl">
    	<li id="brakPl">brak załączonych plików</li>
    </ul>
</div>

Jeśli chodzi o pole input to obowiązkowo trzeba mu nadać atrybuty name oraz id, a ponieważ będziemy to pole powielać, oba atrybuty powinny mieć przyrostek 1, który będziemy zwiększać (name i id dla każdego pola musi być unikatowy). Dla czytelności przed przyrostkiem dodałem podkreślnik. Dodajmy odrobinę css – dla wyglądu i z konieczności:

div {
	border:1px solid #ccc;
	width:270px;
	padding:10px;
	font-size:11px;
	font-family:Arial, Helvetica, sans-serif;
}

form {
	border:1px solid #ccc;
	width:270px;
	padding:10px;
	margin:0 0 10px;
	font-size:11px;
	font-family:Arial, Helvetica, sans-serif;
	height:15px;
	position:relative;
}

p {
	padding:0 0 5px;
	margin:0;
	border-bottom:1px dotted #ccc;
}

ul {
	margin:10px 0 0;
	padding:0;
	list-style:none;
}

.zal {
	position:absolute;
	top:6px;
	left:10px;
	z-index:100;
}

.odlPlik {
	cursor:pointer;
}

Z tego wszystkie najważniejszych i obowiązkowych jest pięć właściwości – position:relative dla kontenera, w którym znajduje się input (a więc niekoniecznie mus być to form), oraz position:absolute, z-index:100 oraz właściwości top i left (przy czym ich wartości są tu już mniej ważne).

Pierwsza właściwość – position:relative – przyda się, ponieważ aby stworzyć stos pól input, musimy je pozycjonować absolutnie. Gdyby nie ta własciwość kontenera, elementy pozycjonowane absolutnie wyświetliłyby się poza nim, zatem jeśli chcemy je trzymać w ryzach, to musimy pamiętać o tej właściwości.

Druga właściwość – position:absolute – potrzebna jest nam właśnie po to, aby stworzyć stos inputów. Gdyby nie ona, inputy wyświetlałyby się obok siebie (tak jak to mają w zwyczaju robić elementy liniowe).

Kolejna właściwość – z-index:100 – jest potrzebna po to, aby ustawić kolejność inputów na stosie. Jest ona takim html’owym odpowiednikiem osi Z. Im większa wartość właściwości, tym input wyżej na stosie. Wynika z tego, że na stosie umieścimy maksymalnie 100 inputów. Jak ktoś chce więcej (powodzenia), to musi sobie zwiększyć tę wartość.

Właściwości top i left ustawią wszystkie nasz inputy w jednym miejscu względem kontenera i względem siebie.

jQuery

Przed przystąpieniem do pisania funkcji, nie wolno zapomnieć o podpięciu biblioteki jQuery w sekcji head. To załatwi sprawę:

<script type="text/javascript" src="http://code.jquery.com/jquery-1.4.2.min.js"></script>

Pora na szybki przegląd działań. Po pierwsze – określamy w zmiennej maksymalną liczbę pól (czyli automatycznie maksymalną liczbę plików do załączenia). Po drugie – kopiujemy już istniejące pole input tyle razy, ile wskazaliśmy w zmiennej minus jeden (bo przecież istnieje już oryginał). Po trzecie – w momencie, w którym pole input otrzyma jakaś wartość (czyli zostanie wybrany jakiś plik), chowamy je, wyświetlamy nazwę pliku w przygotowanym kontenerze, tym samym odsłaniając kolejny, pusty input na stosie. Po czwarte – umożliwiamy usuwanie plików, których nazwy zostały wyświetlone, poprzez kliknięcie tych nazw.

Do dzieła. Zacznijmy od stworzenia wspomnianej zmiennej oraz wyczyszczeniu wartości wszystkich pól <input type="file">. To czyszczenie będzie nam później potrzebne, o czym nie omieszkam wspomnieć.

var iloscPlikow = 5
$('input[type=file]').val('')

Jak widać zdecydowałem się na maksymalnie 5 plików, dlatego zmienna iloscPlikow ma wartość 5. Możecie ją zmienić, jeśli potrzebujecie więcej pól. Zapis $('input[type=file]') odnosi się do wszystkich pól <input type="file">, ale należy pamiętać, że można się do nich odwołać w dowolny sposób (np. poprzez klasę). Następna w kolejności jest funckja, która skopiuje już istniejące pole input:

for (i=1; i<iloscPlikow; i++) {
	var input = $('#zalacznik_1').clone()
	input.removeAttr('id')
	input.removeAttr('name')
	input.attr('id','zalacznik_' + parseInt(i+1))
	input.attr('name','zalacznik_' + parseInt(i+1))
	input.css('z-index', 100-i)
	$('form').append(input)
}

Przede wszystkim potrzebna nam pętla. Ja użyłem for. Dzięki niej czynność polegająca na klonowaniu inputa wykona się zadaną ilość razy. W naszym wypadku ta zadana ilość ma wartość zmiennej iloscPlikow, ponieważ jednak rozpoczynamy pętle od wartości 1, a nie od 0, input zostanie sklonowany 4 razy, a więc o jeden raz mniej, niż wartość zmiennej iloscPlikow. W rezultacie otrzymamy 5 inputów (oryginał + 4 klony).

Zapis var input = $('#zalacznik_1').clone() tworzy zmienną o nazwie input, w której zapisywana zostaje jedna kopia inputa o id="zalacznik_1". Gdybyśmy jednak na tym poprzestali, mielibyśmy 5 inputów o takim samym id i name, co jest błędem. Dlatego w kolejnych dwóch linijkach usuwamy oba atrybuty, a następnie ponownie je dodajemy ale ze zmienionym przyrostkiem. Nasz nowy przyrostek ma wartość i++, czyli dla każdego inputa zwiększy się o jeden. Na koniec ustawiamy wartość 100-i dla właściwości z-index. Tak sklonowane inputy umieszczamy w naszym formularzu – $('form').append(input). Rezultat powinien wyglądać następująco:

<input type="file" class="zal" id="zalacznik_1" name="zalacznik_1">
<input type="file" class="zal" id="zalacznik_2" name="zalacznik_2" style="z-index: 99;">
<input type="file" class="zal" id="zalacznik_3" name="zalacznik_3" style="z-index: 98;">
<input type="file" class="zal" id="zalacznik_4" name="zalacznik_4" style="z-index: 97;">
<input type="file" class="zal" id="zalacznik_5" name="zalacznik_5" style="z-index: 96;">

Pora na kolejny krok – chcemy, by po wybraniu jakiegoś pliku, jego nazwa wyświetlała się na liście <ul id="zalPl"> a input, do którego została wprowadzona ścieżka do pliku znikał odsłaniając kolejny – pusty. Sprawę załatwi parę linijek kodu:

$('.zal').live ('change', function() {
	$('#brakPl').hide()
	$(this).hide()
	var val = $(this).val().split("\\").pop()
	$('#zalPl').append('<li><span  class="odlPlik" style="cursor:pointer;">[x]&nbsp;</span><span class="'+$(this).attr('id')+'">'+val+'</span></li>')
})

Przede wszystkim należałoby zacząć od omówienia metody live() . Generalnie metoda ta śledzi na bieżąco zmiany w dokumencie i przypisuje określonym elementom jakieś zdarzenie. W naszym przypadku będzie ona przypisywać zdarzenie change wszystkim elementom o z class="zal". Dlaczego właściwie potrzebujemy tej metody i dlaczego nie możemy użyć po prostu samego zdarzenia change? Ponieważ za chwilę będziemy klonować oraz usuwać. Samo zdarzenie przypisze funkcję tylko do istniejących elementów, a my za chwilę stworzymy nowe, do których również chcemy przypisać funkcję. Dlatego musimy na bieżąco śledzić zmiany w dokumencie.

Jakby ktoś się pogubił to przypominam – jesteśmy w momencie, w którym użytkownik kliknął przycisk Przeglądaj i właśnie wybrał ścieżkę, a więc coś zmienił, a więc funkcja przyporządkowana do zdarzenia change właśnie zaczyna działać. Na pierwszy ogień idzie element <li id="brakPl">, który ukrywamy. W końcu zaraz dodamy jakiś plik, więc wyświetlanie komunikatu o braku załączonych plików jest bez sensu. Zaraz potem ukrywamy pole input, w które właśnie wstawiliśmy ścieżkę do pliku. UWAGA! To, że ukryliśmy pole input nadając mu display:none;, bo tak działa metoda hide(), nie oznacza, że nie mamy do niego dostępu i nie możemy przesłać zawartym w polu wartości.

W kolejnym kroku zmiennej o nazwie val przypisujemy nazwę wybranego pliku wraz z rozszerzeniem. No dobrze, ale po co w takim razie taki $(this).val().split("\\").pop() zapis?
Ano jest to specjalny ukłon w stronę IE. Użycie samego $(this).val() w IE zwróci nazwę pliku + rozszerzenie poprzedzone takim wynalazkiem – C:\fakepath\. Podobno to wszystko ze względów bezpieczeństwa. Może, nie wiem – w FF zwraca bez takich cudów. Nieważne, specjalny zapis najpierw pobiera wartość pola input, następnie rozdziela pobrany łańcuch używając backslasha (\) jako separatora i z powstałych elementów tworzy tablicę, a na końcu, dzięki metodzie pop() zwraca ostatni element tablicy. W rezultacie otrzymujemy we wszystkich przeglądarkach nazwę pliku + rozszerzenie. Uff…

Wreszcie, na liście <ul id="zalPl"> dołączamy za pomocą metody append() nazwę pliku z przedrostkiem [x] (do kasowania). Przedrostek dodatkowo umieszczamy w <span class="odlPlik">. Klasa odlPlik ułatwi identyfikację klikalnej części listy zarówno nam jak i użytkownikom. Dodatkowo nazwę umieszczamy w <span class="'+$(this).attr('id')+'">. Klasa ta jest identyczna jak atrybut id inputa, którego ukryliśmy dwa akapity wcześniej – ułatwi nam to prace za chwilę, kiedy będziemy usuwać nazwy plików z listy.

Mamy już więc pole input, za pomocą którego możemy dodawać wiele załączników do formularza i wyświetlać ich nazwy. Pora umożliwić użytkownikom usuwanie załączników z listy, na wypadek gdyby się rozmyślili lub chcieli coś zmienić. Oto kod, który nam to umożliwi:

$('.odlPlik').live('click', function() {
	var inputId = $(this).next().attr('class')
	var staryInput =$('input[type=file]#'+inputId)
	var nowyInput = staryInput.clone()
	$('form').append(nowyInput)
	staryInput.remove()
	nowyInput.val('')
	nowyInput.show()
	$(this).parent().remove()
	if ($('#zalPl li').size() == 1) {
			$('#brakPl').show()
		}
})

Po kolei – metodę live() już znamy. Tym razem korzystamy z niej, aby przyporządkować do wszystkich [x] zdarzenie click. Co się dzieje po kliknięciu na któryś z krzyżyków? Generalnie zamysł był taki, żeby ukryty wcześniej input po prostu wyświetlić z wyczyszczoną wartością, czyli bez ścieżki do pliku. Niestety, trzeba było zastosować małe obejście, ponieważ Internet Explorer nie zezwala na majstrowanie za pomocą Java Script przy wartościach pól <input type="file">. Po pierwsze zapisujemy do zmiennej inputId nazwę klasy elementu <span>, w którym zamknięta jest nazwa pliku. Wiadomo, że jest to element znajdujący się w sąsiedztwie [x], dlatego używamy do tego metody next().

Następnie do zmiennej zapisujemy sobie input o id równym inputId (pisałem o tym wcześniej – klasa ta jest identyczna jak atrybut id inputa). Dziki temu zabiegowi wiemy dokładnie, który input chcemy przywrócić. W kolejnym kroku klonujemy starego inputa do zmiennej o nazwie nowyInput. Nowego inputa przypinamy przy pomocy metody append() do formularza. Dlaczego tak? Internet Explorer sklonuje inputa, ale bez ścieżki do pliku. Zostawi za to wszystkie inne parametry jak z-index, id oraz name. Co prawda FF sklonuje go ze ścieżką, ale to już nie problem, bo zaraz ją usuniemy. Najpierw pozbądźmy się starego inputa – staryInput.remove(). Teraz możemy wyczyścić wartość nowego inputa – nowyInput.val(''), a następnie, używając metody show(), pokazać go światu.

Na samiutkim końcu sprawdzimy wielkość tablicy elementów <li> na naszej liście. Jeśli wielkość wyniesie 1, oznaczać to będzie, że użytkownik usunął wszystkie pliki z listy, a jedyny element, który został to ten, w którym znajduje się informacja o braku plików do załączenia. Używając instrukcji warunkowej if sprawdzamy ten warunek i jeśli wynik jest true, pokazujemy wcześniej ukryty element <li id="brakPl">.

To tyle. Pod tym adresem możecie sobie ściągnąć gotowca i używać go do woli. Naturalnie będę wdzięczny za jakieś info, jeśli zdecydujecie się użyć tego rozwiązania. W razie pytań lub wątpliwości – piszcie.

Podobne wpisy: