Re: Factor

Factor: the language, the theory, and the practice.

Pagination

Monday, June 2, 2014

#math

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.