Wicket Stateless な PageNavigator を作る


StatelessPage 上で、PageNavigator が必要になり、
Stateless な PageNavigator が必要になって、作ってみたので、結果を記載します。


参考サイト


StatelessPagingNavigator.html

Navigator の HTML は以下の通り実装しました。

<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<body>
<wicket:panel>
    <nav aria-label="Page navigation" class="text-center">
        <ul class="pagination">
            <li class="page-item" wicket:id="firstWrap"><a wicket:id="first" class="page-link first">&lt;&lt;</a></li>
            <li class="page-item" wicket:id="prevWrap"><a wicket:id="prev" rel="prev" class="page-link prev">&lt;</a>
            </li>
            <li class="page-item" wicket:id="navigation">
                <a class="page-link" wicket:id="pageLink" href="#">
                    <span wicket:id="pageNumber">5</span>
                </a>
            </li>
            <li class="page-item" wicket:id="nextWrap"><a wicket:id="next" rel="next" class="page-link next">&gt;</a>
            </li>
            <li class="page-item" wicket:id="lastWrap"><a wicket:id="last" class="page-link last">&gt;&gt;</a></li>
        </ul>
    </nav>
</wicket:panel>
</body>
</html>

StatelessPagingNavigator.java

以下の通り、StatelessPagingNavigator.java を作成しました。
PagingNavigator で保持している markup 部を変更する必要があったため、
Panel を直接継承するようにして、実装しました。
PagingNavigator と元々の StatelessPagingNavigator.java
マージしたような状態になっています。

import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.AbstractLink;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.list.LoopItem;
import org.apache.wicket.markup.html.navigation.paging.IPageable;
import org.apache.wicket.markup.html.navigation.paging.IPagingLabelProvider;
import org.apache.wicket.markup.html.navigation.paging.PagingNavigation;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.util.collections.MicroMap;

/**
 * StatelessPagingNavigator
 */
public class StatelessPagingNavigator extends Panel {

    private static final long serialVersionUID = 8219151328688000388L;
    public static final String PAGING_PAGE_PARAMETER = "page";
    private final IPageable pageable;
    private final IPagingLabelProvider labelProvider;
    private PageParameters parameters;
    private PagingNavigation pagingNavigation;

    /**
     * Construct.
     * @param id
     * @param parameters
     * @param pageable
     */
    public StatelessPagingNavigator(String id, PageParameters parameters, IPageable pageable) {
        this(id, pageable);
        this.parameters = new PageParameters(parameters);
        pageable.setCurrentPage(this.parameters.get(PAGING_PAGE_PARAMETER).toLong(0));
    }

    /**
     * Construct.
     * @param id
     * @param pageable
     */
    public StatelessPagingNavigator(String id, IPageable pageable) {
        this(id, pageable, (IPagingLabelProvider) null);
    }

    /**
     * Construct.
     *
     * @param id
     * @param pageable
     * @param labelProvider
     */
    public StatelessPagingNavigator(String id, IPageable pageable, IPagingLabelProvider labelProvider) {
        super(id);
        this.pageable = pageable;
        this.labelProvider = labelProvider;
        setRenderBodyOnly(true);
    }

    protected AbstractLink newPagingNavigationIncrementLink(String id, IPageable pageable, int increment) {
        long pageNumber = pageable.getCurrentPage() + increment;
        return newPageLink(id, pageNumber);
    }

    protected AbstractLink newPagingNavigationLink(String id, IPageable pageable, int pageNumber) {
        // The base PagingNavigator uses -1 to refer to the last page
        if (pageNumber < 0) {
            pageNumber += pageable.getPageCount();
        }
        return newPageLink(id, pageNumber);
    }

    protected PagingNavigation newNavigation(final String id, final IPageable pageable, final IPagingLabelProvider labelProvider) {

        return new PagingNavigation(id, pageable, labelProvider) {
            @Override
            protected AbstractLink newPagingNavigationLink(String id, IPageable pageable, long pageNumber) {
                return newPageLink(id, pageNumber);
            }

            protected void populateItem(LoopItem loopItem) {
                long pageIndex = this.getStartIndex() + (long) loopItem.getIndex();
                AbstractLink link = this.newPagingNavigationLink("pageLink", this.pageable, pageIndex);
                link.add(new Behavior[]{new PagingNavigationTitleAppender(pageIndex, this)});
                loopItem.add(new Component[]{link});
                String label = "";
                if (this.labelProvider != null) {
                    label = this.labelProvider.getPageLabel(pageIndex);
                } else {
                    label = String.valueOf(pageIndex + 1L).intern();
                }
                Component components = new Label("pageNumber", label);
                link.add(components);
                // ---------------------------------------
                // 現在表示しているページには、class activeを付与する
                // --------------
                if (pageable.getCurrentPage() == pageIndex) {
                    loopItem.add(AttributeModifier.append("class", "active"));
                }
            }
        };
    }

    @Override
    protected boolean getStatelessHint() {
        return true;
    }

    private AbstractLink newPageLink(String id, long pageNumber) {
        PageParameters withPaging = new PageParameters(parameters);
        withPaging.set(PAGING_PAGE_PARAMETER, pageNumber);
        return new BookmarkablePageLink<>(id, getPage().getPageClass(), withPaging);
    }

    public final IPageable getPageable() {
        return this.pageable;
    }

