Wicket 6 – Paging Navigator for Bootstrap 3

Bootstrap has many fine looking themes and web pages based on its CSS look just great – this is common knowledge. It’s quite simple to integrate Bootstrap into a Wicket application. However, there are a few exceptions like PagingNavigator which require a bit of work. The usual solution to this problem would be the adoption of CSS to match Wickets’s HTML structure. I’ve decided to modify Wicket’s PagingNavigator to work with Bootstrap CSS.

We will use Bootstrap 3 with the Cyborg Theme; the final pager will look as follows:

Before we begin, let’s point out some key differences between Wicket’s HTML markup and Bootstrap’s HTML markup for this component.
Wicket:

<html xmlns:wicket="http://wicket.apache.org">
    <body>
        <wicket:panel>
             <a wicket:id="first" class="first">&lt;&lt;</a>
             <a wicket:id="prev" class="prev">&lt;</a>
             <span wicket:id="navigation" class="goto">
                <a wicket:id="pageLink" href="#">
                <span wicket:id="pageNumber">5</span></a>
             </span>
             <a wicket:id="next" class="next">&gt;</a>
             <a wicket:id="last" class="last">&gt;&gt;</a>
        </wicket:panel>
    </body>
</html>

and corresponding Bootstrap part:

    <ul class="pagination pagination-sm">
        <li class="disabled"><a href="#">«</a></li>
        <li class="active"><a href="#">1</a></li>
        <li><a href="#">2</a></li>
        <li><a href="#">3</a></li>
        <li><a href="#">4</a></li>
        <li><a href="#">5</a></li>
        <li><a href="#">»</a></li>
    </ul>

There are a few notable differences:

  • HTML tree structure is completely different. The Wicket layout places one hyperlinks after another. Bootstrap wraps them with unordered list tags.
  • Some links in the pager are not clickable. For example, it’s not possible to jump to the last page, if we are already there. Both frameworks solve it differently: Wicket changes the rendered HTML and replaces an “a”-tag with “div”-tag. Bootstrap on the other hand does not modify the HTML tree at all but applies a different CSS class. It does not change the class attribute value directly on the “a”-tag, but on it’s parent (“li”-tag), which does not exist in Wicket markup.
  • The current page (nr-1 on picture above, blue rectangle) has also two different indicators: Wicket solves it by replacing the hyperlink with a “div”-tag, Bootstrap sets on “li”-tag.
  • Bootstrap additionally provides focus for onmouseover (gray box) for active links.

Let’s dive into implementation details – we will modify Wicket’s AjaxPagingNavigator by replacing its HTML and corresponding Java part. The new component name is BootstrapPagingNavigator.

BootstrapPagingNavigator.html

<html xmlns:wicket="http://wicket.apache.org">
<body>
<wicket:panel>
    <ul class="pagination pagination-sm">
        <li wicket:id="firstCont" class="disabled">
            <a wicket:id="first" href="#">&lt;&lt;</a></li>
        <li wicket:id="prevCont" class="disabled">
            <a wicket:id="prev" href="#">&lt;</a></li>
        <li wicket:id="navigation">
            <a wicket:id="pageLink" href="#">
            <span wicket:id="pageNumber">1</span></a>
        </li>
        <li wicket:id="nextCont" class="disabled">
            <a wicket:id="next" href="#">&gt;</a></li>
        <li wicket:id="lastCont" class="disabled">
            <a wicket:id="last" href="#">&gt;&gt;</a></li>
    </ul>
</wicket:panel>
</body>
</html>

The HTML markup has now the structure that is required by Bootstrap’s CSS: bulleted list with hyperlinks. All original Wicket tag names are there, plus we have some extra tags in blue.

Let’s move to the Java implementation; it will contain two key modifications:

  • HTML hyperlinks will not get removed for inactive links.
  • Deactivation and activation of links will be handled by dynamic CSS applied to li-tags (blue Wicket ids)

The original AjaxPagingNavigator generates three types of links, each one created by different a factory method:

  • AjaxPagingNavigator#newNavigation(…) handles: “1 | 2 | 3 | 4”
  • AjaxPagingNavigator#newPagingNavigationLink(…) handles: “first, last”
  • AjaxPagingNavigator#newPagingNavigationIncrementLink(…) handles: “prev, next”

BootstrapPagingNavigator will overwrite each of those methods in order to apply the required modifications. Here are the details:

AjaxPagingNavigator#newNavigation ( “1 | 2 | 3 | 4”)

public class BootstrapPagingNavigator extends AjaxPagingNavigator {

// .......

 @Override
 protected PagingNavigation newNavigation(String id,
      IPageable pageable, IPagingLabelProvider labelProvider) {
     return new AjaxPagingNavigation(id, pageable, labelProvider) {
         @Override
         protected LoopItem newItem(int iteration) {
            LoopItem item = super.newItem(iteration);

            // add css for enable/disable link
            long pageIndex = getStartIndex() + iteration;
            item.add(new AttributeModifier("class",
                new PageLinkCssModel(pageable,
                        pageIndex, "active")));

            return item;
        }
    };
 }

  // .......
}

The newNavigation(….) method (line 6) generates links for page numbers: “1 | 2 | 3 | 4”. The only thing that needs to be changed here is the indication of current page – it’s a disabled link on a blue background.
This can be done by applying active class to li-tag (not the hyperlink itself):

<li wicket:id="navigation" class="active">
    <a wicket:id="pageLink" href="#">
      <span wicket:id="pageNumber">1</span></a>
</li>

The HTML above is the modified version for Bootstrap. When compared to the original Wicket HTML, the li-tag was replaced by div (wicket:id=”navigation”), and class=”active” is used to indicate the current page.

