Monday, June 2, 2014

Pagination

Most of you have used the pagination on various websites, usually in the context of search results or forum posts. I thought it would be fun to build a simple "paginator", using Factor.

For example, if you are on page 23 of 28 total pages, you might see something like this, where you show the selected page and other pages that you can quickly link to:

<< 1 2 ... 21 22 [23] 24 25 ... 27 28 >>

Creating a specification from this, our goal will be to show:

  • the first two pages
  • the selected page (with two pages before and after)
  • the last two pages

Using the output>array smart combinator (and lexical variables), we can generate a sequence of page numbers, filtered to make sure we only allow valid page numbers between 1 and #pages:

:: pages-to-show ( page #pages -- seq )
    [
        1 2 page {
            [ 2 - ]
            [ 1 - ]
            [ ]
            [ 1 + ]
            [ 2 + ]
        } cleave #pages [ 1 - ] keep
    ] output>array members
    [ 1 #pages between? ] filter ;

Some unit tests demonstrate that this works for our "spec" pretty well:

{ { 1 2 3 99 100 } } [ 1 100 pages-to-show ] unit-test
{ { 1 2 21 22 23 24 25 27 28 } } [ 23 28 pages-to-show ] unit-test
{ { 1 2 3 } } [ 1 3 pages-to-show ] unit-test

Lastly, we can split the page numbers to display ellipsis on gaps, and print something like our original goal above:

:: pages-to-show. ( page #pages -- )
    page #pages pages-to-show
    [ swap - 1 = ] monotonic-split { f } join
    [
        [
            [ number>string ]
            [ page = [ "[" "]" surround ] when ] bi
        ] [ "..." ] if*
    ] map " " join "<< " " >>" surround print ;

See, it works!

IN: scratchpad 1 100 pages-to-show.
<< [1] 2 3 ... 99 100 >>

IN: scratchpad 23 28 pages-to-show.
<< 1 2 ... 21 22 [23] 24 25 ... 27 28 >>

IN: scratchpad 1 3 pages-to-show.
<< [1] 2 3 >>

Using this in a web application is left as an exercise for the reader, although it might be nice to create a furnace.pagination vocabulary that automatically handles this in our web framework.

You can find this code on my GitHub.

2 comments:

Unknown said...

Just wanted to say how much I enjoy these examples. Studying the solution is obviously a good learning opportunity, but since I'm a Factor newbie the exposure to the vocabulary words and how they are used is a huge help.
Thanks for posting them.
Jason

mrjbq7 said...

Hi Jason, glad you liked it. If you have any particular vocabulary you'd like to know more about, suggest it and I'll see if I can blog about it.