Wicket PageNavigator で表示要素の Limit Offset を指定する


PageNavigator の 表示要素として、 PageableListView を使用していたのを、
DataView に書き換えて、Limit Offset を指定できるようにした話を書きます。


経緯

Stateless な ページを作っていて、後々気づいて書き直した部分になります。 実装経緯は以下の記事を参照して頂くと分かるかと思います。1
[1] 意味不明だったらごめんなさい。。


PageableListView について

PagingNavigator (StatelessPagingNavigator の含む) の表示要素として、
IPageable な インスタンスを渡す必要があります。
PageableListView は、 IPageable を実装しており、
以下のように、IPageable な インスタンスとして設定することができます。

  • 抜粋

        // Add FestivalListView
        PageableListView<Festival> festivals = newFestivalListView(parameters.get(PAGING_PAGE_PARAMETER).toLong(0));
        add(new StatelessPagingNavigator("navigator", parameters, festivals));
        add(festivals);
    

  • newFestivalListView(long l) ページング表示件数に関わらず、PageableListView は要素全件を取得する実装になります。
    1万件あって、1万件取得しても、20件しかなければ、20件だけ描画となります。

        private PageableListView<Festival> newFestivalListView(long l) {
    
            LoadableDetachableModel<List<Festival>> festivals = new LoadableDetachableModel<List<Festival>>() {
    
                private static final long serialVersionUID = 7474274077691068779L;
    
                @Override
                protected List<Festival> load() {
                    // Get ListView Elements
                    FestivalRepository repository = _new(FestivalRepository.class);
                    return repository.findAll();
                }
            };
    
            int itemsPerPage = 20;
    
            PageableListView<Festival> listView = new PageableListView<Festival>("festivals", festivals, itemsPerPage) {
                private static final long serialVersionUID = -6536722749788422260L;
    
                @Override
                protected void populateItem(ListItem<Festival> item) {
                    item.add(new Label("siteUrl"));
    
                    PageParameters param = new PageParameters();
                    Festival fes = item.getModelObject();
                    param.add("id", fes.getId());
                    Link link = new BookmarkablePageLink<>("festivalLink", FestivalDetailPage.class, param);
                    link.add(new Label("festivalName", fes.getName()));
                    item.add(link);
    
                    byte[] imageData = fes.getThumbalizr();
                    Component component = null;
                    if (imageData != null) {
                        component = new InlineImage("festvalImage", imageData);
                    } else {
                        component = new ExternalImage("festvalImage", "/static/images/no_image.png");
                    }
                    item.add(component);
                    // item の css に category を追加
                    String css = "category-" + String.valueOf(fes.getHeldYearId().getHeldYear());
                    item.add(AttributeModifier.append("class", css));
                }
            };
            // ListView を 表示する
            listView.setVisible(true);
            // MarkupI を 表示する
            listView.setOutputMarkupId(true);
            return listView;
        }
    


DataView について

PageableListView の実装を、DataView と、 IDataProvider の実装クラスを使用する形式に書き換えると、
1万件ある際、表示件数のみ取得できるようになります。2
[2] 件数次第ですが、システム的な負荷が削減できるかと。

  • newFestivalDataView(long l)

        private DataView<Festival> newFestivalDataView(long l) {
    
            final FestivalRepository repository = _new(FestivalRepository.class);
    
            IDataProvider<Festival> dataProvider = new IDataProvider<Festival>() {
    
                @Override
                public void detach() {
                    // Do Nothing...
                }
    
                @Override
                public Iterator iterator(long offset, long limit) {
                    return repository.findWithHeldYearAndThumbalizrByLimitAndOffset((int) limit, (int) offset).iterator();
                }
    
                @Override
                public long size() {
                    return repository.getRecordCount();
                }
    
                @Override
                public IModel model(Festival o) {
                    return new LoadableDetachableModel() {
                        @Override
                        protected Festival load() {
                            return o;
                        }
                    };
                }
            };
            int itemsPerPage = 20;
            DataView<Festival> dataView = new DataView<Festival>("festivals", dataProvider, itemsPerPage) {
    
                private static final long serialVersionUID = -9200004825371647245L;
    
                @Override
                protected void populateItem(Item<Festival> item) {
                    item.add(new Label("siteUrl"));
    
                    PageParameters param = new PageParameters();
                    Festival fes = item.getModelObject();
                    param.add("id", fes.getId());
                    Link link = new BookmarkablePageLink<>("festivalLink", FestivalDetailPage.class, param);
                    link.add(new Label("festivalName", fes.getName()));
                    item.add(link);
    
                    byte[] imageData = fes.getThumbalizr();
                    Component component = null;
                    if (imageData != null) {
                        component = new InlineImage("festvalImage", imageData);
                    } else {
                        component = new ExternalImage("festvalImage", "/static/images/no_image.png");
                    }
                    item.add(component);
                    // item の css に category を追加
                    String css = "category-" + String.valueOf(fes.getHeldYearId().getHeldYear());
                    item.add(AttributeModifier.append("class", css));
                }
            };
            // ListView を 表示する
            dataView.setVisible(true);
            // Markup を 表示する
            dataView.setOutputMarkupId(true);
            return dataView;
        }
    

  • 説明1
    IDataProvider をインナークラス定義して、必要なメソッドを実装しています。

        @Override
        public Iterator iterator(long offset, long limit) {
            return repository.findWithHeldYearAndThumbalizrByLimitAndOffset((int) limit, (int) offset).iterator();
        }
    
    で必要な範囲のデータを取得する。
        @Override
        public long size() {
            return repository.getRecordCount();
        }
    
    でデータ件数を返すように実装しています。
        @Override
        public IModel model(Festival o) {
            return new LoadableDetachableModel() {
                @Override
                protected Festival load() {
                    return o;
                }
            };
        }
    
    のロジックの妥当性はちょっとわからないですが、 Users forum - Loadable-detachable model for ListView
    等で、似た様な実装を見かけたので同じように実装しています。3
    [3] おそらく、セッションを確立して30分以内とかそういうレベルであれば、問題なく動作すると思います。

  • 説明2
    DataView 自体の実装は、PageableListView を使う実装とほぼ変わりないです。
    肝は、IDataProvider なのかと思いました。

  • 説明3
    StatelessPagingNavigator に対して DataView を設定していますが、
    DataView 自体が Stateless なのか問題なく動作しました。
    Google で StatelessDataView で検索すると、ヒットしますが、DataView を直接使用しても問題なく Stateless になりました。

以上です。 Stateless を意識しはじめると途端に実装が面倒になりますが、
面倒くさいですが。パターンとしてはそれなりに、網羅し始めているように感じていて、
大きなハマりポイントはそれなりになくなってる気がしてきました。
面倒であることはかわりないですが、
Stateful にしないようにするメリットも大きいと思うので、
もうしばらく、調べたりしようかと思います。

コメント