The replacement of li-tag with div does not require adoption of Java code because Wicket uses WebMarkupContainer, which is compatible with both tags.

The assignment of dynamic CSS to li-tag (wicket:id=”navigation”) takes place in method newItem(…) (line 15 class above). Each link with a page number is generated as LoopItem, and applied CSS will change class-argument to active for current page – this will deactivate link, remove onmouseover highlighting, and change background to blue.
Here comes the model for our dynamic CSS:

 class PageLinkCssModel implements IModel<String>, Serializable {

       private final long pageNumber;
       protected final IPageable pageable;
       private final String css;

     public PageLinkCssModel(IPageable pageable, long pageNumber,
          String css) {
          this.pageNumber = pageNumber;
          this.pageable = pageable;
          this.css = css;
      }

      @Override
      public String getObject() {
          return isSelected() ? css : "";
      }

      @Override
      public void setObject(String object) {
      }

      @Override
      public void detach() {
      }

      public boolean isSelected() {
          return getPageNumber() == pageable.getCurrentPage();
      }

      private long getPageNumber() {
          long idx = pageNumber;
          if (idx < 0) {
              idx = pageable.getPageCount() + idx;
          }

          if (idx > (pageable.getPageCount() - 1)) {
              idx = pageable.getPageCount() - 1;
          }

          if (idx < 0) {
              idx = 0;
          }

          return idx;
      }

  }

AjaxPagingNavigator#newPagingNavigationLink (“first, last”)

public class BootstrapPagingNavigator extends AjaxPagingNavigator {

  @Override
  protected AbstractLink newPagingNavigationLink(String id,
    IPageable pageable, int pageNumber) {
      ExternalLink navCont = new ExternalLink(id + "Cont", (String) null);

      // add css for enable/disable link
      long pageIndex = pageable.getCurrentPage() + pageNumber;
      navCont.add(new AttributeModifier("class", new PageLinkCssModel(pageable,
         pageIndex, "disabled")));

     // change original wicket-link, so that it always generates href
     navCont.add(new AjaxPagingNavigationLink(id, pageable, pageNumber) {
         @Override
         protected void disableLink(ComponentTag tag) {
         }
     });
     return navCont;
 }

and the corresponding markup:

<wicket:panel>
    <ul class="pagination pagination-sm">
        <li wicket:id="firstCont" class="disabled">
           <a wicket:id="first" href="#">&lt;&lt;</a></li>
       ........
        <li wicket:id="lastCont" class="disabled">
           <a wicket:id="last" href="#">&gt;&gt;</a></li>
    </ul>
</wicket:panel>

For the first- and last-links we need to change how they are being deactivated. The HTML above is already modified for Bootstrap – the original had only hyperlinks, now they are wrapped by ul/li-tags.
From a Java code perspective, the AjaxPagingNavigator#newPagingNavigationLink(…) method would return AjaxPagingNavigationLink directly, and it would replace href elements with div for inactive links. However, we need to apply only different CSS on the li-tag (which does not exist in the original Wicket code), and not on hyperlink itself. This CSS will take care of deactivation and also disable highlighting for onmouseover.
The overridden method newPagingNavigationLink(…) in line 4 returns AbstractLink, but we rather need li-tag. The solution is to return an ExternalLink that points to nowhere – it will contain the original link as child. The names for our new tag: firstCont and lastCont are dynamically generated from name of the original link. This gives us a dynamic li-tag, which is required, because we want to change its style.
In line 10 we are registering dynamic CSS on it, which will set class=”disabled” if the pager is already on first or last page respectively.
In line 14 we create the original link, that would be returned by unmodified newPagingNavigationLink(…) method. But of course we have to add this link to #navCont in order to mach changed HTML structure.
The method disableLink(…) remains empty, to prevent Wicket from replacing an a-tag with a div-tag.

AjaxPagingNavigator#newPagingNavigationIncrementLink (“prev, next”)

public class BootstrapPagingNavigator extends AjaxPagingNavigator {

  @Override
  protected AbstractLink newPagingNavigationIncrementLink(String id,
        IPageable pageable, int increment) {
      ExternalLink navCont = new ExternalLink(id + "Cont", (String) null);

      // add css for enable/disable link
      long pageIndex = pageable.getCurrentPage() + increment;
      navCont.add(new AttributeModifier("class",
         new PageLinkIncrementCssModel(pageable, pageIndex)));

      // change original wicket-link, so that it always generates href
      navCont.add(new AjaxPagingNavigationIncrementLink(id,
            pageable, increment) {
         @Override
         protected void disableLink(ComponentTag tag) {
         }
       });
      return navCont;
   }
}

 

public class PageLinkIncrementCssModel implements IModel<String>,
     Serializable {

   protected final IPageable pageable;
   private final long pageNumber;

   public PageLinkIncrementCssModel(IPageable pageable,
      long pageNumber) {
       this.pageable = pageable;
       this.pageNumber = pageNumber;
   }

   @Override
   public String getObject() {
      return isEnabled() ? "" : "disabled";
   }

   @Override
     public void setObject(String object) {
   }

   @Override
    public void detach() {
   }

   public boolean isEnabled() {
      if (pageNumber < 0) {
          return !isFirst();
      } else {
          return !isLast();
      }
  }

  public boolean isFirst() {
      return pageable.getCurrentPage() <= 0;
  }

  public boolean isLast() {
      return pageable.getCurrentPage() >=
          (pageable.getPageCount() - 1);
  }
}

Links for next and previous page are created analogous to the first/last links described above. The only difference is the CSS model, which disables the link when the first or last page is active.

You can download the accompanying source code on Github.

Advertisements

3 thoughts on “Wicket 6 – Paging Navigator for Bootstrap 3

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s