source: vlo/branches/vlo-3.3-oeaw/vlo-web-app/src/main/java/eu/clarin/cmdi/vlo/wicket/panels/search/FacetValuesPanel.java @ 6501

Last change on this file since 6501 was 6501, checked in by davor.ostojic@oeaw.ac.at, 9 years ago

#792 from clarin
null values is resolved by adding "default" param to solr's schema.xml. String that represents null values is "[missing value]"

File size: 12.5 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.ImmutableSet;
20import eu.clarin.cmdi.vlo.JavaScriptResources;
21import eu.clarin.cmdi.vlo.pojo.FacetSelection;
22import eu.clarin.cmdi.vlo.pojo.FieldValuesFilter;
23import eu.clarin.cmdi.vlo.pojo.FieldValuesOrder;
24import eu.clarin.cmdi.vlo.pojo.QueryFacetsSelection;
25import eu.clarin.cmdi.vlo.wicket.components.FieldValueLabel;
26import eu.clarin.cmdi.vlo.wicket.provider.PartitionedDataProvider;
27import eu.clarin.cmdi.vlo.wicket.model.FacetFieldModel;
28import eu.clarin.cmdi.vlo.wicket.model.SolrFieldNameModel;
29import eu.clarin.cmdi.vlo.wicket.pages.AllFacetValuesPage;
30import eu.clarin.cmdi.vlo.wicket.provider.FacetFieldValuesProvider;
31import eu.clarin.cmdi.vlo.wicket.provider.FieldValueConverterProvider;
32import java.util.Collection;
33import java.util.Collections;
34import java.util.List;
35import org.apache.solr.client.solrj.response.FacetField;
36import org.apache.solr.client.solrj.response.FacetField.Count;
37import org.apache.wicket.ajax.AjaxRequestTarget;
38import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
39import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxFallbackLink;
40import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
41import org.apache.wicket.markup.head.IHeaderResponse;
42import org.apache.wicket.markup.head.JavaScriptHeaderItem;
43import org.apache.wicket.markup.html.WebMarkupContainer;
44import org.apache.wicket.markup.html.basic.Label;
45import org.apache.wicket.markup.html.form.Form;
46import org.apache.wicket.markup.html.form.TextField;
47import org.apache.wicket.markup.html.link.Link;
48import org.apache.wicket.markup.html.list.ListItem;
49import org.apache.wicket.markup.html.list.ListView;
50import org.apache.wicket.markup.html.panel.GenericPanel;
51import org.apache.wicket.markup.repeater.Item;
52import org.apache.wicket.markup.repeater.data.DataView;
53import org.apache.wicket.model.CompoundPropertyModel;
54import org.apache.wicket.model.IModel;
55import org.apache.wicket.model.Model;
56import org.apache.wicket.model.PropertyModel;
57import org.apache.wicket.request.cycle.RequestCycle;
58import org.apache.wicket.spring.injection.annot.SpringBean;
59
60/**
61 * A panel representing a single facet and its selectable values
62 *
63 * @author twagoo
64 */
65public abstract class FacetValuesPanel extends GenericPanel<FacetField> {
66
67    public final static int MAX_NUMBER_OF_FACETS_TO_SHOW = 10; //TODO: get from config
68    public final static Collection<String> LOW_PRIORITY_VALUES = ImmutableSet.of("unknown", "unspecified", "", "[missing value]");
69
70    private final ModalWindow valuesWindow;
71    private final IModel<QueryFacetsSelection> selectionModel;
72    private final WebMarkupContainer valuesContainer;
73    private final IModel<FieldValuesFilter> filterModel;
74    private final int subListSize;
75    private final IModel<String> fieldNameModel;
76   
77    @SpringBean
78    private FieldValueConverterProvider fieldValueConverterProvider;
79    /**
80     * Creates a new panel with selectable values for a single facet
81     *
82     * @param id component id
83     * @param model facet field model for this panel
84     * @param selectionModel model holding the global query/facet selection
85     */
86    public FacetValuesPanel(String id, final IModel<FacetField> model, final IModel<QueryFacetsSelection> selectionModel) {
87        this(id, model, selectionModel, 0);
88    }
89
90    /**
91     * Creates a new panel with selectable values for a single facet
92     *
93     * @param id component id
94     * @param model facet field model for this panel
95     * @param selectionModel model holding the global query/facet selection
96     * @param subListSize if large than 0, multiple lists will be generated each
97     * with a maximum size of this value
98     */
99    public FacetValuesPanel(String id, final IModel<FacetField> model, final IModel<QueryFacetsSelection> selectionModel, int subListSize) {
100        super(id, model);
101        this.selectionModel = selectionModel;
102        this.subListSize = subListSize;
103
104        // shared model that holds the string for filtering the values (quick search)
105        filterModel = Model.of(new FieldValuesFilter());
106        // create a form with an input bound to the filter model
107        add(createFilterForm("filter"));
108
109        // create a container for values to allow for AJAX updates when filtering
110        valuesContainer = new WebMarkupContainer("valuesContainer");
111        valuesContainer.setOutputMarkupId(true);
112        add(valuesContainer);
113
114        // create a view for the actual values
115        valuesContainer.add(createValuesView("valuesList"));
116
117        // create a link for showing all values       
118        valuesContainer.add(createAllValuesLink("allFacetValuesLink"));
119
120        // create a popup window for all facet values
121        valuesWindow = createAllValuesWindow("allValues");
122        add(valuesWindow);
123
124        fieldNameModel = new PropertyModel<>(model, "name");
125    }
126
127    /**
128     * Creates a form with an input bound to the filter model
129     *
130     * @param id component id
131     * @return filter form
132     */
133    private Form createFilterForm(String id) {
134        final Form filterForm = new Form(id);
135        final TextField<String> filterField = new TextField<String>("filterText",
136                new PropertyModel<String>(filterModel, "name"));
137        // make field update
138        filterField.add(new AjaxFormComponentUpdatingBehavior("keyup") {
139
140            @Override
141            protected void onUpdate(AjaxRequestTarget target) {
142                //update values
143                target.add(valuesContainer);
144            }
145        });
146        filterForm.add(filterField);
147        return filterForm;
148    }
149
150    /**
151     * Creates a view for the actual values (as links) for selection
152     *
153     * @param id component id
154     * @return data view with value links
155     */
156    private DataView createValuesView(String id) {
157        final FacetFieldValuesProvider valuesProvider = new FacetFieldValuesProvider(getModel(), MAX_NUMBER_OF_FACETS_TO_SHOW, LOW_PRIORITY_VALUES, fieldValueConverterProvider){
158
159            @Override
160            protected IModel<FieldValuesFilter> getFilterModel() {
161                return filterModel;
162            }
163
164        };
165        // partition the values according to the specified partition size
166        final PartitionedDataProvider<Count, FieldValuesOrder> partitionedValuesProvider = new PartitionedDataProvider<FacetField.Count, FieldValuesOrder>(valuesProvider, subListSize);
167
168        // create the view for the partitions
169        final DataView<List<? extends Count>> valuesView = new DataView<List<? extends Count>>(id, partitionedValuesProvider) {
170
171            @Override
172            protected void populateItem(Item<List<? extends Count>> item) {
173                // create a list view for the values in this partition
174                item.add(new ListView<Count>("facetValues", item.getModel()) {
175
176                    @Override
177                    protected void populateItem(ListItem<Count> item) {
178                        addFacetValue("facetSelect", item);
179                    }
180                });
181            }
182        };
183        valuesView.setOutputMarkupId(true);
184        return valuesView;
185    }
186
187    /**
188     * Adds an individual facet value selection link to a dataview item
189     *
190     * @param item item to add link to
191     */
192    private void addFacetValue(String id, final ListItem<Count> item) {
193        item.setDefaultModel(new CompoundPropertyModel<Count>(item.getModel()));
194
195        // link to select an individual facet value
196        final Link selectLink = new IndicatingAjaxFallbackLink(id) {
197
198            @Override
199            public void onClick(AjaxRequestTarget target) {
200                // reset filter
201                filterModel.getObject().setName(null);
202
203                // call callback
204                onValuesSelected(
205                        // for now only single values can be selected
206                                Collections.singleton(item.getModelObject().getName()),
207                        target);
208            }
209        };
210        item.add(selectLink);
211
212        // 'name' field from Count (name of value)
213        selectLink.add(new FieldValueLabel("name", fieldNameModel));
214        // 'count' field from Count (document count for value)
215        selectLink.add(new Label("count"));
216    }
217
218    /**
219     * Creates a link that leads to the 'all facet values' view, either as a
220     * modal window (if JavaScript is enabled, see {@link #createAllValuesWindow(java.lang.String)
221     * }) or by redirecting to {@link AllFacetValuesPage}
222     *
223     * @param id component id
224     * @return 'show all values' link
225     */
226    private Link createAllValuesLink(String id) {
227        final Link link = new IndicatingAjaxFallbackLink<FacetField>(id, getModel()) {
228
229            @Override
230            public void onClick(AjaxRequestTarget target) {
231                if (target == null) {
232                    // no JavaScript, open a new page with values
233                    setResponsePage(new AllFacetValuesPage(getModel(), selectionModel));
234                } else {
235                    // JavaScript enabled, show values in a modal popup
236                    valuesWindow.show(target);
237                }
238            }
239
240            @Override
241            protected void onConfigure() {
242                super.onConfigure();
243                // only show if there actually are more values!
244                setVisible(getModelObject().getValueCount() > MAX_NUMBER_OF_FACETS_TO_SHOW);
245            }
246
247        };
248        return link;
249    }
250
251    /**
252     * Creates a modal window showing a {@link AllFacetValuesPanel} for this
253     * facet
254     *
255     * @param id component id
256     * @return 'all facet values' modal window component
257     */
258    private ModalWindow createAllValuesWindow(String id) {
259        final ModalWindow window = new ModalWindow(id) {
260
261            @Override
262            public IModel<String> getTitle() {
263                return new SolrFieldNameModel(getModel(), "name");
264            }
265
266        };
267        window.showUnloadConfirmation(false);
268
269        final AllFacetValuesPanel allValuesPanel = new AllFacetValuesPanel(window.getContentId(), getModel(), filterModel) {
270
271            @Override
272            protected void onValuesSelected(Collection<String> values, AjaxRequestTarget target) {
273                if (target != null) {
274                    // target can be null if selection link was opened in a new tab
275                    window.close(target);
276                }
277                FacetValuesPanel.this.onValuesSelected(values, target);
278            }
279        };
280        window.addOrReplace(allValuesPanel);
281        return window;
282    }
283
284    @Override
285    public void detachModels() {
286        super.detachModels();
287        selectionModel.detach();
288        filterModel.detach();
289    }
290   
291
292    /**
293     * Callback triggered when values have been selected on this facet
294     *
295     * @param facet name of the facet this panel represents
296     * @param values selected values
297     * @param target Ajax target allowing for a partial update. May be null
298     * (fallback)!
299     */
300    protected abstract void onValuesSelected(Collection<String> values, AjaxRequestTarget target);
301   
302    @Override
303    protected void onConfigure() {
304        // TODO Auto-generated method stub
305        super.onConfigure();
306    }
307
308    @Override
309    protected void onBeforeRender() {
310        super.onBeforeRender();
311        // if an ajax update, set the watermark on the input field
312        final AjaxRequestTarget target = RequestCycle.get().find(AjaxRequestTarget.class);
313        if (target != null) {
314            target.appendJavaScript(String.format("jQuery('#%1$s input').watermark('Type to search for more');", getMarkupId()));
315            // focus? better only when expanded. jQuery('#%1$s input').focus()
316        }
317    }
318
319    @Override
320    public void renderHead(IHeaderResponse response) {
321        // include watermark JQuery extension sources
322        response.render(JavaScriptHeaderItem.forReference(JavaScriptResources.getJQueryWatermarkJS()));
323    }
324
325}
Note: See TracBrowser for help on using the repository browser.