Chapter 10. Customizing the CRM Application

In this final chapter, we will add functionality to our framework to allow for some finishing touches.

Specifically, we will see:

  • What is needed to enhance the user interface to use the sort and filter capabilities in the framework
  • How we can provide the end user with the means to customize an application without the need to program
  • How to use these customizations to enhance the display of items and list of items
  • How to enhance the stored information with information from external sources such as Google Maps

Time for action sorting

When we implemented the Browse class in Chapter 8, Managing Customer Relations, together with the underlying functionality in the listids() method of the AbstractEntity class, we already took care of sorting and filtering.

We did not yet allow for any user interaction to make it actually possible to sort the list of entities shown. What was missing was the JavaScript code and some CSS to make this happen. Take a look at the following screenshot and notice the small arrow icons next to some headers on top of the list of accounts:

Time for action sorting

You can try the sort options for yourself when you run crm2.py.

Clicking once on a column marked with the small double arrows will sort the list on that specific column in ascending order. The header will change its background color to indicate that the list is now sorted and the small icon will change into a single up arrow.

Clicking it again will sort the list in descending order and this will be indicated by a small icon of a downward pointing arrow. A final click will render the list of items unsorted, as clicking the reset button will (not shown).

What just happened?

This sorting behavior is implemented by a few small parts:

  • jQuery click handlers associated with the table headers
  • Some CSS to style those headers with suitable icons
  • Minor changes to the Python code that produces the table to make it simpler to track the sorting state in the browser.

First, let's see what has to be added to the JavaScript (the complete file is available as browse.js):

Chapter10/browse.js

$(".notsorted").live('click',function(){
	$("input[name=sortorder]").remove();
	$(".content form").first() 
	.append('<input type="hidden" name="sortorder" value="' 
			+$("div.colname",this).text()+',asc">'),
	$("button[name=first]").click();
}).live('mouseenter mouseleave',function(){
	$(this).toggleClass("ui-state-highlight");
});
$(".sorted-asc").live('click',function(){
	//alert('sorted-asc '+$(this).text())
	$("input[name=sortorder]").remove();
	$(".content form").first() 
	.append('<input type="hidden" name="sortorder" value="' 
			+$("div.colname",this).text()+',desc">'),
	$("button[name=first]").click();
}).live('mouseenter mouseleave',function(){
	$(this).toggleClass("ui-state-highlight");
});
$(".sorted-desc").live('click',function(){
	//alert('sorted-desc '+$(this).text())
	$("button[name=clear]").click();
}).live('mouseenter mouseleave',function(){
	$(this).toggleClass("ui-state-highlight");
});

Installing the click handlers is straightforward in itself, but what they have to accomplish is a little complicated.

The click handler must first determine which column will be used as the sort key. The element that is clicked on is available to the handler as this and this will give us access to a<div> element within the header that contains the column's name. This<div> element is not shown because its display attribute will be set to none. It is added because we need access to the column's canonical name. The<th> element itself contains just the display name of the column, which may be different from its canonical name.

This sort key will have to be passed to the application server and to this end we will use the machinery already in place: if we trigger submission of the form with the navigation buttons and make sure the proper sort parameters are passed along, we're almost there. How can this be accomplished?

jQuery provides convenient methods to insert new HTML elements into the existing markup (highlighted). From the name of the column, we construct a suitable value by appending either asc or desc to it, separated by a comma and use this as the value of a new hidden input element with a name of sortorder and insert this into the first<form> element with the append() method. The first form in the page is the form element containing the navigation buttons.

Because these same types of hidden<input> elements are used to maintain state when the user pages through the list of items, we first remove any<input> elements with a name attribute equal to sortorder to make sure these elements reflect the newly selected sort order and not any old one. The removal is accomplished by the aptly named remove() method.

The final step is to submit the form. We could trigger the submit event itself but because we have several buttons with a type attribute equal to submit, we have to be more specific.

It is not possible to trigger a submit event on a button, only on a form, but it is possible to trigger the click event on a button, thus mimicking the user interaction. Once the click event is triggered on the button with the name attribute of first, the form is submitted together with all its<input> elements, even hidden ones, including the new or replaced ones that indicate the sort order.

The handler for a<th> element that is already sorted in ascending order and marked by a sorted-asc class is almost identical. The only change we make is that the value of the hidden<input> element with name=sortorder is the column name with a ,desc suffix instead of an ,asc suffix.

Clicking on the<th> element when it is already sorted in descending order will cycle back to showing the unsorted state, so this click handler is even simpler as it just triggers the click handler of the clear button, which will result in an unsorted list.

The changes in the index() method in the Browse class are as follows (full code available as browse.py):

Chapter10/browse.py

yield '<thead><tr>'
				for col in self.columns:
					if type(col) == str :
						sortclass="notsorted"
						iconclass="ui-icon ui-icon-triangle-2-n-s"
						for s in sortorder:
								if s[0]==col :
									sortclass='sorted-'+s[1]
									iconclass=' ui-icon ui-icon-
triangle-1-%s'%({'asc':'n','desc':'s'}[s[1]])
									break
						yield '<th class="%s"><div class="colname" 
style="display:none">%s</div>'%(sortclass,col)+self.entity.
displaynames[col]+'<span class="%s"><span></th>'%iconclass
				else :
						yield '<th>'+col.__name__+'</th>'
		yield '</tr></thead>
<tbody>
'

The Python code in our application barely has to change to accommodate this way of interaction. We merely adorn the<th> element of a column with a class that indicates the state of sorting.

It's either notsorted, sorted-asc, or sorted-desc. We also insert a<div> element to hold the true name of the column and an empty<span> element flagged with suitable jQuery UI icon classes to hold the icons that signal the sorting state (highlighted).

The sortorder list holds a number of tuples, each with the name of the column to sort as the first element and either asc or desc as the second element. This second element is used as the index into a dictionary that maps asc to n and desc to s, resulting in choosing either a ui-icon-triangle-1-n or a ui-icon-triangle-1-s class. Appending these classes together with a ui-icon class is all we need to let the jQuery UI stylesheets render our<span> element with a meaningful icon.

Note

Many arrow-like icons, available in jQuery UI, follow a naming pattern similar to those for the small triangles here. The final part indicates a compass direction (here n for north, or upward) and the number indicates how many arrowheads are depicted in the icon (here just one, but there are many double-headed variants).

The resulting HTML for a column named time that is currently sorted in ascending order, would look something like this:

<th class="sorted-asc">
<div class="colname" style="display:none">time</div>
Time
<span class="ui-icon ui-icon-triangle-1-n"><span>
</th>

Besides the icon, we add some additional styles to base.css to make the headers more visible:

Chapter10/base.css

th.notsorted { padding-right:1px; border:solid 1px #f0f0f0; }
th.sorted-asc { padding-right:1px; border:solid 1px #f0f0f0; 
background-color: #fff0f0; }
th.sorted-desc { padding-right:1px; border:solid 1px #f0f0f0; 
background-color: #fffff0; }
th span { float:right; }

The table headers themselves are merely styled with a light gray color, but floating the<span> element that will hold the icon to the right is important, otherwise it would move below the text in the column header instead of the side.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.67.16