0

Reload Listbox After Filter In A LoadOnDemand Model

asked 2020-10-01 17:31:19 +0800

suzuha gravatar image suzuha
15 4

updated 2020-10-08 02:17:30 +0800

Hello,

The szenario:

Currently I'm trying to implement a Listbox/Model to load hughe Data with paging in MVVM (this works reather well after looking at some tech talks).

The code thus far is working fine! The Listmodel works and loads Items on each page switch and puts them in a cache. The items are also in the Listbox.

The problem:

What I don't get to work is the reset of the model + Listbox. When I change the value in one of the search Textboxes the search method for the specific box is called and the correct db query is used. But the Listbox won't update the entries to the new model.

I tried two approaches in the methods searchForCode() and searchForDisplayText() in ViewModel class (see below). With both approaches the displayed elements won't change. If I also reload the totalSize of the ListModel sometimes when I write in a textbox, delete it and rewrite it the Listbox content changes.

Any ideas how I can reload the listbox content?

Here is the ZUL Code of the Listbox:

<groupbox hflex="1" vflex="1" closable="false" mold="3d">
                    <caption label="Test"/>
                    <vlayout hflex="1" vflex="1">
                        <hlayout hflex="1" height="60px">
                            <label value="Code: " />
                            <textbox onChanging="@command('searchForCode')" width="200px" />
                            <label value="Display text: " />
                            <textbox onChanging="@command('searchForDisplayText')" width="200px" />
                        </hlayout>
                        <listbox id="lbPaging" mold="paging" model="@bind(vm.model)" emptyMessage="no entries found"
                                 pageSize="10">
                            <listhead>
                                <listheader label="Code"/>
                                <listheader label="Wert"/>
                                <listheader label="Orig."/>
                            </listhead>
                            <template name="model">
                                <listitem>
                                    <listcell>
                                        <label value="@load(each.code)"/>
                                    </listcell>
                                    <listcell>
                                        <label value="@load(each.displayText)"/>
                                    </listcell>
                                </listitem>
                            </template>
                        </listbox>
                    </vlayout>
                </groupbox>

And this is my ListModel:

public abstract class PagingListModelList<T> extends AbstractListModel {
    private final int pageSize;

    private Integer listTotalSize = null;
    private final Map<Integer, List<T>> pageCache = new HashMap<>();

    public PagingListModelList(int pageSize) {
        super();
        this.pageSize = pageSize;
    }

    @Override
    public T getElementAt(int index) {
        int pageIndex = index / pageSize;
        List<T> pageElements = pageCache.get(pageIndex);
        if (pageElements == null) {
            pageElements = loadPageFromDB(pageIndex * pageSize, pageSize);
            pageCache.put(pageIndex, pageElements);
        }

        return pageElements != null ? pageElements.get(index % pageSize) : null;
    }

    @Override
    public int getSize() {
        if (listTotalSize == null) {
            listTotalSize = loadListTotalSize();
        }
        return listTotalSize;
    }

    public void clearCaches() {
        listTotalSize = null;
        pageCache.clear();
        loadListTotalSize();
        pageCache.put(0, loadPageFromDB(0, pageSize));
        fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1);
    }

    protected abstract Integer loadListTotalSize();

    protected abstract List<T> loadPageFromDB(int startIndex, int size);
}

And finally here the ViewModel:

public class MyViewModel {
    private int pageSize = 10;

    private String searchCode;
    private String searchDisplayText;

    private PagingListModelList<CodeDisplay> model;
    @Wire("#lbPaging")
    private Listbox lbPaging;


    private boolean isSearchCode = false;
    private boolean isSearchDisplayText = false;

    @Init
    public void init() {
        model = new PagingListModelList<CodeDisplay>(pageSize) {
            @Override
            protected Integer loadListTotalSize() {
                int count = 0;
                try (Session hb_session = HibernateUtil.getSessionFactory().openSession()) {
                    Long counter = 0L;
                    if (isSearchCode) {
                        counter = hb_session.createQuery("Select count(cv) From CodeValue cv where cv.code = 'codesystem' and cv.shortCode like :code ", Long.class).setParameter("code", "%"+searchCode+"%").uniqueResult();
                    } else if (isSearchDisplayText) {
                        counter = hb_session.createQuery("Select count(cv) From CodeValue cv where cv.code = 'codesystem' and cv.displayText like :display ", Long.class).setParameter("display", "%"+searchDisplayText+"%").uniqueResult();
                    } else {
                        counter = hb_session.createQuery("Select count(cv) From CodeValue cv where cv.code = 'codesystem' ", Long.class).uniqueResult();
                    }
                    count = counter.intValue();
                }
                return count;
            }

            @Override
            protected List<CodeDisplay> loadPageFromDB(int startIndex, int size) {
                List<CodeDisplay> currentPageData = new LinkedList<>();
                try (Session hb_session = HibernateUtil.getSessionFactory().openSession()) {
                    if (isSearchCode) {
                        currentPageData = hb_session.createQuery("Select new org.test.model.CodeDisplay(cv.shortCode, cv.displayText)  From CodeValue cv where cv.code = 'codesystem' and cv.shortCode like :code order by cv.shortCode asc", CodeDisplay.class).setParameter("code", "%"+searchCode+"%").setFirstResult(startIndex).setMaxResults(size).list();
                    } else if (isSearchDisplayText) {
                        currentPageData = hb_session.createQuery("Select new org.test.model.CodeDisplay(cv.shortCode, cv.displayText)  From CodeValue cv where cv.code = 'codesystem' and cv.displayText like :display order by cv.shortCode asc", CodeDisplay.class).setParameter("display", "%"+searchDisplayText+"%").setFirstResult(startIndex).setMaxResults(size).list();
                    } else {
                        currentPageData = hb_session.createQuery("Select new org.test.model.CodeDisplay(cv.shortCode, cv.displayText)  From CodeValue cv where cv.code = 'codesystem' order by cv.shortCode asc", CodeDisplay.class).setFirstResult(startIndex).setMaxResults(size).list();
                    }
                }
                return currentPageData;
            }
        };
    }

