source: vlo/trunk/vlo-web-app/src/main/java/eu/clarin/cmdi/vlo/wicket/panels/search/AllFacetValuesPanel.java @ 6437

Last change on this file since 6437 was 6437, checked in by Twan Goosen, 9 years ago

Major dependency upgrades: bumped Wicket to 7.x and Spring to 4.x and made required migration changes in code

File size: 10.7 KB
Line 
1/*
2 * Copyright (C) 2014 CLARIN
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17package eu.clarin.cmdi.vlo.wicket.panels.search;
18
19import com.google.common.collect.ImmutableList;
20import eu.clarin.cmdi.vlo.pojo.FacetSelection;
21import eu.clarin.cmdi.vlo.pojo.FieldValuesFilter;
22import eu.clarin.cmdi.vlo.pojo.FieldValuesOrder;
23import eu.clarin.cmdi.vlo.wicket.components.AjaxIndicatingForm;
24import eu.clarin.cmdi.vlo.wicket.components.FieldValueLabel;
25import eu.clarin.cmdi.vlo.wicket.components.FieldValueOrderSelector;
26import eu.clarin.cmdi.vlo.wicket.model.BridgeModel;
27import eu.clarin.cmdi.vlo.wicket.model.BridgeOuterModel;
28import eu.clarin.cmdi.vlo.wicket.provider.FacetFieldValuesProvider;
29import eu.clarin.cmdi.vlo.wicket.provider.FieldValueConverterProvider;
30import java.util.Collections;
31import org.apache.solr.client.solrj.response.FacetField;
32import org.apache.wicket.ajax.AjaxRequestTarget;
33import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
34import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
35import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
36import org.apache.wicket.ajax.markup.html.navigation.paging.AjaxPagingNavigator;
37import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
38import org.apache.wicket.markup.html.WebMarkupContainer;
39import org.apache.wicket.markup.html.basic.Label;
40import org.apache.wicket.markup.html.form.CheckBox;
41import org.apache.wicket.markup.html.form.DropDownChoice;
42import org.apache.wicket.markup.html.form.Form;
43import org.apache.wicket.markup.html.form.TextField;
44import org.apache.wicket.markup.html.link.Link;
45import org.apache.wicket.markup.html.panel.GenericPanel;
46import org.apache.wicket.markup.repeater.Item;
47import org.apache.wicket.markup.repeater.data.DataView;
48import org.apache.wicket.model.CompoundPropertyModel;
49import org.apache.wicket.model.IModel;
50import org.apache.wicket.model.Model;
51import org.apache.wicket.model.PropertyModel;
52import org.apache.wicket.spring.injection.annot.SpringBean;
53
54/**
55 * A panel that shows all available values for a selected facet. Supports two
56 * ordering modes (by name or result count) and dynamic filtering.
57 *
58 * TODO: group by first letter when sorted by name
59 *
60 * @author twagoo
61 */
62public abstract class AllFacetValuesPanel extends GenericPanel<FacetField> {
63
64    @SpringBean
65    private FieldValueConverterProvider fieldValueConverterProvider;
66
67    private final FacetFieldValuesProvider valuesProvider;
68    private final WebMarkupContainer valuesContainer;
69    private final IModel<FieldValuesFilter> filterModel;
70
71    /**
72     *
73     * @param id component id
74     * @param model model for the facet field to show values for
75     */
76    public AllFacetValuesPanel(String id, IModel<FacetField> model) {
77        this(id, model, null);
78    }
79
80    /**
81     *
82     * @param id component id
83     * @param model model for the facet field to show values for
84     * @param filterModel model that holds a string to filter in (can be null)
85     */
86    public AllFacetValuesPanel(String id, IModel<FacetField> model, IModel<FieldValuesFilter> filterModel) {
87        super(id, model);
88
89        if (filterModel != null) {
90            this.filterModel = filterModel;
91        } else {
92            this.filterModel = Model.of(new FieldValuesFilter());
93        }
94
95        // create a provider that shows all values and is sorted by name by default
96        valuesProvider = new FacetFieldValuesProvider(model, Integer.MAX_VALUE, FieldValueOrderSelector.NAME_SORT, fieldValueConverterProvider) {
97
98            @Override
99            protected IModel<FieldValuesFilter> getFilterModel() {
100                // filters the values
101                return AllFacetValuesPanel.this.filterModel;
102            }
103
104        };
105
106        // create a container for the values to allow for AJAX updates
107        valuesContainer = new WebMarkupContainer("facetValuesContainer");
108        valuesContainer.setOutputMarkupId(true);
109        add(valuesContainer);
110
111        // create the view of the actual values
112        final DataView<FacetField.Count> valuesView = createValuesView("facetValue");
113        valuesContainer.add(new AjaxPagingNavigator("navigator", valuesView) {
114
115            @Override
116            protected void onConfigure() {
117                super.onConfigure();
118                setVisible(valuesView.getPageCount() > 1);
119            }
120
121        });
122        valuesContainer.add(valuesView);
123
124        // create the form for selection sort option and entering filter string
125        final Form optionsForm = createOptionsForm("options");
126        optionsForm.setOutputMarkupId(true);
127        add(optionsForm);
128    }
129
130    private DataView<FacetField.Count> createValuesView(String id) {
131        final IModel<String> fieldNameModel = new PropertyModel<>(getModel(), "name");
132        return new DataView<FacetField.Count>(id, valuesProvider, ITEMS_PER_PAGE) {
133
134            @Override
135            protected void populateItem(final Item<FacetField.Count> item) {
136                item.setDefaultModel(new CompoundPropertyModel<>(item.getModel()));
137
138                // link to select an individual facet value
139                final Link selectLink = new AjaxFallbackLink("facetSelect") {
140
141                    @Override
142                    public void onClick(AjaxRequestTarget target) {
143                        // call callback
144                        onValuesSelected(
145                                item.getModelObject().getFacetField().getName(),
146                                // for now only single values can be selected
147                                new FacetSelection(Collections.singleton(item.getModelObject().getName())),
148                                target);
149                    }
150                };
151                item.add(selectLink);
152
153                // 'name' field from Count (name of value)
154                selectLink.add(new FieldValueLabel("name", fieldNameModel));
155
156                // 'count' field from Count (document count for value)
157                item.add(new Label("count"));
158            }
159        };
160    }
161    private static final int ITEMS_PER_PAGE = 250;
162
163    private Form createOptionsForm(String id) {
164        final Form options = new AjaxIndicatingForm(id);
165
166        final DropDownChoice<SortParam<FieldValuesOrder>> sortSelect
167                = new FieldValueOrderSelector("sort", new PropertyModel<SortParam<FieldValuesOrder>>(valuesProvider, "sort"));
168        sortSelect.add(new UpdateOptionsFormBehavior(options));
169        options.add(sortSelect);
170
171        final TextField filterField = new TextField<>("filter", new PropertyModel(filterModel, "name"));
172        filterField.add(new AjaxFormComponentUpdatingBehavior("keyup") {
173
174            @Override
175            protected void onUpdate(AjaxRequestTarget target) {
176                target.add(valuesContainer);
177            }
178        });
179        options.add(filterField);
180
181        addOccurenceOptions(options);
182
183        return options;
184    }
185
186    /**
187     * Creates form controls for minimal occurrences options.
188     *
189     * This requires a 'bridge' construct to combine a checkbox to
190     * enable/disable filtering by occurence altogether (which actually sets the
191     * min occurence to 0) and a dropdown for the minimal number of occurences,
192     * which only gets applied if the checkbox is ticket.
193     *
194     * The checkbox opens/closes a bridge between the minimal occurences in the
195     * filter model and the model that holds the selected option (modulated via
196     * the dropdown).
197     *
198     * @param options options form
199     */
200    private void addOccurenceOptions(final Form options) {
201
202        // Model that holds the actual number of occurences filtered on
203        final IModel<Integer> minOccurenceModel = new PropertyModel<>(filterModel, "minimalOccurence");
204        // Model that represents the filter state ('bridge' between filter and selection)
205        final IModel<Boolean> bridgeStateModel = Model.of(false);
206        // Model that represents the *selected* number of minimal occurences (passes it on if not decoupled)
207        final IModel<Integer> minOccurenceSelectModel = new BridgeOuterModel<>(minOccurenceModel, bridgeStateModel, 2);
208        // Model that links the actual filter, selection and bridge (object opens and closes it)
209        final IModel<Boolean> minOccurenceCheckBoxModel = new BridgeModel<>(minOccurenceModel, minOccurenceSelectModel, bridgeStateModel, 0);
210
211        // checkbox to open and close the 'bridge'
212        final CheckBox minOccurenceToggle = new CheckBox("minOccurrencesToggle", minOccurenceCheckBoxModel);
213        minOccurenceToggle.add(new UpdateOptionsFormBehavior(options));
214        options.add(minOccurenceToggle);
215
216        // Dropdown to select a value (which is applied to the filter if the 'bridge' is open)
217        final DropDownChoice<Integer> minOccurence = new DropDownChoice<>("minOccurences", minOccurenceSelectModel, ImmutableList.of(2, 5, 10, 100, 1000));
218        minOccurence.add(new UpdateOptionsFormBehavior(options) {
219
220            @Override
221            protected void onUpdate(AjaxRequestTarget target) {
222                super.onUpdate(target);
223                // on change, apply to inner model ('open bridge')
224                if (!minOccurenceCheckBoxModel.getObject()) {
225                    minOccurenceCheckBoxModel.setObject(true);
226                }
227            }
228
229        });
230        options.add(minOccurence);
231    }
232
233    private class UpdateOptionsFormBehavior extends OnChangeAjaxBehavior {
234
235        private final Form options;
236
237        public UpdateOptionsFormBehavior(Form options) {
238            this.options = options;
239        }
240
241        @Override
242        protected void onUpdate(AjaxRequestTarget target) {
243            target.add(options);
244            target.add(valuesContainer);
245        }
246
247    }
248
249    @Override
250    public void detachModels() {
251        super.detachModels();
252        filterModel.detach();
253    }
254
255    /**
256     * Callback triggered when values have been selected on this facet
257     *
258     * @param facet name of the facet this panel represents
259     * @param values selected values
260     * @param target Ajax target allowing for a partial update. May be null
261     * (fallback)!
262     */
263    protected abstract void onValuesSelected(String facet, FacetSelection values, AjaxRequestTarget target);
264
265}
Note: See TracBrowser for help on using the repository browser.