• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

knowledgepixels / nanodash / 26622127697

29 May 2026 06:33AM UTC coverage: 20.725% (-0.007%) from 20.732%
26622127697

push

github

tkuhn
feat: apply view filters live as the user types

Filter text fields used AjaxFormComponentUpdatingBehavior("change"),
which only fires on blur/Enter, so the filter required pressing Enter.

Add a reusable FilterUpdatingBehavior that listens on the "input" event
(fires per keystroke) with a 300ms debounce so rapid typing coalesces
into a single request. Swap it into all filter fields.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

1003 of 6148 branches covered (16.31%)

Branch coverage included in aggregate %.

2584 of 11160 relevant lines covered (23.15%)

3.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
src/main/java/com/knowledgepixels/nanodash/component/QueryResultItemList.java
1
package com.knowledgepixels.nanodash.component;
2

3
import com.knowledgepixels.nanodash.*;
4
import com.knowledgepixels.nanodash.domain.IndividualAgent;
5
import com.knowledgepixels.nanodash.domain.User;
6
import com.knowledgepixels.nanodash.page.PublishPage;
7
import com.knowledgepixels.nanodash.page.UserPage;
8
import org.apache.wicket.ajax.AjaxRequestTarget;
9
import org.apache.wicket.ajax.markup.html.navigation.paging.AjaxPagingNavigator;
10
import org.apache.wicket.extensions.markup.html.repeater.data.table.NavigatorLabel;
11
import org.apache.wicket.markup.html.WebMarkupContainer;
12
import org.apache.wicket.markup.html.basic.Label;
13
import org.apache.wicket.markup.html.form.TextField;
14
import org.apache.wicket.markup.repeater.Item;
15
import org.apache.wicket.markup.repeater.data.DataView;
16
import org.apache.wicket.model.Model;
17
import org.apache.wicket.request.cycle.RequestCycle;
18
import org.apache.wicket.request.resource.ContextRelativeResourceReference;
19
import org.apache.wicket.util.string.Strings;
20
import org.eclipse.rdf4j.model.IRI;
21
import org.nanopub.extra.services.ApiResponse;
22
import org.nanopub.extra.services.ApiResponseEntry;
23
import org.nanopub.extra.services.QueryRef;
24

25
/**
26
 * Component for displaying query results as a vertical item list.
27
 * Each result row is rendered as a single linked item, with icon handling
28
 * for user_iri and template_iri columns.
29
 */
