In this final chapter, we will add functionality to our framework to allow for some finishing touches.
Specifically, we will see:
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:
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).
This sorting behavior is implemented by a few small parts:
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.
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.
3.147.67.16