HTML5 localStorage

HTML5 Web Storage (określane również DOMStorage) jest to API, które ułatwia utrzymywanie danych pomiędzy stronami www. Można przechowywać dane bezpośrednio po stronie klienta w przeglądarce w celu ich wielokrotnego wykorzystania lub do pobrania dużo później po całkowitym zamknięciu przeglądarki i jej ponownym uruchomieniu. Web Storage dzieli się na sessionStorage i localStorage. W przypadku sessionStorage wartości mogą być przechowywane tak długo jak okno lub karta, w którym były przechowywane. Ponadto są widoczne tylko dla tego okna (lub karty), które je stworzyło. Dla localStorage czas życia jest dłuższy niż czas życia okna przeglądarki oraz wartości są dzielone dla każdego okna, karty z tego samego pochodzenia.

localStorage w praktyce

Stworzymy prostą listę zadań w localStorage, a właściwie kilka jej wersji stopniowo rozszerzając funkcjonalności. To ostateczna wersja todo listy. Możesz również pobrać wszystkie przykłady. TODO Lista
Zaczniemy od stworzenia szkieletu dokumentu html.
<!DOCTYPE html>
<html lang="pl">
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="style.css">
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
    <title>HTML5 localStorage demo</title>
  </head>
  <body>
  	<h1>Do zrobienia</h1>
		<ul id="todolist" contenteditable="true">
		    <li>Zadanie pierwsze</li>
		    <li>Matematyka</li>
		    <li>Zakupy</li>
		    <li>Kolejne zadanie</li>
		</ul>
	<a id="clear" href="#">Czyść</a>
    <script>
    	//miejsce na skrypty
    </script>
  </body>
</html>
W pierwszym przykładzie zastosowaliśmy dla naszej listy atrybut contenteditable. Ustawiliśmy jego wartość na true, co oznacza że zawartość listy można edytować. Jest to tzw. edycja w miejscu, która w HTML5 jest osiągalna właśnie dzięki temu jednemu atrybutowi. Czas teraz zacząc pracę z Javascript. Wykorzystałam trochę również bibliotekę jquery. Zapisujemy w zmiennej naszą listę.
var todo = document.getElementById('todolist');
Po kliknięciu na jej zawartość możemy ją edytować dzięki atrybutowi contenteditable. Chcemy teraz, aby nasze zmiany zostały zapamiętane nawet gdy odświeżymy stronę lub zamkniemy okno przeglądarki. Dla zdarzenia blur naszej listy zapisujemy dane w localStorage. Robimy to funkcją setItem, która pod określonym kluczem (pierwszy parametr) zapisuje dane (drugi parametr). U nas kluczem jest todoData, a danymi zawartość listy todo.
$("#todolist").blur(function() {
	localStorage.setItem('todoData', this.innerHTML);
});
Zapisaliśmy dane w localStorage, ale to nie koniec. Musimy je jeszcze odczytać (po załadowaniu strony). Wykorzystujemy funkcję getItem, której jako parametr przekazujemy klucz spod którego chcemy odczytać dane. Jak widać na poniższym listingu jeśli istnieją jakieś dane zapisane pod kluczem todoData wstawiamy je do naszej listy ul.
if (localStorage.getItem('todoData')) {
	todo.innerHTML = localStorage.getItem('todoData');
}
Na koniec dodamy możliwość czyszczenia wszystkich danych zapisanych w localStorage. Dla zdarzenia click na linka o id clear czyścimy localStorage metodą clear.
$('#clear').click(function(){
	localStorage.clear();
	return false;
});
Przekształcimy trochę powyższy przykład na następujący. Zamiast atrybutu contenteditable dodamy zwykłe pole tekstowe do dodawania nowych zadań.
<input type="text" name="new_task" id="new_task" />
<input id="add_task" type="submit" value="" title="Dodaj zadanie" />
Część dotycząca odbierania danych pozostaje bez zmian. Przekształcenia wymaga część zapisywania nowych danych do localStorage. Oczywiście przechwytujemy zdarzenie kliknięcia w przycisk dodawania zadania. Pod zmienną new_item zapisujemy tekst, który wpisaliśmy w pole tekstowe. Następnie metodą append jquery dodajemy nowy element li do listy. Ostatecznie zapisujemy dane całej listy w localStorage.
$('#add_task').live('click',function(){
	var new_item = document.getElementById('new_task').value;
	$('#todolist').append('<li>'+new_item+'</li>');
	localStorage.setItem('todoData', todo.innerHTML);
});

Priorytety zadań i przywitanie z JSON

Pomyślałam, że ciekawe byłoby dodanie możliwości ustawiania priorytetów zadań. Wyboru priorytetu dodawanego zadania dokonamy za pomocą rozwijanej listy.
<select id="task_priority">
	<option value="high">Wysoki</option>
	<option value="normal" selected="selected">Normalny</option>
	<option value="low">Niski</option>