    @AfterCompose
    public void afterCompose(@ContextParam(ContextType.VIEW) Component view) {
        Selectors.wireComponents(view, this, false);
    }

    @Command
    @NotifyChange("searchForCode")
    public void searchForCode(@ContextParam(ContextType.TRIGGER_EVENT) InputEvent event) {
        searchDisplayText = null;
        isSearchDisplayText = false;
        isSearchCode = true;
        searchCode = event.getValue();
        model.clearCaches();

        //using this instead of fireing the event in model.clearCaches() works:
        //lbPaging.setModel(new ListModelList<CodeValue>());
        //lbPaging.setActivePage(0);
        //lbPaging.setModel(model);    
    }

    @Command
    @NotifyChange("searchForDisplayText")
    public void searchForDisplayText(@ContextParam(ContextType.TRIGGER_EVENT) InputEvent event) {
        searchCode = null;
        isSearchCode = false;
        isSearchDisplayText = true;
        searchDisplayText = event.getValue();
        model.clearCaches();
    }

    public PagingListModelList<CodeDisplay> getModel() {
        return model;
    }

    public String getSearchCode() {
        return searchCode;
    }

    public void setSearchCode(String searchCode) {
        this.searchCode = searchCode;
    }

    public String getSearchDisplayText() {
        return searchDisplayText;
    }

    public void setSearchDisplayText(String searchDisplayText) {
        this.searchDisplayText = searchDisplayText;
    }
}
delete flag offensive retag edit

1 Answer

Sort by ยป oldest newest most voted
0

answered 2020-10-05 17:28:47 +0800

cor3000 gravatar image cor3000
5399 2 7
ZK Team

updated 2020-10-05 17:32:39 +0800

Your custom ListModel implementation has to fire a data Event to informing the ListBox to rerender the items at the affected indices.

Since filtering your data is similar to sorting where all the contents change. You can do the same as in the sort() method of ListModelList https://github.com/zkoss/zk/blob/v9.5.0/zul/src/org/zkoss/zul/ListModelList.java#L452

e.g. in your clearCachesMethod you can call AbstractListModel.fireEvent(...)

public void clearCaches() {
    listTotalSize = null;
    pageCache.clear();
    fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1); 
    //-1, -1 means everything
}

I haven't tested this with your code since it's not runnable in isolation.

Once you fire the ListDataEvent setting the model on the listbox directly should not be necessary any more.

Please also read the related section in our developer reference: https://www.zkoss.org/wiki/ZKDeveloper'sReference/MVC/Model/ListModel#NotifyforDataUpdates

link publish delete flag offensive edit

Comments

Thank you! I didn't even see the last page you posted (seems like I was at the wrong place in the docs). I tried using the fireEvent(...) Method but when I use it the webapp seems to freeze. The debugger never continuous after the method call. I updated the code above.

suzuha ( 2020-10-06 18:37:19 +0800 )edit

I see your updated code, it's still not runnable at my side due to your application specific DB queries and domain objects. Can you somehow provide a runnable example on https://zkfiddle.org/ demonstrating the problem directly?

cor3000 ( 2020-10-07 11:53:18 +0800 )edit

You said "the webapp seems to freeze": this is not very specifc. When you are already in the debugger, did you try to identify where the code is "freezing"? Often there is an infinite loop, or the code is waiting for e.g. your sql queries. That's where I'd look.

cor3000 ( 2020-10-07 11:54:54 +0800 )edit

The debugger also stops working when the event is fired. I can debug to the usage of syncModel0() in https://github.com/zkoss/zk/blob/9.0-Stable/zul/src/org/zkoss/zul/impl/ListboxDataLoader.java but it seems the debuger won't leave the while loop at line 411.

suzuha ( 2020-10-08 02:11:37 +0800 )edit

If I reset the ListModel in the Listbox with setModel() and don't use the fireEvent its working now (thou loading the new model takes about a second). So I think that using the fireEvent approach something happens to the number of items (reset/change?) inside the loop mentioned above.

suzuha ( 2020-10-08 02:13:30 +0800 )edit
Your answer
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!

[hide preview]

Question tools

Follow
1 follower

RSS

Stats

Asked: 2020-10-01 17:31:19 +0800

Seen: 8 times

Last updated: Oct 08

Support Options
  • Email Support
  • Training
  • Consulting
  • Outsourcing
Learn More