Obsługa linków i formularzy za pomoca AJAXa

Pracuję ostatnio nad dość ciekawym projektem, który realizuję wykorzystując kohana 2. Mimo mojej szczerej niechęci (ciągle) do JavaScript oraz zupełnej nieznajomości (nie powinnam się przyznawać?) mechanizmów AJAX musiałam się zmierzyć z pewnymi zagadnieniami, których puki co inaczej zrealizować się nie da.

Opis problemu

W serwisie, który tworzę będzie znajdowała się lista obrazków do których użytkownicy mogą dodawać tagi. Użytkownik, który dodał tag do zdjęcia może go również usunąć. Wszystko to ma się dziać w tle, bez przeładowania strony. Trzeba więc przechwycić event (click albo submit) a następnie w tle przeprowadzić odpowiednią operację na bazie. No i w końcu w zależności od wyniku operacji na bazie trzeba na końcu odpowiednio zmodyfikować kod HTML.

Odpowiedni kod HTML

Żeby lepiej zrozumieć na czym polegać będą operacje pokażę najpierw kod HTML na którym będziemy operować:

<div class="photo">
	<img src="/media/img/1.png" />
	<div id="tag_list">
		<div id="tag_1">tag 1</div>  
		<div id="tag_2">inny</div>
	</div>
	<div>
	  	<form action="#" method="post">
	  		<input type="text"value="tag" onclick="$(this).val('')"/> 
	    		<input value="dodaj" type="submit"/>
	  	</form>
  	</div>	
</div>

Dla porządku tylko powiem, że powyższy kod powoduje wyświetlenie obrazka, dwóch tagów oraz prostego formularza do wpisywania kolejnych tagów. Trzeba równiez pamiętać, by w nagłówku strony dołączyć plik z jQuery.

Tagi, które user będzie mógł usunąć będą miały dodatkowo link i będą zakodowane w następujący sposób:

<div id="tag_3">kolejny <a href="#" id="3">x</a></div>

Jeszcze tylko warto w tym miejscu zwrócić uwagę na atrybut onlick w polu tekstowym formularza. Przypisany do niego skrypt jQuery powoduje wyczyszczenie zawartości pola gdy tylko klikniemy na niego myszką. Takie małe a cieszy.

Przechwycenie zdarzenia

Uważny czytelnik z pewnością dostrzegł, że w formularzu atrybut action nie ma, jak to zwykle bywa wartości wskazującej na jakiś skrypt, który będzie przetwarzał dane. Podobnie jest z atrybutem href linka służącego do usuwania taga. Nie jest to potrzebne, skoro i tak chcemy przechwycić kliknięcie i
obsłużyć je “w tle”. Jak to zrobić?

Zacznijmy od formularza. Najpierw trzeba przechwycić w jQuery zdarzenie związane z kliknięciem w submit i zablokować wykonanie domyślnej akcji, za pomocą funkcji preventDefault():

$("form").bind('submit', function(ev) { //bierze handler do event
    ev.preventDefault();
});

Następnie za pomocą funkcji serialize() należy przechwycić dane z formularza i zdefiniować sobie url do odpowiedniej funkcji wybranego kontrolera, w której będziemy dane z formularza przetwarzać:

$("form").bind('submit', function(ev) { //bierze handler do event
    ev.preventDefault();
    var params = $(this).serialize();
    var strUrl = '/ajax/add/';
});

Skoro mamy już wszystkie niezbędne dane należy wywołać ajaxową metodę post() i przesłać dane gdzie należy:

$("form").bind('submit', function(ev) { //bierze handler do event
    ev.preventDefault();
    var params = $(this).serialize();
    var strUrl = '/ajax/add/';
    $.post(strUrl,	params,function(data){
	  //obsługa tego co wróci z kontrolera
    });
});

Załóżmy w tym momencie, że z kontrolera wróci string w postaci: “$intTagID,$strTagName” (jak to zrobić napiszę dalej) będziemy chcieli wykorzystać te dane, żeby dołożyć pod zdjęciem tag z przyciskiem do kasowania.Zatem w miejscu, które wskazałam (linia nr 6) należy umieścić następujący kod:

var strTags = new String(data);
var arrTags = strTags.split(",");
var tagButton = '<div id="tag_' + arrTags[0] + '">' + arrTags[1] 
    + '<a href="#" id="' + arrTags[0] + '">x</a></div>';
$('#tag_list').append(tagButton);				

W pierwszych dwóch liniach dzielimy string na tablicę. Następnie definiujemy kod html, który opusuje ramkę tagu z przyciskiem do kasowania. W ostatniej linii dopisujemy zdefiniowany fragment kodu na końcu listy tagów, która znajduje się wewnątrz elementu o id=tag_list. I gotowe.

