May 07, 2011

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" }
}
}
}
}
```