    protected void onInitialize() {
        super.onInitialize();
        this.pagingNavigation = this.newNavigation("navigation", this.pageable, this.labelProvider);
        this.add(new Component[]{this.pagingNavigation});

        MarkupContainer firstWrap = new WebMarkupContainer("firstWrap");
        firstWrap.add(new Component[]{this.newPagingNavigationLink("first", this.pageable, 0).add(new Behavior[]{new PagingNavigatorTitleAppender("PagingNavigator.first")})});
        // 1ページ目を選択時は、非活性化する
        if (getPageable().getCurrentPage() == 0) {
            firstWrap.add(AttributeModifier.append("class", "disabled"));
            firstWrap.setEnabled(false);
        }
        this.add(firstWrap);

        MarkupContainer prevWrap = new WebMarkupContainer("prevWrap");
        prevWrap.add(new Component[]{this.newPagingNavigationIncrementLink("prev", this.pageable, -1).add(new Behavior[]{new PagingNavigatorTitleAppender("PagingNavigator.previous")})});
        // 1ページ目を選択時は、非活性化する
        if (getPageable().getCurrentPage() == 0) {
            prevWrap.add(AttributeModifier.append("class", "disabled"));
            prevWrap.setEnabled(false);
        }
        this.add(prevWrap);

        MarkupContainer nextWrap = new WebMarkupContainer("nextWrap");
        nextWrap.add(new Component[]{this.newPagingNavigationIncrementLink("next", this.pageable, 1).add(new Behavior[]{new PagingNavigatorTitleAppender("PagingNavigator.next")})});
        // 最終ページを選択時は、非活性化する
        if (getPageable().getPageCount() - 1 == getPageable().getCurrentPage()) {
            nextWrap.add(AttributeModifier.append("class", "disabled"));
            nextWrap.setEnabled(false);
        }
        this.add(nextWrap);
        MarkupContainer lastWrap = new WebMarkupContainer("lastWrap");
        lastWrap.add(new Component[]{this.newPagingNavigationLink("last", this.pageable, -1).add(new Behavior[]{new PagingNavigatorTitleAppender("PagingNavigator.last")})});
        // 最終ページを選択時は、非活性化する
        if (getPageable().getPageCount() - 1 == getPageable().getCurrentPage()) {
            lastWrap.add(AttributeModifier.append("class", "disabled"));
            lastWrap.setEnabled(false);
        }
        this.add(lastWrap);
    }

    public final PagingNavigation getPagingNavigation() {
        return this.pagingNavigation;
    }

    // PagingNavigatorTitleAppender
    private final class PagingNavigatorTitleAppender extends Behavior {
        private static final long serialVersionUID = 1L;
        private final String resourceKey;

        public PagingNavigatorTitleAppender(String resourceKey) {
            this.resourceKey = resourceKey;
        }

        public void onComponentTag(Component component, ComponentTag tag) {
            tag.put("title", StatelessPagingNavigator.this.getString(this.resourceKey));
        }
    }

    // PagingNavigationTitleAppender
    private final class PagingNavigationTitleAppender extends Behavior {
        private static final long serialVersionUID = 1L;
        private static final String RES = "PagingNavigation.page";
        private PagingNavigation pagingNavigation;
        private final long page;

        public PagingNavigationTitleAppender(long page, PagingNavigation pagingNavigation) {
            this.page = page;
            this.pagingNavigation = pagingNavigation;
        }

        public void onComponentTag(Component component, ComponentTag tag) {
            String pageIndex = String.valueOf(this.page + 1L).intern();
            MicroMap vars = new MicroMap("page", pageIndex);
            tag.put("title", this.pagingNavigation.getString(RES, Model.ofMap(vars)));
        }
    }
}
  • 説明1
    if (getPageable().getCurrentPage() == 0) で 1ページ目かどうか判定できます。

  • 説明2
    if (getPageable().getPageCount() - 1 == getPageable().getCurrentPage()) で 最終ページかどうか判定できます。

  • 説明3
    StatelessPagingNavigator#newNavigation() で生成する PagingNavigationpopulateItem メソッドで、
    if (pageable.getCurrentPage() == pageIndex) で 現在表示ページがどれかを判断できます。
    HTML 上以下の部分に該当する処理になります。

<li class="page-item" wicket:id="navigation">
    <a class="page-link" wicket:id="pageLink" href="#">
        <span wicket:id="pageNumber">5</span>
    </a>
</li>

StatelessPagingNavigator.java の使用箇所

使用箇所は、以下のような実装になります。

  • Java
    // Add FestivalListView
    PageableListView<Festival> festivals = newFestivalListView(parameters.get(PAGING_PAGE_PARAMETER).toLong(0));
    add(new StatelessPagingNavigator("navigator", parameters, festivals));
    add(festivals);
  • HTML
    <!-- StatelessPagingNavigator の Markup -->
    <div class="col-md-12">
        <div wicket:id="navigator"></div>
    </div>
    <!-- ListView の Markup -->
    <div class="col-md-12">
        <div class="isotope-container row">
            <div class="col-sm-6 col-md-3 isotope-item" wicket:id="festivals">
                <div class="image-box">
                    <div class="overlay-container">
                        <img class="fit" wicket:id="festvalImage" alt="Festibal image"
                             style="width:100%; height:auto;">
                        <a class="overlay">
                            <i class="fa fa-search-plus"></i>
                            <span wicket:id="siteUrl">Festival URL</span>
                        </a>
                    </div>
                    <a class="btn btn-default btn-block" wicket:id="festivalLink">
                        <span wicket:id="festivalName">Festival Name</span>
                    </a>
                </div>
            </div>
        </div>
    </div>
  • 画面表示

使用箇所は以下のように表示されます。
page navigator

以上です。

コメント