Skoro tak, to teraz zrobimy szybko obsługę linku do kasowania, który będzie się pojawiał przy dodanych tagach. Podobnie jak poprzednio pora zacząć od przechwycenia zdarzenia i zablokowania domyślnej akcji. Tym razem chodzi kliknięcie w link:

$("a").live('click', function(ev){
    ev.preventDefault();
});

Tym razem do przechwycenia zdarzenia nie wykorzystałam funkcji bind() jak to miało miejsce poprzednio, ale sięgnęłam po funkcję live(), ponieważ tych linków, które chcę obsłużyć, nie było w chwili ładowania dokumentu, a tylko w stosunku do takich elementów zadziała funkcja bind(). Jeśli chcemy przechwycić zdarzenie dla elementu wstawianego dynamicznie po załadowaniu dokumentu należy się posłużyć metodą live().

Po ustaleniu linku do odpowiedniej metody wybranego kontrolera, która ma obsłużyć zdarzenie, przesyłamy do niej dane metodą get():

$("a").live('click', function(ev){
    ev.preventDefault();
	var strUrl = 'ajax/del/' + $(this).attr('id'); 
	$.get(strUrl,function(data){
		//obsługa tego co zwróci kontroler
	});
});

Załóżmy, że postaramy się o to, by w przypadku pomyślnego usunięcia tagu z bazy kontroler zwrócił id usuniętego tagu. Wtedy we wskazanym miejscu (linia nr 5) wystarczy wstawić kod:

if(data){
	$('div#tag_' + data).remove();
}	

Ten fragment skryptu spowoduje usunięcie z listy ramki tagu o wskazanym id. I już.

Obsługa zdarzenia w kontrolerze

W kontrolerze, w metodach, które mają obsłużyć przechwycone zdarzenia i zwrócić coś do skryptu jQuery, należy przede wszystkim sprawdzić, czy żądanie jest AJAX-em. Służy do tego metoda is_ajax(). Następnie trzeba zablokować renderowanie jeśłi kontroler dziedziczy po kontrolerze Template. Aby przekazać dane z powrotem do jQuery wystarczy wyświetlić je za pomocą funkcji echo. Tak więc metody wykorzystane przeze mnie będa wyglądały następująco:

public function add(){
	if (request::is_ajax()){
		$this->auto_render = false;
		$strTagName = $this->input->post('tag');
			//tu komunikujemy się z modelem, dodajemy tag do bazy 
			//i pobieramy jego id do zmiennej $intTagId;
		echo $intTagId.','.$strTagName;		
	}
}
	
public function del($intTagID){
	if (request::is_ajax()){
		$this->auto_render = false;
			//tu komunikujemy się z odpowiednim modelem, usuwamy tag z bazy
		echo $intTagID;		
	}
}

I to tyle. Wynik działania skryptu można obejrzeć sobie na przykładzie tego DEMO. Dowcipnisiów uprzejmie informuję, że to demo nie dodaje nic do żadnej bazy, więc nie ma sensu próbować jej zapychać.

Przy okazji chcę podziękować kolegom z forum kohanaphp.pl dzięki którym powstał ten wpis.

5 komentarzy do wpisu „Obsługa linków i formularzy za pomoca AJAXa”

  1. Super extra, ale co w przypadku jak ktoś wyłączy js? Strona traci całą funkcjonalność, tak nie powinno być, najpierw tworzy sie akcje jakby js nie istniał a dopiero potem dodaje sie ajaxa żeby uprzyjemnić jej działanie

  2. kwiateusz: Owszem strona straci funkcjonalność, ale to nie o tym jest artykuł, tylko o tym jak to zrobić w JS. To nie gotowiec :)

    Rafał: Ja nie jestem Master Of JS więc piszę jak pokazują w różnych manualach. Pewnie nie zawsze jest to najlepsze i najbardziej optymalne. Dzięki za linka. W wolnej chwili będę studiować.

  3. Parę uwag:
    1. W html4 atrybut id powinien zaczynać się od litery, nie cyfry.
    2. Przesyłanie odpowiedzi w formacie json ułatwia późniejszą obsługę danych – można uniknąć splitów czy innych wygibasów.
    3. Sugeruję zapoznać się z metodą .delegate(), która pozwala na ograniczenie przechwytywania zdarzeń do danego elementu, a nie dla całego dokumentu, jak to ma miejsce przy .live()
    Póki co, to by było na tyle.

Leave a Reply

%d bloggers like this: