Zarządzanie danymi tabelarycznymi za pomocą dhtmlxGrid

Prosta aplikacja Django zarządzająca danymi tabelarycznymi za pomocą grida dhtmlxGrid stworzonego przez DHTML eXtensions.

DHTML eXtensions, czyli DHTMLX to zbiór gotowych JavaScriptowych frontendów i widżetów taki jak drzewa, layouty, gridy, comboboxy, okna i inne. Na stronie projektu znajdziemy przykłady i opisy wszystkich komponentów. Pakiet dostępny jest w dwóch wersjach - GPL dla projektów na tej samej otwartej licencji, oraz w płatnej wersji komercyjnej z większymi możliwościami. Darmowa wersja posiada dość spory zasób możliwości pozwalających stworzyć funkcjonalne interfejsy w naszych aplikacjach. W odróżnieniu widżetów Ext.js, czy jQuery otrzymujemy prosto z pudełka bardziej kompleksowy widżet, który posiada gotowy interfejs do funkcjonalności server-side (choć i w jQuery można dojść do tego samego).

dhtmlxGrid

dhtmlxGrid to JavaScriptowy grid do zarządzania i prezentacji danych tabelarycznych. Posiada gotowe rozwiązania do obsługi dużych kolekcji danych ładowanych porcjami poprzez żądania Ajax w formatach takich jak XML, JSON, CSV, tablice JS i tabele HTML. Na stronie dhtmlx znajdziemy dokumentację oraz liczne przykłady.

grid1

Szybki start z dhtmlxGrid

Poniższy przewodnik pokaże jak wykorzystać grida na statycznej stronie HTML - jak go skonfigurować i jak można go rozbudowywać.

  • Gorąco polecam stosowanie Firefoksa z wtyczką Firebug. Jest to podstawowe narzędzie do debugowania i śledzenia przebiegu operacji wykonywanych przez komponenty dhtmlx.
  • Pobieramy pakiet grida na licencji GPL dhtmlxGrid.zip
  • Wypakuj archiwum w pustym katalogu (ja wszystko wrzuciłem do podkatalogu dhtmlxGrid
  • Żeby wykorzystać grida (czy inny komponent dhtmlx) należy stworzyć funkcję rozruchową, która wygeneruje widżet we wskazanym DIVie na stronie. Prosty szkielet dla grida wyglądałby tak:
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
    	<title>xGrid</title>
    	<!-- pliki xGrida -->
    	<link rel="stylesheet" type="text/css" href="dhtmlxGrid/dhtmlxGrid/codebase/dhtmlxgrid.css" />
    	<link rel="stylesheet" type="text/css" href="dhtmlxGrid/dhtmlxGrid/codebase/skins/dhtmlxgrid_dhx_skyblue.css" />
    	<script src="dhtmlxGrid/dhtmlxGrid/codebase/dhtmlxcommon.js" type="text/javascript"></script>
    	<script src="dhtmlxGrid/dhtmlxGrid/codebase/dhtmlxgrid.js" type="text/javascript"></script>
    	<script src="dhtmlxGrid/dhtmlxGrid/codebase/dhtmlxgridcell.js" type="text/javascript"></script>
    	<script type="text/javascript">
    		// funkcja inicjalizująca grid.
    		function doInitGrid(){
    			mygrid = new dhtmlXGridObject('grid_container'); // ID DIVa, w którym pojawi się grid
    			mygrid.setImagePath("dhtmlxGrid/dhtmlxGrid/codebase/imgs/"); // ścieżka do grafik
    			mygrid.setHeader("A,B,C"); // etykiety kolumn
    			mygrid.setSkin("dhx_skyblue"); // skórka
    			mygrid.init();
    			}
    		
    	</script>
    </head>
    <body onload="doInitGrid();">
    
    <div id="my_grid">
    	<div id="grid_container" style="height:400px;"></div>
    </div>
    
    </body>
    </html>
    
    W sekcji HEAD załączamy style, oraz pliki JS grida, a następnie definiujemy funkcję doInitGrid generującą grid. Wewnątrz tej funkcji możemy określić liczne opcje konfiguracyjne grida, o których nieco później. Powyższy przykład wyświetli pusty grid z samymi kolumnami, bez wierszy.
  • Dane mogą być wczytywane z różnych źródeł, np. z dokumentów XML. By załadować wiersze z dokumenty XML wystarczy dodać do funkcji doInitGrid:
    mygrid.loadXML('dane.xml');
    
    Gdzie dane.xml to nazwa pliku XML z danymi (wraz ze ścieżką).
  • Dla xGrida format dokumenty XML ma postać:
    <?xml version="1.0" encoding="UTF-8"?>
    <rows>
    	<row id="1">
    		<cell>Kol A</cell>
    		<cell>Kol B</cell>
    		<cell>Kol C</cell>
    	</row>
    	<row id="2">
    		<cell>Kol Aa</cell>
    		<cell>Kol Bb</cell>
    		<cell>Kol Cc</cell>
    	</row>
    	<row id="3">
    		<cell>wartość</cell>
    		<cell>przykładowa</cell>
    		<cell>123</cell>
    	</row>
    </rows>
    
    Każdy wiersz (row) posiada swoje unikalne ID, oraz zestaw komórek z ich wartościami. Nasz przykład wygląda obecnie tak (należy go odpalić na serwerze, np. lokalnym apache):
    grid2
  • Do grida można dodać filtrowanie, sortowanie, ładowanie wierszy z dużych zbiorów danych, połączyć go z innymi komponentami dhtmlx. Wymaga to już języka/aplikacji server-side, która będzie obsługiwać te funkcjonalności. Zaprezentowany powyżej przykład to wyjściowy kod jaki rozbudowując da nam w pełni dynamiczny grid prezentujący nasze dane.

dhtmlxGrid i Django

Stosując dhtmlxGrid w aplikacji Django, GAE itp. nie trafimy na jakieś specjalne problemy i przypadki. Widoki zwracające dane w postaci dokumentów XML muszą także stosować poprawny typ MIME. Wystarcz dodać do np. render_to_response:
mimetype='text/xml'
Mając już opanowane podstawy xgrida możemy stworzyć prostą aplikację Django wykorzystującą możliwości tego widżetu do obsługi dużych kolekcji danych wraz z ich edycją.
  • Tworzymy projekt i aplikację:
    django-admin.py startproject grid
    python manage.py startapp testgrid
  • Ustawiamy dane bazy danych (ja użyłem SQLite), dodajemy "testgrid" do INSTALLED_APPS.
  • Tworzymy model wierszy:
    from django.db import models
    
    class Row(models.Model):
    	cell1 = models.CharField(max_length=255, blank=True)
    	cell2 = models.CharField(max_length=255, blank=True)
    	cell3 = models.CharField(max_length=255, blank=True)
    	cell4 = models.CharField(max_length=255, blank=True)
    	cell5 = models.CharField(max_length=255, blank=True)
    	
    	def __unicode__(self):
    		return self.cell1
    	class Meta:
    		verbose_name = 'Wiersz'
    		verbose_name_plural = 'Wiersze'
    	
    
    Zakładamy prosty przykład sztywnego pięciokolumnowego grida.
  • Skopiuj cały katalog dhtmlxGrid (pliki grida) do katalogu site_media projektu Django
  • Plik HTML z kodem wyświetlającym grid kopiujemy do katalogu szablonów (templates) jako show_grid.html, a plik dane.xml jako get_data.xml. Wykorzystamy je w dwoch widokach odpowiednio wyświetlający grid i przesyłający dane do grida.
  • Oto wstępne wersje obu widoków:
    #!/usr/bin/python
    
    from django.shortcuts import render_to_response
    from django.conf import settings
    from django.template import RequestContext
    from django.http import HttpResponseRedirect,HttpResponse
    
    from testgrid.models import *
    
    def show_grid(request):
    	"""
    	Display the page with grid
    	"""
    	return render_to_response(
    		'show_grid.html',
    		{},
    		context_instance=RequestContext(request))
    
    def get_data(request):
    	"""
    	Return grid data in XML format
    	"""
    	data = Row.objects.all()[:20]
    	return render_to_response(
    		'get_data.xml',
    		{'data': data},
    		mimetype='text/xml', context_instance=RequestContext(request))
    
    Szablon get_data.xml modyfikujemy do postaci:
    <?xml version="1.0" encoding="UTF-8"?>
    <rows>
    	{% for i in data %}
    	<row id="{{ i.id }}">
    		<cell>{{ i.cell1 }}</cell>
    		<cell>{{ i.cell2 }}</cell>
    		<cell>{{ i.cell3 }}</cell>
    		<cell>{{ i.cell4 }}</cell>
    		<cell>{{ i.cell5 }}</cell>
    	</row>
    	{% endfor %}
    </rows>
    
    Natomiast w show_grid.html zmieniamy ścieżki do plików statycznych i dokumentu XML z danymi:
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
    	<title>xGrid</title>
    	<!-- pliki xGrida -->
    	<link rel="stylesheet" type="text/css" href="/site_media/dhtmlxGrid/dhtmlxGrid/codebase/dhtmlxgrid.css" />
    	<link rel="stylesheet" type="text/css" href="/site_media/dhtmlxGrid/dhtmlxGrid/codebase/skins/dhtmlxgrid_dhx_skyblue.css" />
    	<script src="/site_media/dhtmlxGrid/dhtmlxGrid/codebase/dhtmlxcommon.js" type="text/javascript"></script>
    	<script src="/site_media/dhtmlxGrid/dhtmlxGrid/codebase/dhtmlxgrid.js" type="text/javascript"></script>
    	<script src="/site_media/dhtmlxGrid/dhtmlxGrid/codebase/dhtmlxgridcell.js" type="text/javascript"></script>
    
    	<script type="text/javascript">
    		// funkcja inicjalizująca grid.
    		function doInitGrid(){
    			mygrid = new dhtmlXGridObject('grid_container'); // ID DIVa, w którym pojawi się grid
    			mygrid.setImagePath("/site_media/dhtmlxGrid/dhtmlxGrid/codebase/imgs/"); // ścieżka do grafik
    			mygrid.setHeader("A,B,C,D,E"); // etykiety kolumn
    			mygrid.setSkin("dhx_skyblue"); // skórka
    			mygrid.loadXML('/get_data/');
    			mygrid.init();
    			}
    		
    	</script>
    </head>
    <body onload="doInitGrid();">
    
    <div id="my_grid">
    	<div id="grid_container" style="height:400px;"></div>
    </div>
    
    </body>
    </html>
    
  • Mamy wstępnie gotowe dwa widoki. Do testów stworzyłem też skrypt spawn.py do generowania wielu wierszy w bazie danych:
    # -*- coding: utf-8 -*-
    import sys
    from os import environ
    
    environ['DJANGO_SETTINGS_MODULE'] = 'settings'
    
    from settings import *
    
    from testgrid.models import *
    
    for i in range(1,500):
    	print i
    	r = Row(cell1='abc', cell2='d', cell3=i, cell4='1234', cell5='this is Sparta!')
    	r.save()
    
    Gdy wygenerujemy kolekcję wierszy i otworzymy w przeglądarce naszą aplikację - zobaczymy pierwsze 20 wierszy z bazy danych.

Dynamiczne ładowanie wierszy

Mając w bazie tysiące i więcej rekordów nie da się ich załadować do grida w jednej operacji. dhtmlXgrid posiada mechanizm pobierania grupy wierszy na żądanie. Wywołuje on wtedy URL /get_data/ (URL dokumentu danych) z dwoma parametrami GET: posStart i count. Pierwszy oznacza offset od pierwszego rekordu, a drugi ilość wierszy jakiej potrzebuje.
  • Do funkcji rozruchowej grida dodaj:
    mygrid.enableSmartRendering(true);
  • Zmodyfikuj tag "rows" w szablonie get_data.xml do postaci:
    <rows total_count="{{ total }}" pos="{{ pos }}">
    
  • Do show_grid.html dodaj:
    <script src="/site_media/dhtmlxGrid/dhtmlxGrid/codebase/ext/dhtmlxgrid_srnd.js"></script>
    
  • Widok get_data zmień do postaci:
    def get_data(request):
    	"""
    	Return grid data in XML format
    	"""
    	if 'posStart' in request.GET:
    		offset = request.GET['posStart']
    		quantity = request.GET['count']
    	else:
    		offset = 0
    		quantity = 20
    	
    	data = Row.objects.all()[offset:offset+quantity]
    	
    	total = Row.objects.all().count()
    	return render_to_response(
    		'get_data.xml',
    		{'data': data, 'total': total, 'pos': offset},
    		mimetype='text/xml', context_instance=RequestContext(request))
    
    Nasz grid potrafi teraz wczytać wszystkie wiersze na żądanie, gdy będziemy przewijać grid.
grid3

dataprocessor do edycji wierszy

W skład komponentów dhtmlx wchodzi dataprocessor - pośrednik między gridem a językami server side. Pozwala on na obsługę edycji, kasowania, czy dodawania wierszy. Dla przykładu dodamy możliwość edycji wierszy za jego pomocą. Po włączeniu dataprocessora po edycji wiersza na wskazany URL automatyczne zostaną wysłane dane zmodyfikowanego wiersza (jako parametry GET):
?gr_id=6&c0=abc&c1=dsdsd&c2=6&c3=1234&c4=this%20is%20Sparta!
Gdzie gr_id to ID wiersza, a c0-c* to wartości kolejnych komórek.
  • Do show_grid.html dostawiamy:
    <script src="/site_media/dhtmlxGrid/dhtmlxDataProcessor/codebase/dhtmlxdataprocessor.js"></script>
    
    Do funkcji doInitGrid dodajemy:
    var dp = new dataProcessor('/update_data/');
    dp.init(mygrid);
    dp.defineAction("error_of_datastore",handle_error_of_datastore);
    
  • Do tego samego szablonu dodajemy funkcję JS informującą o błędzie zapisu:
    function handle_error_of_datastore(obj)
    			{
    			alert('Dane nie zostały zapisane z powodu błędu!<br />' + obj.firstChild.data);
    			return false;
    			}
    
  • Zapis obsługujemy przez nowy widok update_data:
    def update_data(request):
    	"""
    	Update row
    	"""
    	try:
    		rid = request.GET['gr_id']
    		r = Row.objects.get(id=rid)
    		r.cell1 = request.GET['c0']
    		r.cell2 = request.GET['c1']
    		r.cell3 = request.GET['c2']
    		r.cell4 = request.GET['c3']
    		r.cell5 = request.GET['c4']
    		r.save()
    	except:
    		return render_to_response(
    			'update_data_error.xml',
    			{},
    			mimetype='text/xml', context_instance=RequestContext(request))
    	else:
    		return render_to_response(
    			'update_data.xml',
    			{'id': rid},
    			mimetype='text/xml', context_instance=RequestContext(request))
    
  • Szablon update_data.xml wygląda tak:
    <?xml version="1.0" encoding="UTF-8"?>
    <data>
    <action type="update" sid="{{ id }}" tid="{{ id }}" />
    </data>
    
  • update_data_error.xml ma postać:
    <?xml version="1.0" encoding="UTF-8"?>
    <data>
    	<action type="error_of_datastore">Coś się zepsuło</action>
    </data>
    
Nasza przykładowa aplikacja jest gotowa.

Kod Źródłowy

blog comments powered by Disqus

Kategorie

Strony