</select>
Dane w localStorage przechowywane są jako zwykły tekst. Co zrobić jeśli chcemy manipulować danymi zapisanymi jako obiekty? Po prostu należy przed zapisaniem danych do localStorage zamienić je na ciąg znaków, a przy ich odczycie znowu na obiekt. Oczywiście mowa tu o obiekcie JSON. Do zamiany obiektu na string i na odwrót wykorzystamy json2. Wykorzystuje ona wbudowane metody przeglądarek jeśli takowe posiadają, a jeśli nie implementuje własne. W sekcji head dodajmy więc linijkę.
<script src="json2.js"></script>
Funkcja odpowiedzialna za zapis nowego elementu jest już teraz bardziej skomplikowana. Skupię się na omówieniu nowych rzeczy. Pod zmienną priority zapisujemy wybrany priorytet zadania tzn. wartość wybranego elementu option. Potem tak jak w poprzednim przykładzie dodajemy element do listy metodą append. Jedyną różnicą jest to, że do wygenerowania elementu li używamy też zmiennej priority. Po to aby dodać odpowiednią nazwę klasy dla tego elementu. Dzięki klasom możemy określić odpowiednie style css dla różnych priorytetów zadań np. niski priorytet ma zielone tło. Najważniejszą zmianą w tym przykładzie jest wykorzystanie obiektów. Jeśli chcesz się dowiedzieć więcej na temat obiektów w javascript odsyłam do linków na dole artykułu. Tworzymy obiekt store_data i pod kluczem content tego obiektu zapisujemy zawartość naszej todo listy. Teraz musimy zamienić ten obiekt na ciąg znaków, aby można go było później zapisać w localStorage. Zamiana odbywa się poprzez JSON.stringify(store_data). Następnie uzyskany w ten sposób string można już zapisać w localStorage.
$('#add_task').live('click',function(){
	var new_item = document.getElementById('new_task').value;
	var select = document.getElementById('task_priority');
	var priority = select.options[select.selectedIndex].value;
	$('#todolist').append('<li class="'+priority+'">'+new_item+'</li>');
	var store_data = {
		content : todo.innerHTML
	};
	var store_data_string = JSON.stringify(store_data);
	localStorage.setItem('todoData', store_data_string);
});
W części odbierającej dane też zaszły drobne zmiany. Po pierwsze zamieniamy odebrane dane na obiekt JSON.parse(localStorage.getItem('todoData')). Potem wstawiamy do naszej listy treść zawartą w atrybucie content obiektu.
if (localStorage.getItem('todoData')) {
	var obj = JSON.parse(localStorage.getItem('todoData'));
	todo.innerHTML = obj.content;
}
Teraz przyszedł czas na właściwe wykorzystanie obiektów. W obiekcie będziemy trzymać osobno treść oraz priorytet zadania. Ponadto nasze zadania będziemy przechowywać w tablicy. Stwórzmy dwie zmienne globalne. Pierwsza będzie przechowywała aktualną zawartość listy, a druga przechowywane dane (tablica).
var actual_inner = '';
var store_data = [];
Kod obsługujący dodawanie nowego zadania wygląda teraz następująco. Tym razem nasz obiekt będzie się składał z dwóch atrybutów: item i priority. Wartością atrybutu item będzie treść wpisanego przez nas nowego zadania, a wartością priority wybrany priorytet. Następnie dopisujemy nowy obiekt do tablicy store_data. Na koniec zamieniamy tablicę obiektów (store_data) na string i zapisujemy w localStorage.
$('#add_task').live('click',function(){
	var new_item = document.getElementById('new_task').value;
	var select = document.getElementById('task_priority');
	var priority = select.options[select.selectedIndex].value;
	$('#todolist').append('<li class="'+priority+'">'+new_item+'</li>');
	var store_data_cont = {
		item : new_item,
		priority : priority
	};
	store_data[store_data.length] = store_data_cont;
	store_data_string = JSON.stringify(store_data);
	localStorage.setItem('todoData', store_data_string);
});
Omówię teraz zmiany w części odbierającej dane. Na początku tak samo jak w poprzednim przykładzie zamieniamy odebrane dane na obiekt metodą parse obiektu JSON. Później przypisujemy ten obiekt do zmiennej store_data. Następnie pętlą for in budujemy ciąg znaków (iterujemy się przez obiekty zapisane w tablicy), który wstawimy do listy metodą innerHTML.
if (localStorage.getItem('todoData')) {
	var obj = JSON.parse(localStorage.getItem('todoData'));
	store_data = obj;
	for(item in obj)
	{
		actual_inner = actual_inner+'<li class="'+obj[item].priority+'">'+obj[item].item+'</li>';
	}
	todo.innerHTML = actual_inner;
}
Ostatnim usprawnieniem, które dodamy do naszej listy jest możliwość usuwania poszczególnych zadań. W kodzie dodającym zadania zajdzie jedna zmiana. Mianowicie budowany ciąg znaków elementu li będzie zawierał element a o klasie del_task i atrybutem href ustawionym na liczbę będącą id elementu w tablicy (store_data.length).
$('#todolist').append('<li class="'+priority+'">'+new_item+'<a class="del_task" href="'+store_data.length+'" title="Usuń"></a></li>');
W kodzie odbierającym dane też musimy uwzględnić przycisk usuwania zadania.
actual_inner = actual_inner+'<li class="'+obj[item].priority+'">'+obj[item].item+'<a class="del_task" href="'+item+'" title="Usuń"></a></li>';
Na zakończenie pozostaje tylko określenie akcji usuwania zadania po kliknięciu na linka. Najpierw odczytujemy indeks zadania, które chcemy usunąć (var del_index = $(this).attr('href')). Potem usuwamy odpowiedni element li z drzewa dokumentu oraz element z tablicy store_data. Do usunięcia elementu z tablicy wykorzystujemy metodę splice. Zamiana tablicy na string oraz zapis danych w localStorage jest taki sam jak w poprzednich przykładach.
$('a.del_task').live('click',function(){
	var del_index = $(this).attr('href');
	$(this).parent().remove();
	store_data.splice(del_index,1);
	store_data_string = JSON.stringify(store_data);
	localStorage.setItem('todoData', store_data_string);
	return false;
});
Jeśli chcesz uzyskać więcej informacji na temat localStorage zapraszam na Dive Into HTML5 oraz specyfikację HTML5 localStorage. Zobacz też jakie przeglądarki obsługują localStorage.