OK - I now have a fully working, model driven, re-usable paging component!
: First page
: All buttons enabled
: Last page
I’m happy to publish/share/contribute all of the code; in lieu of a better way to do it, I’ll embed it all here.
The only thing I can’t contribute are the icons themselves, as we’ve licensed them…
Hope this is of some use to someone - and, btw, this goes to show how quick it is to get up-and-running with Vaadin. As of Thursday last week, I had know nothing about it at all. Four working days later, I have written and used
a) A customized container seemlessly accessing a paged, hibernate-driven DAO (for table access without UI paging. Not without problems though, see other posts in Data Binding forum)
b) A standard outlook-layout-looky-likey main application (prototype)
c) This custom component for doing UI paging
No doubt there are some blunders in the code, but still : this goes to show that Vaadin is quick to get going with.
Cheers,
Charles
Usage Example
PagingModel model = new PagingModel(10);
PagingComponent pagingComponent = new PagingComponent(model);
Label lCurrentPage = new Label("Current Page: ?");
public void init() {
setTheme("gemini");
VerticalLayout layout = new VerticalLayout();
layout.setSizeFull();
Label spacer = new Label();
layout.setSpacing(true);
layout.setMargin(true);
layout.addComponent(pagingComponent);
layout.addComponent(lCurrentPage);
layout.addComponent(spacer);
layout.setExpandRatio(spacer, 1);
updatePageNumber();
setMainWindow(new Window("Paging Component", layout));
model.addPropertyChangeListener("currentPage", new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
updatePageNumber();
}
});
}
private void updatePageNumber() {
lCurrentPage.setValue("Current Page : " + model.getCurrentPage());
}
Model
public class PagingModel {
private int totalNumberOfPages;
private int numPageNumbersToDisplay;
private int currentPage = 1;
private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
public PagingModel(int totalNumberOfPages) {
this(totalNumberOfPages, 5);
}
public PagingModel(int totalNumberOfPages, int numPageNumbersToDisplay) {
this.totalNumberOfPages = totalNumberOfPages;
this.numPageNumbersToDisplay = numPageNumbersToDisplay;
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
int oldValue = this.currentPage;
this.currentPage = currentPage;
pcs.firePropertyChange("currentPage", oldValue, currentPage);
}
public int getTotalNumberOfPages() {
return totalNumberOfPages;
}
public void setTotalNumberOfPages(int totalNumberOfPages) {
int oldValue = this.totalNumberOfPages;
this.totalNumberOfPages = totalNumberOfPages;
pcs.firePropertyChange("totalNumberOfPages", oldValue, totalNumberOfPages);
}
public int getNumPageNumbersToDisplay() {
return numPageNumbersToDisplay;
}
public void setNumPageNumbersToDisplay(int numPageNumbersToDisplay) {
int oldValue = this.numPageNumbersToDisplay;
this.numPageNumbersToDisplay = numPageNumbersToDisplay;
pcs.firePropertyChange("rageSize", oldValue, numPageNumbersToDisplay);
}
public int getPreviousPage() {
return currentPage - 1 < 1 ? 1 : currentPage - 1;
}
public int getNextPage() {
return currentPage + 1 > totalNumberOfPages ? totalNumberOfPages : currentPage + 1;
}
public int getLastPageNumberToDisplay() {
int rangeEnd = currentPage <= (numPageNumbersToDisplay / 2) ? numPageNumbersToDisplay
: currentPage + (this.numPageNumbersToDisplay / 2);
return rangeEnd > totalNumberOfPages ? totalNumberOfPages : rangeEnd;
}
public int getFirstPageNumbertoDisplay() {
int rangeStart = getLastPageNumberToDisplay() - numPageNumbersToDisplay + 1;
return rangeStart < 1 ? 1 : rangeStart;
}
public boolean isFirstPage() {
return currentPage == 1;
}
public boolean hasNextPage() {
return currentPage < totalNumberOfPages;
}
public boolean hasPreviousPage() {
return currentPage > 1;
}
public boolean isLastPage() {
return currentPage == totalNumberOfPages;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
pcs.addPropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
pcs.removePropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
}
Component
public class PagingComponent extends CustomComponent implements Button.ClickListener, PropertyChangeListener {
private Button bFirst = new Button("First", this);
private Button bPrevious = new Button("Previous", this);
private Button bNext = new Button("Next", this);
private Button bLast = new Button("Last", this);
private HorizontalLayout pageNumbers = new HorizontalLayout();
private PagingModel model;
public PagingComponent(PagingModel model) {
this();
setModel(model);
}
public PagingComponent() {
setStyleName("paging");
initButton(bFirst, "first");
initButton(bPrevious, "previous");
initButton(bNext, "next");
initButton(bLast, "last");
HorizontalLayout mainLayout = new HorizontalLayout();
mainLayout.addComponent(bFirst);
mainLayout.addComponent(bPrevious);
mainLayout.addComponent(pageNumbers);
mainLayout.addComponent(bNext);
mainLayout.addComponent(bLast);
setCompositionRoot(mainLayout);
configureButtons();
}
public PagingModel getModel() {
return model;
}
public void setModel(PagingModel model) {
PagingModel oldValue = model;
this.model = model;
if (oldValue != null) {
oldValue.removePropertyChangeListener(this);
}
if (model != null) {
model.addPropertyChangeListener(this);
}
configureButtons();
}
private void configureButtons() {
if (model == null) {
bFirst.setEnabled(false);
bPrevious.setEnabled(false);
bNext.setEnabled(false);
bLast.setEnabled(false);
} else {
/* Enable/Disable the navigation buttons */
bFirst.setEnabled(!model.isFirstPage());
bPrevious.setEnabled(model.hasPreviousPage());
bNext.setEnabled(model.hasNextPage());
bLast.setEnabled(!model.isLastPage());
/* Refresh the page numbers displayed */
pageNumbers.removeAllComponents();
for (int pageNumber = model.getFirstPageNumbertoDisplay(); pageNumber <= model.getLastPageNumberToDisplay(); pageNumber++) {
Button pageButton = new Button(Integer.toString(pageNumber), new PageClickListener(pageNumber));
pageButton.setStyleName("link");
if (pageNumber == model.getCurrentPage()) {
pageButton.addStyleName("current-page");
}
pageNumbers.addComponent(pageButton);
}
}
}
private void initButton(Button button, String iconPath) {
button.setStyleName("link");
button.addStyleName("navigation-button");
button.setIcon(new ThemeResource("img/paging-" + iconPath + ".png"));
button.setEnabled(false);
}
public void propertyChange(PropertyChangeEvent evt) {
configureButtons();
}
public void buttonClick(Button.ClickEvent event) {
Button clicked = event.getButton();
if (clicked == bFirst) {
model.setCurrentPage(1);
} else if (clicked == bPrevious) {
model.setCurrentPage(model.getCurrentPage() - 1);
} else if (clicked == bNext) {
model.setCurrentPage(model.getCurrentPage() + 1);
} else if (clicked == bLast) {
model.setCurrentPage(model.getTotalNumberOfPages());
}
}
private class PageClickListener implements Button.ClickListener, Serializable {
private final int page;
public PageClickListener(int page) {this.page = page;}
public void buttonClick(Button.ClickEvent event) {
model.setCurrentPage(page);
}
}
}
CSS Styling
/* Hide all captions on navigation-buttons inside the paging compnent */
.paging div > .v-button.v-button-link.navigation-button .v-button-caption,
.paging div > .v-button.v-button-link.navigation-button:focus .v-button-caption,
.paging div > .v-button.v-button-link.navigation-button:active .v-button-caption,
.v-ie7 .paging .v-button-link.navigation-button .v-button-caption,
.v-ie7 .paging .v-button-link.navigation-button.v-pressed .v-button-caption,
.paging div > .v-button.v-button-link.navigation-button.v-disabled .v-button-caption,
.v-ie7 .paging .v-button-link.navigation-button.v-disabled .v-button-caption {
display: none;
}
/* Highlight the current page button by making it bold */
.paging div > .v-button.v-button-link.current-page .v-button-caption,
.paging div > .v-button.v-button-link.current-page:focus .v-button-caption,
.paging div > .v-button.v-button-link.current-page:active .v-button-caption,
.v-ie7 .paging .v-button-link.current-page .v-button-caption,
.v-ie7 .paging .v-button-link.current-page.v-pressed .v-button-caption,
.paging div > .v-button.v-button-link.current-page.v-disabled .v-button-caption,
.v-ie7 .paging .v-button-link.current-page.v-disabled .v-button-caption {
font-weight: bold;
}
/* Make sure we have a margin on the captions of all link-buttons inside the paging component */
.paging div > .v-button.v-button-link .v-button-caption,
.paging div > .v-button.v-button-link:focus .v-button-caption,
.paging div > .v-button.v-button-link:active .v-button-caption,
.v-ie7 .paging .v-button-link .v-button-caption,
.v-ie7 .paging .v-button-link.v-pressed .v-button-caption,
.paging div > .v-button.v-button-link.v-disabled .v-button-caption,
.v-ie7 .paging .v-button-link.v-disabled .v-button-caption {
margin-left: 2px;
margin-right: 2px;
}
/* Sort out the spacing, and stop the up-and-downing of the icons when pressed*/
.paging div > .v-button .v-icon,
.v-ie7 .paging .v-button .v-icon,
.paging div > .v-button.v-disabled .v-icon,
.v-ie7 .paging .v-button.v-disabled .v-icon {
margin: 1px;
}