30
public class QueryResultItemList extends QueryResult {
31

32
    private FilteredQueryResultDataProvider filteredDataProvider;
33
    private final Model<String> filterModel = Model.of("");
×
34
    private WebMarkupContainer itemsContainer;
35

36
    QueryResultItemList(String markupId, QueryRef queryRef, ApiResponse response, ViewDisplay viewDisplay) {
37
        super(markupId, queryRef, response, viewDisplay);
×
38

39
        String label = grlcQuery.getLabel();
×
40
        if (viewDisplay.getTitle() != null) {
×
41
            label = viewDisplay.getTitle();
×
42
        }
43
        add(new Label("label", label));
×
44
        setOutputMarkupId(true);
×
45

46
        TextField<String> filterField = new TextField<>("filter", filterModel);
×
47
        filterField.setOutputMarkupId(true);
×
48
        filterField.add(new FilterUpdatingBehavior() {
×
49
            @Override
50
            protected void onUpdate(AjaxRequestTarget target) {
51
                if (filteredDataProvider != null && itemsContainer != null) {
×
52
                    filteredDataProvider.setFilterText(filterModel.getObject());
×
53
                    target.add(itemsContainer);
×
54
                }
55
            }
×
56
        });
57
        add(filterField);
×
58

59
        populateComponent();
×
60
    }
×
61

62
    @Override
63
    protected void populateComponent() {
64
        QueryResultDataProvider dataProvider = new QueryResultDataProvider(response.getData());
×
65
        filteredDataProvider = new FilteredQueryResultDataProvider(dataProvider, response);
×
66

67
        DataView<ApiResponseEntry> dataView = new DataView<>("items", filteredDataProvider) {
×
68
            @Override
69
            protected void populateItem(Item<ApiResponseEntry> item) {
70
                ApiResponseEntry entry = item.getModelObject();
×
71
                for (String key : response.getHeader()) {
×
72
                    if (key.endsWith("_label") || key.endsWith("_label_multi")) continue;
×
73
                    String value = entry.get(key);
×
74
                    if (value == null || value.isBlank()) continue;
×
75
                    String entryLabel = entry.get(key + "_label");
×
76

77
                    if (key.endsWith("user_iri")) {
×
78
                        IRI userIri = Utils.vf.createIRI(value);
×
79
                        IRI profilePicIri = User.getProfilePicture(userIri);
×
80
                        String imgSrc;
81
                        String iconClass;
82
                        if (profilePicIri != null) {
×
83
                            imgSrc = Strings.escapeMarkup(profilePicIri.stringValue()).toString();
×
84
                            iconClass = "user-icon";
×
85
                        } else if (IndividualAgent.isSoftware(userIri)) {
×
86
                            imgSrc = RequestCycle.get().urlFor(new ContextRelativeResourceReference("images/bot-icon.svg", false), null).toString();
×
87
                            iconClass = "bot-icon";
×
88
                        } else {
89
                            imgSrc = RequestCycle.get().urlFor(new ContextRelativeResourceReference("images/user-icon.svg", false), null).toString();
×
90
                            iconClass = "user-icon";
×
91
                        }
92
                        String displayLabel = (entryLabel != null && !entryLabel.isBlank()) ? entryLabel : User.getShortDisplayName(userIri);
×
93
                        String userUrl = UserPage.MOUNT_PATH + "?id=" + Utils.urlEncode(value);
×
94
                        String html = "<img class=\"" + iconClass + "\" src=\"" + imgSrc + "\" /> <a href=\"" + Strings.escapeMarkup(userUrl) + "\">" + Strings.escapeMarkup(displayLabel) + "</a>";
×
95
                        item.add(new Label("listItem", html).setEscapeModelStrings(false));
×
96
                        return;
×
97
                    } else if (key.endsWith("template_iri")) {
×
98
                        String displayLabel = (entryLabel != null && !entryLabel.isBlank()) ? entryLabel : value;
×
99
                        String templateUrl = PublishPage.MOUNT_PATH + "?template=" + Utils.urlEncode(value) + "&template-version=latest";
×
100
                        String html = "<span class=\"form-icon\"></span> <a href=\"" + Strings.escapeMarkup(templateUrl) + "\">" + Strings.escapeMarkup(displayLabel) + "</a>";
×
101
                        item.add(new Label("listItem", html).setEscapeModelStrings(false));
×
102
                        return;
×
103
                    } else if (value.matches("https?://.*")) {
×
104
                        item.add(new NanodashLink("listItem", value, null, null, entryLabel, contextId));
×
105
                        return;
×
106
                    } else {
107
                        item.add(new Label("listItem", value));
×
108
                        return;
×
109
                    }
110
                }
111
                item.add(new Label("listItem", ""));
×
112
            }
×
113
        };
114
        dataView.setItemsPerPage(viewDisplay.getPageSize());
×
115

116
        WebMarkupContainer navigation = new WebMarkupContainer("navigation");
×
117
        navigation.add(new NavigatorLabel("navigatorLabel", dataView));
×
118
        AjaxPagingNavigator pagingNavigator = new AjaxPagingNavigator("navigator", dataView);
×
119
        navigation.setVisible(dataView.getPageCount() > 1);
×
120
        navigation.add(pagingNavigator);
×
121

122
        itemsContainer = new WebMarkupContainer("items-container");
×
123
        itemsContainer.setOutputMarkupId(true);
×
124
        itemsContainer.add(dataView);
×
125
        itemsContainer.add(navigation);
×
126
        add(itemsContainer);
×
127
    }
×
128
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc