Zarządzanie danymi tabelarycznymi za pomocą dhtmlxGrid
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.

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:
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.
<?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>
- 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:
Gdzie dane.xml to nazwa pliku XML z danymi (wraz ze ścieżką).
mygrid.loadXML('dane.xml');
- Dla xGrida format dokumenty XML ma postać:
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):
<?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>

- 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:- 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:
Zakładamy prosty przykład sztywnego pięciokolumnowego grida.
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'
- 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:
Szablon get_data.xml modyfikujemy do postaci:
#!/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))
Natomiast w show_grid.html zmieniamy ścieżki do plików statycznych i dokumentu XML z danymi:<?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>
<?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:
Gdy wygenerujemy kolekcję wierszy i otworzymy w przeglądarce naszą aplikację - zobaczymy pierwsze 20 wierszy z bazy 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()
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:
Nasz grid potrafi teraz wczytać wszystkie wiersze na żądanie, gdy będziemy przewijać grid.
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))

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):- Do show_grid.html dostawiamy:
Do funkcji doInitGrid dodajemy:
<script src="/site_media/dhtmlxGrid/dhtmlxDataProcessor/codebase/dhtmlxdataprocessor.js"></script>
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>
Comment article