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

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

merged with trunk 3.4

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