wiki:Search_Pagination
Warning: Can't synchronize with repository "(default)" (Unsupported version control system "svn": No module named svn). Look in the Trac log for more information.

Search Pagination

Note

The information on this page is obsolete, incomplete or incorrect and left here only for reference and has not been ported to the new documentation wiki.

Please refer to the TurboGears documentation wiki for up-to-date documentation.

This article is showing you how to make a "Search Pagination" with SQLObject and kid.

Expect result

We assume we have 35 entries, and make each page shows 10 entries.

Expect result is: "| 1 | 2 | 3 | 4 |"

Advanced Expect result is: "Prev | 1 | 2 | 3 | 4 | Next",

"Prev/Next?" only shows if this action is possible. (first page shows "| 1 | 2 | 3 | 4 | Next", last page shows "Prev | 1 | 2 | 3 | 4 |")

Solution

Basic:

  • query data from database
  • select a number of entries as a set
  • offset to next set
  • add url link

Advanced:

  • compare "start not equal to zero" if we need to show the "prev" button.
  • compare "set_end" and "set_size" to decide if we need to show the "Next" button.
  • add url link

Detail

Basic:

controller.py expose function is:

@expose(html=".templates.pagesearch")
def index(self, start = 0):
    a = TableEntry.select(TableEntry.q.fieldA == True, orderBy='fieldB')
    return dict(context = a, start = int(start)*10, offset = 10,)

"a = TableEntry.select...." is SQLObject select expression, it will return a generator type.

then we pass it in a return dict key, which named "context".

The "start" param is used to indicate the "page" sets.

The "offset" param in the return dict is used to control the "size of display list".

kid template pagesearch.kid

We tempt to keep controll logic and presentation seperate. So we do all "view" stuff in template.

Now we have some calculation work,

Since we have 35 entries, and make each page shows 10 entries.

We got total "4" pages.

The expression is :

>>> (35/10)+1
4

So now we programmer see a function with 2 params can be used in template, "35" as "query_size" and "10" as "offset".

>>>query_size = 35
>>>offset = 10
>>>(query_size/offset)+1
4

Yes it works correctly.

We can read each entry from generator by using "for" loop.

To get subsets of the context generator type, we use list-like function to draw target sets. "context[start:end]"

ex: for the first 10 records:

context[0:10]

and for the next 10:

context[10:20]

and so on.

Let's put those python expressions into real kid template

<?python
    query_size = context.count()+1
    end = start*offset +offset
?>
<ul>
  <li py:for="post in context[start:end]">[${post.fieldA}] ${post.fieldB}: ${post.fieldC}</li>
</ul>

<div>
 | <span py:for = " i in xrange((query_size/offset)+1)"> <a href="/?start=${i}">${i+1}</a> | </span>
</div>

kid template's "py:for" is equal to python "for" loop.

"query_size" is equal to "context.count()",

which "context.count()" will return total queried entry numbers.

and we get "offset" param from "index" method's return dict.

"|" is used only for pretty.

Advanced:

Prev:

compare "start not equal to zero" if we need to show the "prev" button.

<span py:if = "start!=0"> <a href = "/?start=${(start/offset)-1}">Prev</a> </span>

Next:

compare "set_end" and "set_size" to decide if we need to show the "Next" button.

<span py:if = "end &#x03C; query_size"><a href = "/?start=${(start/offset)+1}">Next</a> </span>

Since in kid template you can't use small than (<) operator in "kid"'s "py:if" statement.

the operator is mis-recognized as a html quote.

You can use "&#x03C;" for "<" operator and "&#x03E;" for ">" operator.

Extend

you can follow "Pagination Control" for Search Pagination on yahoo Design Pattern Library to implement more functional Search Pagination.  http://developer.yahoo.net/ypatterns/pattern_searchpagination.php

Alternatives to using "<" and ">" operators

There are two alternatives to using those directly (there's no problem with ">" so you should use it to make things more readable):

  • using &lt; and &gt; entities (which are more readable than their Unicode code, specially to avoid mixing charsets...)
  • using Boole's algebra and inverting the comparison you want to do (since ">" can be used directly). So, rewriting the above code you'd end up with:
<span py:if = "query_size >= end"><a href = "/?start=${(start/offset)+1}">Next</a> </span>

(Remember that "a < b" is equal to "b >= a", "a <= b" is the the same as "b > a" and so on.)

These changes will improve readability and maintenability of your code.

Improve Performance

After every thing works, you may want to take a cup of coffee and have confidence to paly some "refactor" trick to improve the performance and clearity.

We intutivily saw that we do too much "coding" stuff on "pagesearch.kid" to present "${params}", we may draw those code back to controller.py to clean the template.

We could return only what we expect to present on template, thus we should do more process and return more keys in dict. It's a bit harder to understand than the previous code.

@expose(html=".templates.welcome")
def index(self, start = 0):
    a = TableEntry.select(TableEntry.q.fieldA == True, orderBy='fieldB')
    offset = 10
    start = int(start)*offset
    query_size = a.count()
    end = start+offset
    context = a[start:end]
    return dict(context = context, start = start, offset = offset, query_size = query_size, end = end)

the "context" will return less sets because we constrain it with [start:end] directly.

Then you can remove the <?python ...?> section in "pagesearch.kid", becausse "query_size", "end" params are already returned by "controller.py".

Then we can try to assign values in dict direcly and remove the temporary parameters.

Finally the code will look like:

@expose(html=".templates.welcome")
def index(self, start = 0):
    a = TableEntry.select(TableEntry.q.fieldA == True, orderBy='fieldB')
    return dict(context = a[start:(start+offset)],
                offset = 10,                 
                start = int(start)*offset, 
                query_size = a.count(), 
                end = start+offset,
                )