As soon as I announced that I had restored content from my old blog, a response followed about a bug. The page index at the bottom of the page overflowed, as previous applications did not have to deal with more pages than would fit on the breadth of the page. A quick fix was to set the overflow
property of the content to hide
so that the page index or any other content for that matter would no longer stick out of the content box. But that was not a real fix of course.
High time to generalize the pageIndex
definition in the library.
The old pageIndex
template rendered a list of links to all pages in a series. Thus with a total of 17 pages and the current page at 4, it would display the following list, where each element is a link, except for the current page:
Previous 1 2 3 4 5 6 7 8 9 [10] 11 12 13 14 15 16 17 Next
In WebDSL this expressed with the following template, which is parameterized with the index
of the current page, a count
of the total number of pages, and the number of items perpage
. The template is parameterized with a call to pageIndexLink(i,s)
, which should produce a link (navigate) to the i
-th page with anchor string s
.
define pageIndex(index: Int, count: Int, perpage: Int) {
var pages : Int := 1 + (count - 1)/perpage
var idx := min(max(1,index), pages)
if(pages > 1) {
pageIndexLink(idx-1, "Previous")
for(i : Int from 1 to pages+1) {
if(i == idx) {
"[" output(i) "]"
} else {
pageIndexLink(i, i + "")
}
}
pageIndexLink(idx+1,"Next")
}
}
Note that the index
is normalized to be within the range 1 to pages
. We’ll see below that the template can be enhanced by including CSS classes for the different cases to associate appropriate style with the list elements.
There is no point in displaying the page index if there is only one page.
If we have room for a page index with at most 10 pages, the index above will overflow. Thus, we can only show a selection of the pages. Typically, an index shows the first and last page(s), and some pages around the current page. For example, in the situation above we could show.
Previous 1 ... 7 8 9 [10] 11 12 ... 17 Next
In general, we want to divide the index into several intervals of pages that we want to include. The parameters for the index are the maximum number of entries in the index (max
) and the number of pages at the start and end of the index (end
). The following definition of pageIndex
iterates over a list of intervals based on these parameters:
define pageIndex(index : Int, count : Int, perpage : Int, max: Int, end: Int) {
var pages : Int := 1 + (count - 1)/perpage
var idx := min(max(1,index), pages)
var intervals : List<List<Int>> := idxIntervals(idx, count, perpage, max, end)
if(pages > 1) {
pageIndexLink(idx-1, "Previous")
for(iv : List<Int> in intervals) {
for(i : Int from iv.get(0) to iv.get(1) + 1) {
if(i == idx) {
"[" output(i) "]"
} else {
pageIndexLink(i, i + "")
}
}
} separated-by { "..." }
pageIndexLink(idx+1,"Next")
}
}
An interval is represented by a list of (two) integers. Thus, the list of intervals is represented by a list of lists of integers. The body of the template is an iteration over intervals with a nested iteration over each interval displaying links to the corresponding pages. The intervals are separated by an ellipsis "..."
.
Remains to compute the intervals. The typical case is where idx
is somewhere in the middle, e.g.
Previous 1 ... 7 8 9 [10] 11 12 ... 17 Next
Thus, we take some pages at the start, a bunch of pages around idx
, and some pages at the end. Given that middle
is (max - (2 * (end + 1)))/2
, i.e. half the number of slots that remain after accounting for the slots at the beginning and end, including the slots taken by the ellipses, this generalizes to the following intervals:
[[1, end], [idx - middle, idx + middle - 1], [pages - end + 1, pages]]
But if idx
is close to one of the edges, it doesn’t make sense to include the left or right gap. For example,
Previous 1 2 3 [4] 5 6 7 8 ... 17 Next
For the left edge the index is too close to the edge if idx
is smaller than end + 2 + middle
. In that case, we only get the intervals:
[[1, end + 1 + 2 * middle], [pages - end + 1, pages]]
On the right edge we have the same situation
Previous 1 ... 10 11 12 [13] 14 15 16 17 Next
with the pattern
[[1,end], [pages - end - 2 * middle, pages]]
Putting this together, the function idxIntervals
computes the appropriate interval:
function idxIntervals(idx: Int, count: Int, perpage: Int, max: Int, end: Int)
: List<List<Int>>
{
var pages : Int := 1 + (count - 1)/perpage;
var middle := (max - (2 * (end + 1)))/2;
var intervals : List<List<Int>>;
if(pages <= max) {
intervals := [[1,pages]];
} else { if(idx <= end + 2 + middle) {
intervals := [[1,end + 1 + 2 * middle],[pages - end + 1, pages]];
} else { if(idx >= pages - end - middle) {
intervals := [[1,end],[pages - end - 2 * middle, pages]];
} else {
intervals := [[1,end],[idx - middle, idx + middle - 1],[pages - end + 1, pages]];
}}}
return intervals;
}
The full definition of pageIndex
is a bit more involved, since it applies CSS classes to the entries in the list and avoids making Previous and Next active links when there are previous or next pages.
define pageIndex(index : Int, count : Int, perpage : Int, max: Int, end: Int) {
var pages : Int := 1 + (count - 1)/perpage
var idx := min(max(1,index), pages)
var intervals : List<List<Int>> := pageIndexIntervals(idx, count, perpage, max, end)
if(pages > 1) {
container[class="pageIndex"] {
if(idx > 1) {
container[class="indexEntryActive"]{ pageIndexLink(idx-1, "Previous") }
} else {
container[class="indexEntryInactive"]{ "Previous" }
}
for(iv : List<Int> in intervals) {
for(i : Int from iv.get(0) to iv.get(1) + 1) {
if(i == idx) {
container[class="indexEntryCurrent"]{ output(i) }
} else {
container[class="indexEntryActive"]{ pageIndexLink(i, i + "") }
}
}
} separated-by {
container[class="indexEntryGap"]{ "..." }
}
if(idx < pages) {
container[class="indexEntryActive"]{ pageIndexLink(idx+1,"Next") }
} else {
container[class="indexEntryInactive"]{ "Next" }
}
}
}
}