Using DHTMLXgrid in a Django application

DHTML eXtensions (DHTMLX) is a set of JavaScript widgets/frontend UI elements such as trees, grids, comboboxes, windows etc. On the project web page you will find all components. DHTMLX offers GPL version, and paid commercial one with more features and with support. The components offer out of the box complete solutions - like dhtmlx grid can load partial data on requests from large datasets, edit data and so on.

dhtmlxGrid

dhtmlxGrid is a JavaScript frontend for displaying table like, spreadsheet like data. It can read data from formats like XML, JSON, CSV. For more details check the documentation and examples.

grid1

dhtmlxGrid quickstart

In this how-to I'll show you how to make a skeleton static HTML page with the initial grid code.

  • When using dhtmlx components Firefox extension - Firebug comes really handy. You will be able to debug the app, and track it execution stages.
  • Download the GPL version: dhtmlxGrid.zip
  • Extract the archive to empty folder (I extracted everything to a subfolder dhtmlxGrid)
  • To use dhtmlx grid (or other component) you have to create a initialization function that will put and configure the grid in a selected place on the page. A simple code that does the job would look like this:
    <?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>
    	<!-- xGrid files -->
    	<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">
    		// init function
    		function doInitGrid(){
    			mygrid = new dhtmlXGridObject('grid_container'); // DIV id in which the grid will show up
    			mygrid.setImagePath("dhtmlxGrid/dhtmlxGrid/codebase/imgs/"); // path to images
    			mygrid.setHeader("A,B,C"); // column labels
    			mygrid.setSkin("dhx_skyblue"); // theme
    			mygrid.init();
    			}
    		
    	</script>
    </head>
    <body onload="doInitGrid();">
    
    <div id="my_grid">
    	<div id="grid_container" style="height:400px;"></div>
    </div>
    
    </body>
    </html>
    
    In HEAD we add CSS and JS files for the grid, and then we define doInitGrid function, which will create the grid. In this function you can configure the grid as you like. This example doesn't has any rows in it yet.
  • To load data from XML document we just have to add in doInitGrid function:
    mygrid.loadXML('dane.xml');
    
    Where dane.xml is the file with data.
  • The XML document has this type of structure:
    <?xml version="1.0" encoding="UTF-8"?>
    <rows>
    	<row id="1">
    		<cell>Col A</cell>
    		<cell>Col B</cell>
    		<cell>Col C</cell>
    	</row>
    	<row id="2">
    		<cell>Col Aa</cell>
    		<cell>Col Bb</cell>
    		<cell>Col Cc</cell>
    	</row>
    	<row id="3">
    		<cell>some</cell>
    		<cell>value</cell>
    		<cell>123</cell>
    	</row>
    </rows>
    
    Every row has unique ID and a set of cells with values. You can run this example on a server (like localhost apache).
    grid2
  • The grid supports sorting, filtering, loading data from big datasets and more. But most of this requires some server-side code that will respond to grid requests. Using this skeleton code we can star making real apps that use grid to display data from database.

dhtmlxGrid and Django

There is no special problems or issues when using dhtmlx components with Django, GAE or other Python tools. One thing to remember that XML documents returned to the grid have to have correct MIME. If you use render_to_response, then just add:
mimetype='text/xml'
So now we will make very basic Django application with dynamic loading of data and row editing.
  • Create Django project and app:
    django-admin.py startproject grid
    python manage.py startapp testgrid
  • Edit database settings (I used SQLite), add "testgrid" to INSTALLED_APPS.
  • Create app model:
    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 = 'Row'
    		verbose_name_plural = 'Rows'
    	
    
    This is basic 5-column row.
  • Copy all the static files - dhtmlxGrid folder to site_media folder
  • Copy the HTML file with the skeleton code to templates folder (templates) as show_grid.html and the dane.xml as get_data.xml. We will use them in two views - one to show the grid, and second one to return the data for the grid.
  • This is the initial view code:
    #!/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))
    
    The get_data.xml template we change to:
    <?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>
    
    And in show_grid.html we change the static files path as well as the XML data file path:
    <?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">
    		function doInitGrid(){
    			mygrid = new dhtmlXGridObject('grid_container');
    			mygrid.setImagePath("/site_media/dhtmlxGrid/dhtmlxGrid/codebase/imgs/");
    			mygrid.setHeader("A,B,C,D,E");
    			mygrid.setSkin("dhx_skyblue");
    			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>
    
  • So we have two working views. I've made also a script spawn.py for generating some rows:
    # -*- 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()
    
    When you spawn some rows and open the app in a browser you will see first 20 rows.

Dynamic row loading

Grid can load rows in parts on request. When you have thousands or millions of rows - this allows you to browse them without killing the servers. xgrid can load rows on requests requesting data URL - /get_data/ with two GET variables: posStart i count. First one is offset and second one is the quantity of rows grid needs.
  • To grid init function add:
    mygrid.enableSmartRendering(true);
  • Modify "rows" tag in template get_data.xml:
    <rows total_count="{{ total }}" pos="{{ pos }}">
    
  • Add to show_grid.html:
    <script src="/site_media/dhtmlxGrid/dhtmlxGrid/codebase/ext/dhtmlxgrid_srnd.js"></script>
    
  • Change view get_data into:
    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))
    
    The grid will now load new rows on request when you will be scrolling it.
grid3

dataprocessor for row editing

DHTMLX has also dataprocessor component which connects client-side actions like row edition with server-side actions. We can use it to save changes in edited row. Dataprocessor will send ajax request to given URL with data as GET variables, for example:
?gr_id=6&c0=abc&c1=dsdsd&c2=6&c3=1234&c4=this%20is%20Sparta!
Where gr_idis the row ID and c0-c* are the cell values.
  • Add to show_grid.html:
    <script src="/site_media/dhtmlxGrid/dhtmlxDataProcessor/codebase/dhtmlxdataprocessor.js"></script>
    
    Add to doInitGrid:
    var dp = new dataProcessor('/update_data/');
    dp.init(mygrid);
    dp.defineAction("error_of_datastore",handle_error_of_datastore);
    
  • To the same template add JS function that will handle database errors:
    function handle_error_of_datastore(obj)
    			{
    			alert('Data not saved!<br />' + obj.firstChild.data);
    			return false;
    			}
    
  • The row update is handled by new view 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))
    
  • Template update_data.xml looks like:
    <?xml version="1.0" encoding="UTF-8"?>
    <data>
    <action type="update" sid="{{ id }}" tid="{{ id }}" />
    </data>
    
  • update_data_error.xml looks like:
    <?xml version="1.0" encoding="UTF-8"?>
    <data>
    	<action type="error_of_datastore">Something went wrong</action>
    </data>
    
Our example app is ready

Source Code

RkBlog

Django web framework tutorials, 12 December 2009, Piotr Maliński

Comment article
RkBlog main page Search RSS Contact