├── filteringtable-demo ├── src │ └── main │ │ ├── resources │ │ ├── .gitignore │ │ └── org │ │ │ └── tepi │ │ │ └── filtertable │ │ │ └── demo │ │ │ └── FilterTableDemoWidgetset.gwt.xml │ │ ├── webapp │ │ └── META-INF │ │ │ ├── MANIFEST.MF │ │ │ └── context.xml │ │ └── java │ │ └── org │ │ └── tepi │ │ └── filtertable │ │ └── demo │ │ ├── DemoFilterGenerator.java │ │ ├── DemoFilterDecorator.java │ │ └── FilterTableDemoUI.java ├── .gitignore └── pom.xml ├── .gitignore ├── filteringtable-addon ├── .gitignore ├── src │ └── main │ │ ├── resources │ │ └── org │ │ │ └── tepi │ │ │ └── filtertable │ │ │ ├── WidgetSet.gwt.xml │ │ │ └── public │ │ │ └── filteringtable │ │ │ └── styles.css │ │ └── java │ │ └── org │ │ └── tepi │ │ └── filtertable │ │ ├── paged │ │ ├── PagedTableChangeEvent.java │ │ ├── PagedFilterControlConfig.java │ │ ├── PagedFilterTableContainer.java │ │ └── PagedFilterTable.java │ │ ├── datefilter │ │ ├── DateInterval.java │ │ └── DateFilterPopup.java │ │ ├── numberfilter │ │ ├── NumberInterval.java │ │ ├── NumberFilterPopupConfig.java │ │ └── NumberFilterPopup.java │ │ ├── FilterGenerator.java │ │ ├── client │ │ └── ui │ │ │ ├── FilterTreeTableConnector.java │ │ │ ├── FilterTableConnector.java │ │ │ ├── VFilterTable.java │ │ │ └── VFilterTreeTable.java │ │ ├── FilterDecorator.java │ │ ├── FilterTable.java │ │ ├── FilterTreeTable.java │ │ └── FilterFieldGenerator.java ├── assembly │ ├── MANIFEST.MF │ └── assembly.xml └── pom.xml ├── pom.xml ├── README.md └── LICENSE.txt /filteringtable-demo/src/main/resources/.gitignore: -------------------------------------------------------------------------------- 1 | rebel.xml 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .settings 3 | .project 4 | .classpath 5 | .DS_Store 6 | **/rebel.xml 7 | -------------------------------------------------------------------------------- /filteringtable-demo/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .settings 3 | .project 4 | .classpath 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /filteringtable-addon/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .settings 3 | .project 4 | .classpath 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /filteringtable-demo/src/main/webapp/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Class-Path: 3 | 4 | -------------------------------------------------------------------------------- /filteringtable-demo/src/main/webapp/META-INF/context.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /filteringtable-addon/src/main/resources/org/tepi/filtertable/WidgetSet.gwt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /filteringtable-addon/assembly/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Vaadin-Package-Version: 1 3 | Vaadin-Addon: ${Vaadin-Addon} 4 | Vaadin-License-Title: ${Vaadin-License-Title} 5 | Implementation-Vendor: ${Implementation-Vendor} 6 | Implementation-Title: ${Implementation-Title} 7 | Implementation-Version: ${Implementation-Version} 8 | -------------------------------------------------------------------------------- /filteringtable-demo/src/main/resources/org/tepi/filtertable/demo/FilterTableDemoWidgetset.gwt.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | org.tepi.filteringtable 6 | filteringtable-root 7 | pom 8 | 1.0.1.v8 9 | FilteringTable Add-on Root Project 10 | 11 | 12 | 3 13 | 14 | 15 | 16 | filteringtable-addon 17 | filteringtable-demo 18 | 19 | 20 | -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/paged/PagedTableChangeEvent.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable.paged; 2 | 3 | import java.io.Serializable; 4 | 5 | @SuppressWarnings("serial") 6 | public class PagedTableChangeEvent implements Serializable { 7 | private final PagedFilterTable table; 8 | 9 | public PagedTableChangeEvent(PagedFilterTable table) { 10 | this.table = table; 11 | } 12 | 13 | public PagedFilterTable getTable() { 14 | return table; 15 | } 16 | 17 | public int getCurrentPage() { 18 | return table.getCurrentPage(); 19 | } 20 | 21 | public int getTotalAmountOfPages() { 22 | return table.getTotalAmountOfPages(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/datefilter/DateInterval.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable.datefilter; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | /** 7 | * Simple date interval providing isBetween comparison for other Date objects. 8 | * 9 | * @author Teppo Kurki 10 | * 11 | */ 12 | @SuppressWarnings("serial") 13 | public class DateInterval implements Serializable { 14 | private final Date from; 15 | private final Date to; 16 | 17 | public DateInterval(Date from, Date to) { 18 | this.from = from; 19 | this.to = to; 20 | } 21 | 22 | public Date getFrom() { 23 | return from; 24 | } 25 | 26 | public Date getTo() { 27 | return to; 28 | } 29 | 30 | public boolean isNull() { 31 | return from == null && to == null; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/numberfilter/NumberInterval.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable.numberfilter; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * @author Vimukthi 7 | * 8 | */ 9 | @SuppressWarnings("serial") 10 | public class NumberInterval implements Serializable { 11 | 12 | private final String lessThanValue; 13 | private final String greaterThanValue; 14 | private final String equalsValue; 15 | 16 | public NumberInterval(String lessThanValue, String greaterThanValue, String equalsValue) { 17 | this.lessThanValue = lessThanValue; 18 | this.greaterThanValue = greaterThanValue; 19 | this.equalsValue = equalsValue; 20 | } 21 | 22 | public String getLessThanValue() { 23 | return lessThanValue; 24 | } 25 | 26 | public String getGreaterThanValue() { 27 | return greaterThanValue; 28 | } 29 | 30 | public String getEqualsValue() { 31 | return equalsValue; 32 | } 33 | } -------------------------------------------------------------------------------- /filteringtable-addon/assembly/assembly.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | addon 7 | 8 | 10 | 11 | 12 | zip 13 | 14 | 15 | 16 | false 17 | 18 | 19 | 20 | .. 21 | 22 | LICENSE.txt 23 | README.md 24 | 25 | 26 | 27 | target 28 | 29 | 30 | *.jar 31 | *.pdf 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | assembly/MANIFEST.MF 40 | META-INF 41 | true 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/numberfilter/NumberFilterPopupConfig.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable.numberfilter; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 7 | * Provides way to set decorative configurations for the 8 | * {@link NumberFilterPopup} 9 | * 10 | * @author Vimukthi 11 | * 12 | */ 13 | @SuppressWarnings("serial") 14 | public class NumberFilterPopupConfig implements Serializable { 15 | private String ltPrompt; 16 | private String gtPrompt; 17 | private String eqPrompt; 18 | private String okCaption; 19 | private String resetCaption; 20 | private String valueMarker; 21 | 22 | public String getLtPrompt() { 23 | return ltPrompt; 24 | } 25 | 26 | public void setLtPrompt(String ltPrompt) { 27 | this.ltPrompt = ltPrompt; 28 | } 29 | 30 | public String getGtPrompt() { 31 | return gtPrompt; 32 | } 33 | 34 | public void setGtPrompt(String gtPrompt) { 35 | this.gtPrompt = gtPrompt; 36 | } 37 | 38 | public String getEqPrompt() { 39 | return eqPrompt; 40 | } 41 | 42 | public void setEqPrompt(String eqPrompt) { 43 | this.eqPrompt = eqPrompt; 44 | } 45 | 46 | public String getOkCaption() { 47 | return okCaption; 48 | } 49 | 50 | public void setOkCaption(String okCaption) { 51 | this.okCaption = okCaption; 52 | } 53 | 54 | public String getResetCaption() { 55 | return resetCaption; 56 | } 57 | 58 | public void setResetCaption(String resetCaption) { 59 | this.resetCaption = resetCaption; 60 | } 61 | 62 | public String getValueMarker() { 63 | return valueMarker; 64 | } 65 | 66 | public void setValueMarker(String valueMarker) { 67 | this.valueMarker = valueMarker; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/paged/PagedFilterControlConfig.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable.paged; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class PagedFilterControlConfig { 7 | 8 | private String itemsPerPage = "Items per page:"; 9 | private String page = "Page:"; 10 | 11 | private String first = "<<"; 12 | private String last = ">>"; 13 | 14 | private String previous = "<"; 15 | private String next = ">"; 16 | 17 | private List pageLengths; 18 | 19 | public String getItemsPerPage() { 20 | return itemsPerPage; 21 | } 22 | 23 | public void setItemsPerPage(String itemsPerPage) { 24 | this.itemsPerPage = itemsPerPage; 25 | } 26 | 27 | public String getPage() { 28 | return page; 29 | } 30 | 31 | public void setPage(String page) { 32 | this.page = page; 33 | } 34 | 35 | public String getFirst() { 36 | return first; 37 | } 38 | 39 | public void setFirst(String first) { 40 | this.first = first; 41 | } 42 | 43 | public String getLast() { 44 | return last; 45 | } 46 | 47 | public void setLast(String last) { 48 | this.last = last; 49 | } 50 | 51 | public String getPrevious() { 52 | return previous; 53 | } 54 | 55 | public void setPrevious(String previous) { 56 | this.previous = previous; 57 | } 58 | 59 | public String getNext() { 60 | return next; 61 | } 62 | 63 | public void setNext(String next) { 64 | this.next = next; 65 | } 66 | 67 | public List getPageLengths() { 68 | return pageLengths; 69 | } 70 | 71 | public void setPageLengthsAndCaptions(List pageLengths) { 72 | this.pageLengths = pageLengths; 73 | } 74 | 75 | public PagedFilterControlConfig() { 76 | pageLengths = new ArrayList(); 77 | pageLengths.add(5); 78 | pageLengths.add(10); 79 | pageLengths.add(25); 80 | pageLengths.add(50); 81 | pageLengths.add(100); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Published on Vaadin Directory](https://img.shields.io/badge/Vaadin%20Directory-published-00b4f0.svg)](https://vaadin.com/directory/component/filteringtable) 2 | [![Stars on Vaadin Directory](https://img.shields.io/vaadin-directory/star/filteringtable.svg)](https://vaadin.com/directory/component/filteringtable) 3 | 4 | FilteringTable 5 | ============== 6 | 7 | FilterTable is an extension of the Table component in Vaadin. FilterTable is available for Vaadin 6, Vaadin 7 (versions postfixed with '.v7') and Vaadin 8 (versions postfixed with '.v8'). 8 | 9 | ##### Added features: 10 | * Provides automatically generated filter components between the table header and body 11 | * Automatically filters enum, boolean and date properties 12 | * Other properties are filtered as a string 13 | * Numeric properties can optionally be filtered with a comparator popup 14 | * Allows developer to provide own filter implementation for any property 15 | * Allows developer to decorate the numeric, date, enum and boolean filter components with custom captions and icons 16 | * Provides integration of PagedTable add-on with the filter bar 17 | * Provides integration of TreeTable with the filter bar 18 | 19 | ## Please always use the latest version of FilteringTable add-on. Bugfixes will only be done for the latest versions of each branch, and the Vaadin 8 version has priority. The Vaadin 6 version will no longer receive any fixes. 20 | 21 | A big thanks goes to the numerous contributors providing ideas, bug fixes and new features for FilteringTable. 22 | 23 | License 24 | ======= 25 | 26 | Copyright 2012 - 2019 Teppo Kurki / Vaadin Ltd. 27 | 28 | Licensed under the Apache License, Version 2.0 (the "License"); 29 | you may not use this file except in compliance with the License. 30 | You may obtain a copy of the License at 31 | 32 | http://www.apache.org/licenses/LICENSE-2.0 33 | 34 | Unless required by applicable law or agreed to in writing, software 35 | distributed under the License is distributed on an "AS IS" BASIS, 36 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 37 | See the License for the specific language governing permissions and 38 | limitations under the License. 39 | -------------------------------------------------------------------------------- /filteringtable-demo/src/main/java/org/tepi/filtertable/demo/DemoFilterGenerator.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable.demo; 2 | 3 | import java.io.Serializable; 4 | 5 | import org.tepi.filtertable.FilterGenerator; 6 | 7 | import com.vaadin.server.Page; 8 | import com.vaadin.ui.Notification; 9 | import com.vaadin.v7.data.Container.Filter; 10 | import com.vaadin.v7.data.util.filter.Compare; 11 | import com.vaadin.v7.data.util.filter.Or; 12 | import com.vaadin.v7.ui.AbstractField; 13 | import com.vaadin.v7.ui.CheckBox; 14 | import com.vaadin.v7.ui.Field; 15 | 16 | @SuppressWarnings("serial") 17 | class DemoFilterGenerator implements FilterGenerator, Serializable { 18 | 19 | @Override 20 | public Filter generateFilter(Object propertyId, Object value) { 21 | if ("id".equals(propertyId)) { 22 | /* Create an 'equals' filter for the ID field */ 23 | if (value != null && value instanceof String) { 24 | try { 25 | return new Compare.Equal(propertyId, Integer.parseInt((String) value)); 26 | } catch (NumberFormatException ignored) { 27 | // If no integer was entered, just generate default filter 28 | } 29 | } 30 | } else if ("checked".equals(propertyId)) { 31 | if (value != null && value instanceof Boolean) { 32 | if (Boolean.TRUE.equals(value)) { 33 | return new Compare.Equal(propertyId, value); 34 | } else { 35 | return new Or(new Compare.Equal(propertyId, true), new Compare.Equal(propertyId, false)); 36 | } 37 | } 38 | } 39 | // For other properties, use the default filter 40 | return null; 41 | } 42 | 43 | @Override 44 | public Filter generateFilter(Object propertyId, Field originatingField) { 45 | // Use the default filter 46 | return null; 47 | } 48 | 49 | @Override 50 | public AbstractField getCustomFilterComponent(Object propertyId) { 51 | // removed custom filter component for id 52 | if ("checked".equals(propertyId)) { 53 | CheckBox box = new CheckBox(); 54 | return box; 55 | } 56 | return null; 57 | } 58 | 59 | @Override 60 | public void filterRemoved(Object propertyId) { 61 | Notification n = new Notification("Filter removed from: " + propertyId, Notification.Type.TRAY_NOTIFICATION); 62 | n.setDelayMsec(800); 63 | n.show(Page.getCurrent()); 64 | } 65 | 66 | @Override 67 | public void filterAdded(Object propertyId, Class filterType, Object value) { 68 | Notification n = new Notification("Filter added to: " + propertyId, Notification.Type.TRAY_NOTIFICATION); 69 | n.setDelayMsec(800); 70 | n.show(Page.getCurrent()); 71 | } 72 | 73 | @Override 74 | public Filter filterGeneratorFailed(Exception reason, Object propertyId, Object value) { 75 | /* Return null -> Does not add any filter on failure */ 76 | return null; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/FilterGenerator.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable; 2 | 3 | import java.io.Serializable; 4 | 5 | import com.vaadin.v7.data.Container.Filter; 6 | import com.vaadin.v7.ui.AbstractField; 7 | import com.vaadin.v7.ui.Field; 8 | 9 | /** 10 | * Interface for generating custom Filters from values entered to the filtering 11 | * fields by the user. 12 | * 13 | * @author tepi 14 | * 15 | */ 16 | @SuppressWarnings({ "deprecation" }) 17 | public interface FilterGenerator extends Serializable { 18 | 19 | /** 20 | * Generates a new Filter for the property with the given ID, using the 21 | * Value object as basis for the filtering. 22 | * 23 | * @param propertyId 24 | * ID of the filtered property. 25 | * @param value 26 | * Value entered by the user. Type of the value will depend on 27 | * the type this property has in the underlying container. Date, 28 | * Boolean and enum types will be provided as themselves. All 29 | * other types of properties will result in a String-typed 30 | * filtering value. 31 | * @return A generated Filter object, or NULL if you want to allow 32 | * FilterTable to generate the default Filter for this property. 33 | */ 34 | public Filter generateFilter(Object propertyId, Object value); 35 | 36 | /** 37 | * Generates a new Filter for the property with the given ID, using the 38 | * Field object and its value as basis for the filtering. 39 | * 40 | * @param propertyId 41 | * ID of the filtered property. 42 | * @param originatingField 43 | * Reference to the field that triggered this filter generating 44 | * request. 45 | * @return A generated Filter object, or NULL if you want to allow 46 | * FilterTable to generate the default Filter for this property. 47 | */ 48 | public Filter generateFilter(Object propertyId, Field originatingField); 49 | 50 | /** 51 | * Allows you to provide a custom filtering field for the properties as 52 | * needed. 53 | * 54 | * @param propertyId 55 | * ID of the property for for which the field is asked for 56 | * @return a custom filtering field OR null if you want to use the generated 57 | * default field. 58 | */ 59 | public AbstractField getCustomFilterComponent(Object propertyId); 60 | 61 | /** 62 | * This method is called when a filter has been removed from the container 63 | * by the FilterTable. 64 | * 65 | * @param propertyId 66 | * ID of the property from which the filter was removed 67 | */ 68 | public void filterRemoved(Object propertyId); 69 | 70 | /** 71 | * This method is called when a filter has been added to the container by 72 | * the FilterTable 73 | * 74 | * @param propertyId 75 | * ID of the property to which the filter was added 76 | * @param filterType 77 | * Type of the filter (class) 78 | * @param value 79 | * Value the filter was based on 80 | */ 81 | public void filterAdded(Object propertyId, Class filterType, Object value); 82 | 83 | /** 84 | * This method is called when Filter generator fails for any reason. You may 85 | * handle the error and return any Filter or null as the result. 86 | * 87 | * @param reason 88 | * Root exception 89 | * @param propertyId 90 | * ID of the property to which the filter was to be added 91 | * @param value 92 | * Value the filter was to be based on 93 | * @return A Filter or null 94 | */ 95 | public Filter filterGeneratorFailed(Exception reason, Object propertyId, Object value); 96 | } 97 | -------------------------------------------------------------------------------- /filteringtable-addon/src/main/resources/org/tepi/filtertable/public/filteringtable/styles.css: -------------------------------------------------------------------------------- 1 | .filters-panel { 2 | background-color: #f4f4f4; 3 | width: 90000px; 4 | overflow: hidden; 5 | height: 37px; 6 | } 7 | 8 | .filters-panel .v-label, 9 | .filters-panel .filterplaceholder { 10 | height: 37px; 11 | } 12 | 13 | .v-app .v-table .filters-panel input.v-textfield.v-widget, 14 | .v-window .v-table .filters-panel input.v-textfield.v-widget { 15 | height: 37px; 16 | border-radius: 0px; 17 | -webkit-border-radius: 0px; 18 | -moz-border-radius: 0px; 19 | -o-border-radius: 0px; 20 | } 21 | 22 | .datefilterpopup .v-button.v-popupbutton, 23 | .datefilterpopup .v-button.v-popupbutton:focus, 24 | .datefilterpopup .v-button.v-popupbutton:active, 25 | .datefilterpopup .v-button.v-popupbutton.v-pressed, 26 | .numberfilterpopup .v-button.v-popupbutton, 27 | .numberfilterpopup .v-button.v-popupbutton:focus, 28 | .numberfilterpopup .v-button.v-popupbutton:active, 29 | .numberfilterpopup .v-button.v-popupbutton.v-pressed { 30 | background: #fff none repeat-x 0 100%; 31 | height: 37px; 32 | border-radius: 0; 33 | border: 1px solid #c5c5c5; 34 | position: relative; 35 | text-align: left; 36 | box-shadow: none; 37 | } 38 | 39 | .datefilterpopup .v-button.v-popupbutton:focus, 40 | .datefilterpopup .v-button.v-popupbutton:active, 41 | .datefilterpopup .v-button.v-popupbutton.v-pressed, 42 | .numberfilterpopup .v-button.v-popupbutton:focus, 43 | .numberfilterpopup .v-button.v-popupbutton:active, 44 | .numberfilterpopup .v-button.v-popupbutton.v-pressed { 45 | border-color: #4F83B4 #5B97D0 #5CA0DF; 46 | } 47 | 48 | .datefilterpopup .v-button.v-popupbutton .v-button-wrap, 49 | .datefilterpopup .v-button.v-popupbutton:focus .v-button-wrap, 50 | .datefilterpopup .v-button.v-popupbutton:active .v-button-wrap, 51 | .datefilterpopup .v-button.v-popupbutton.v-pressed .v-button-wrap, 52 | .numberfilterpopup .v-button.v-popupbutton .v-button-wrap, 53 | .numberfilterpopup .v-button.v-popupbutton:focus .v-button-wrap, 54 | .numberfilterpopup .v-button.v-popupbutton:active .v-button-wrap, 55 | .numberfilterpopup .v-button.v-popupbutton.v-pressed .v-button-wrap { 56 | background: none; 57 | overflow: hidden; 58 | padding: 0; 59 | padding-top: 1px; 60 | } 61 | 62 | 63 | .datefilterpopup .v-button.v-popupbutton .v-popup-indicator, 64 | .numberfilterpopup .v-button.v-popupbutton .v-popup-indicator { 65 | height: 100%; 66 | width: 37px; 67 | position: absolute; 68 | right: 0; 69 | top: 0; 70 | zoom: 1; 71 | border-left: 1px solid #e4e4e4; 72 | padding-top: 2px; 73 | font-size: 20px; 74 | color: #a3a3a3; 75 | text-align: center; 76 | } 77 | 78 | .datefilterpopup .v-button.v-popupbutton .v-popup-indicator::before, 79 | .numberfilterpopup .v-button.v-popupbutton .v-popup-indicator::before { 80 | opacity: 1; 81 | margin: 0; 82 | } 83 | 84 | .v-ie .datefilterpopup .v-button.v-popupbutton .v-popup-indicator, 85 | .v-ie .numberfilterpopup .v-button.v-popupbutton .v-popup-indicator { 86 | display: inline; 87 | } 88 | 89 | .datefilterpopup .v-button.v-popupbutton .v-button-caption, 90 | .numberfilterpopup .v-button.v-popupbutton .v-button-caption { 91 | display: inline-block; 92 | background: none; 93 | height: 12px; 94 | padding: 0; 95 | padding-top: 2px; 96 | font-weight: 400; 97 | color: #474747; 98 | } 99 | 100 | .filterwrapper { 101 | overflow: hidden !important; 102 | border-left: 1px solid #d4d4d4; 103 | } 104 | .filterwrapper-first { 105 | border-left: none; 106 | } 107 | 108 | .filters-panel .v-checkbox { 109 | margin-top: 9px; 110 | margin-left: 14px; 111 | } 112 | 113 | .filters-panel .v-filterselect.v-widget { 114 | border-radius: 0; 115 | } -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/client/ui/FilterTreeTableConnector.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable.client.ui; 2 | 3 | import java.util.HashMap; 4 | import java.util.Iterator; 5 | import java.util.Map; 6 | 7 | import org.tepi.filtertable.FilterTreeTable; 8 | 9 | import com.google.gwt.user.client.ui.Widget; 10 | import com.vaadin.client.ApplicationConnection; 11 | import com.vaadin.client.ComponentConnector; 12 | import com.vaadin.client.UIDL; 13 | import com.vaadin.shared.ui.Connect; 14 | import com.vaadin.v7.client.ui.treetable.TreeTableConnector; 15 | 16 | /** 17 | * Class FilterTableConnector. 18 | * 19 | * @author Teppo Kurki 20 | * @since 27.10.2014 21 | */ 22 | @SuppressWarnings("serial") 23 | @Connect(FilterTreeTable.class) 24 | public class FilterTreeTableConnector extends TreeTableConnector { 25 | 26 | public static final String TAG_FILTERS = "filters"; 27 | public static final String TAG_FILTER_COMPONENT = "filtercomponent-"; 28 | public static final String ATTRIBUTE_FILTERS_VISIBLE = "filtersvisible"; 29 | public static final String ATTRIBUTE_FORCE_RENDER = "forceRender"; 30 | public static final String ATTRIBUTE_COLUMN_ID = "columnid"; 31 | public static final String ATTRIBUTE_CACHED = "cached"; 32 | public static final String ATTRIBUTE_COLUMN_HEADER_STYLE_NAMES = "columnheaderstylenames"; 33 | 34 | @Override 35 | public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { 36 | super.updateFromUIDL(uidl, client); 37 | updateFiltersFromUIDL(uidl.getChildByTagName(FilterTreeTableConnector.TAG_FILTERS), client); 38 | } 39 | 40 | @Override 41 | public VFilterTreeTable getWidget() { 42 | return (VFilterTreeTable) super.getWidget(); 43 | } 44 | 45 | @SuppressWarnings("deprecation") 46 | private void updateFiltersFromUIDL(UIDL uidl, ApplicationConnection client) { 47 | VFilterTreeTable filterTable = getWidget(); 48 | 49 | boolean filtersVisible = uidl.hasAttribute(ATTRIBUTE_FILTERS_VISIBLE) 50 | ? uidl.getBooleanAttribute(ATTRIBUTE_FILTERS_VISIBLE) : false; 51 | filterTable.setFiltersVisible(filtersVisible); 52 | 53 | /* If filters are not set visible, clear and hide filter panel */ 54 | if (filtersVisible == false) { 55 | filterTable.filters.clear(); 56 | } else { 57 | if (uidl.hasAttribute(ATTRIBUTE_COLUMN_HEADER_STYLE_NAMES)) { 58 | getWidget().setColumnHeaderStylenames(uidl.getMapAttribute(ATTRIBUTE_COLUMN_HEADER_STYLE_NAMES)); 59 | } 60 | /* Prepare and paint filter components */ 61 | Map newWidgets = new HashMap(); 62 | boolean allCached = true; 63 | for (final Iterator it = uidl.getChildIterator(); it.hasNext();) { 64 | final UIDL childUidl = (UIDL) it.next(); 65 | if (childUidl.getTag().startsWith(TAG_FILTER_COMPONENT)) { 66 | String cid = childUidl.getStringAttribute(ATTRIBUTE_COLUMN_ID); 67 | UIDL uidld = childUidl.getChildUIDL(0); 68 | if (uidld == null) { 69 | newWidgets.put(cid, null); 70 | } else { 71 | ComponentConnector connector = client.getPaintable(uidld); 72 | newWidgets.put(cid, connector.getWidget()); 73 | if (uidld.hasAttribute(ATTRIBUTE_CACHED) == false 74 | || uidld.getBooleanAttribute(ATTRIBUTE_CACHED) == false) { 75 | allCached = false; 76 | } 77 | } 78 | } 79 | } 80 | 81 | boolean forceRender = uidl.getBooleanAttribute(ATTRIBUTE_FORCE_RENDER); 82 | if (forceRender || !allCached || filterTable.filters.isEmpty()) { 83 | filterTable.filters.clear(); 84 | for (String cid : newWidgets.keySet()) { 85 | filterTable.filters.put(cid, newWidgets.get(cid)); 86 | } 87 | filterTable.reRenderFilterComponents(); 88 | } else { 89 | filterTable.resetFilterWidths(); 90 | } 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/client/ui/FilterTableConnector.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable.client.ui; 2 | 3 | import java.util.HashMap; 4 | import java.util.Iterator; 5 | import java.util.Map; 6 | 7 | import org.tepi.filtertable.FilterTable; 8 | 9 | import com.google.gwt.user.client.ui.Widget; 10 | import com.vaadin.client.ApplicationConnection; 11 | import com.vaadin.client.ComponentConnector; 12 | import com.vaadin.client.UIDL; 13 | import com.vaadin.client.ValueMap; 14 | import com.vaadin.shared.ui.Connect; 15 | import com.vaadin.v7.client.ui.table.TableConnector; 16 | 17 | /** 18 | * Class FilterTableConnector. 19 | * 20 | * @author Teppo Kurki 21 | * @since 27.10.2014 22 | */ 23 | @SuppressWarnings("serial") 24 | @Connect(FilterTable.class) 25 | public class FilterTableConnector extends TableConnector { 26 | 27 | public static final String TAG_FILTERS = "filters"; 28 | public static final String TAG_FILTER_COMPONENT = "filtercomponent-"; 29 | public static final String ATTRIBUTE_FILTERS_VISIBLE = "filtersvisible"; 30 | public static final String ATTRIBUTE_FORCE_RENDER = "forceRender"; 31 | public static final String ATTRIBUTE_COLUMN_ID = "columnid"; 32 | public static final String ATTRIBUTE_CACHED = "cached"; 33 | public static final String ATTRIBUTE_COLUMN_HEADER_STYLE_NAMES = "columnheaderstylenames"; 34 | 35 | @Override 36 | public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { 37 | super.updateFromUIDL(uidl, client); 38 | updateFiltersFromUIDL(uidl.getChildByTagName(FilterTableConnector.TAG_FILTERS), client); 39 | } 40 | 41 | @Override 42 | public VFilterTable getWidget() { 43 | return (VFilterTable) super.getWidget(); 44 | } 45 | 46 | @SuppressWarnings("deprecation") 47 | private void updateFiltersFromUIDL(UIDL uidl, ApplicationConnection client) { 48 | VFilterTable filterTable = getWidget(); 49 | 50 | boolean filtersVisible = uidl.hasAttribute(ATTRIBUTE_FILTERS_VISIBLE) 51 | ? uidl.getBooleanAttribute(ATTRIBUTE_FILTERS_VISIBLE) : false; 52 | filterTable.setFiltersVisible(filtersVisible); 53 | filterTable.updateHeight(); 54 | 55 | /* If filters are not set visible, clear and hide filter panel */ 56 | if (filtersVisible == false) { 57 | filterTable.filters.clear(); 58 | } else { 59 | if (uidl.hasAttribute(ATTRIBUTE_COLUMN_HEADER_STYLE_NAMES)) { 60 | getWidget().setColumnHeaderStylenames(uidl.getMapAttribute(ATTRIBUTE_COLUMN_HEADER_STYLE_NAMES)); 61 | } 62 | /* Prepare and paint filter components */ 63 | Map newWidgets = new HashMap(); 64 | boolean allCached = true; 65 | for (final Iterator it = uidl.getChildIterator(); it.hasNext();) { 66 | final UIDL childUidl = (UIDL) it.next(); 67 | if (childUidl.getTag().startsWith(TAG_FILTER_COMPONENT)) { 68 | String cid = childUidl.getStringAttribute(ATTRIBUTE_COLUMN_ID); 69 | UIDL uidld = childUidl.getChildUIDL(0); 70 | if (uidld == null) { 71 | newWidgets.put(cid, null); 72 | } else { 73 | ComponentConnector connector = client.getPaintable(uidld); 74 | newWidgets.put(cid, connector.getWidget()); 75 | if (uidld.hasAttribute(ATTRIBUTE_CACHED) == false 76 | || uidld.getBooleanAttribute(ATTRIBUTE_CACHED) == false) { 77 | allCached = false; 78 | } 79 | } 80 | } 81 | } 82 | 83 | boolean forceRender = uidl.getBooleanAttribute(ATTRIBUTE_FORCE_RENDER); 84 | if (forceRender || !allCached || filterTable.filters.isEmpty()) { 85 | filterTable.filters.clear(); 86 | for (String cid : newWidgets.keySet()) { 87 | filterTable.filters.put(cid, newWidgets.get(cid)); 88 | } 89 | filterTable.reRenderFilterComponents(); 90 | } else { 91 | filterTable.resetFilterWidths(); 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /filteringtable-demo/src/main/java/org/tepi/filtertable/demo/DemoFilterDecorator.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable.demo; 2 | 3 | import java.io.Serializable; 4 | import java.text.DateFormat; 5 | import java.util.Locale; 6 | 7 | import org.tepi.filtertable.FilterDecorator; 8 | import org.tepi.filtertable.demo.FilterTableDemoUI.State; 9 | import org.tepi.filtertable.numberfilter.NumberFilterPopupConfig; 10 | 11 | import com.vaadin.server.FontAwesome; 12 | import com.vaadin.server.Resource; 13 | import com.vaadin.v7.shared.ui.datefield.Resolution; 14 | 15 | @SuppressWarnings("serial") 16 | class DemoFilterDecorator implements FilterDecorator, Serializable { 17 | 18 | @Override 19 | public String getEnumFilterDisplayName(Object propertyId, Object value) { 20 | if ("state".equals(propertyId)) { 21 | State state = (State) value; 22 | switch (state) { 23 | case CREATED: 24 | return "Order has been created"; 25 | case PROCESSING: 26 | return "Order is being processed"; 27 | case PROCESSED: 28 | return "Order has been processed"; 29 | case FINISHED: 30 | return "Order is delivered"; 31 | } 32 | } 33 | // returning null will output default value 34 | return null; 35 | } 36 | 37 | @Override 38 | public Resource getEnumFilterIcon(Object propertyId, Object value) { 39 | if ("state".equals(propertyId)) { 40 | State state = (State) value; 41 | switch (state) { 42 | case CREATED: 43 | return FontAwesome.FILE; 44 | case PROCESSING: 45 | return FontAwesome.CLOCK_O; 46 | case PROCESSED: 47 | return FontAwesome.CHECK; 48 | case FINISHED: 49 | return FontAwesome.FLAG_CHECKERED; 50 | } 51 | } 52 | return null; 53 | } 54 | 55 | @Override 56 | public String getBooleanFilterDisplayName(Object propertyId, boolean value) { 57 | return "validated".equals(propertyId) ? (value ? "Validated" : "Not validated") : null; 58 | } 59 | 60 | @Override 61 | public Resource getBooleanFilterIcon(Object propertyId, boolean value) { 62 | return "validated".equals(propertyId) ? (value ? FontAwesome.CHECK : FontAwesome.TIMES) : null; 63 | } 64 | 65 | @Override 66 | public String getFromCaption() { 67 | return "Start date:"; 68 | } 69 | 70 | @Override 71 | public String getToCaption() { 72 | return "End date:"; 73 | } 74 | 75 | @Override 76 | public String getSetCaption() { 77 | // use default caption 78 | return null; 79 | } 80 | 81 | @Override 82 | public String getClearCaption() { 83 | // use default caption 84 | return null; 85 | } 86 | 87 | @Override 88 | public boolean isTextFilterImmediate(Object propertyId) { 89 | // use text change events for all the text fields 90 | return true; 91 | } 92 | 93 | @Override 94 | public int getTextChangeTimeout(Object propertyId) { 95 | // use the same timeout for all the text fields 96 | return 500; 97 | } 98 | 99 | @Override 100 | public String getAllItemsVisibleString() { 101 | return "Show all"; 102 | } 103 | 104 | @Override 105 | public Resolution getDateFieldResolution(Object propertyId) { 106 | return Resolution.DAY; 107 | } 108 | 109 | public DateFormat getDateFormat(Object propertyId) { 110 | return DateFormat.getDateInstance(DateFormat.SHORT, new Locale("fi", "FI")); 111 | } 112 | 113 | @Override 114 | public boolean usePopupForNumericProperty(Object propertyId) { 115 | return true; 116 | } 117 | 118 | @Override 119 | public String getDateFormatPattern(Object propertyId) { 120 | // TODO Will use the default settings 121 | return null; 122 | } 123 | 124 | @Override 125 | public Locale getLocale() { 126 | // TODO Will use the default settings 127 | return null; 128 | } 129 | 130 | @Override 131 | public NumberFilterPopupConfig getNumberFilterPopupConfig() { 132 | // TODO Will use the default settings 133 | return null; 134 | } 135 | 136 | @Override 137 | public String getNumberValidationErrorMessage() { 138 | // TODO Auto-generated method stub 139 | return null; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /filteringtable-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | org.tepi.filtertable 6 | filteringtable-demo 7 | war 8 | 1.0.1.v8 9 | FilteringTable Add-on Demo 10 | 11 | 12 | 3 13 | 14 | 15 | 16 | UTF-8 17 | 1.8 18 | 1.8 19 | 8.6.4 20 | 8.6.4 21 | 9.3.9.v20160517 22 | 23 | 24 | 25 | 26 | Apache 2 27 | http://www.apache.org/licenses/LICENSE-2.0.txt 28 | repo 29 | 30 | 31 | 32 | 33 | 34 | vaadin-addons 35 | http://maven.vaadin.com/vaadin-addons 36 | 37 | 38 | 39 | 40 | 41 | 42 | com.vaadin 43 | vaadin-bom 44 | ${vaadin.version} 45 | pom 46 | import 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.tepi.filtertable 54 | filteringtable 55 | ${project.version} 56 | 57 | 58 | com.vaadin 59 | vaadin-push 60 | 61 | 62 | com.vaadin 63 | vaadin-client-compiler 64 | provided 65 | 66 | 67 | com.vaadin 68 | vaadin-compatibility-client 69 | provided 70 | 71 | 72 | com.vaadin 73 | vaadin-themes 74 | 75 | 76 | javax.servlet 77 | javax.servlet-api 78 | 3.0.1 79 | provided 80 | 81 | 82 | 83 | 84 | 85 | 86 | maven-war-plugin 87 | 3.0.0 88 | 89 | false 90 | 91 | 92 | 93 | 94 | com.vaadin 95 | vaadin-maven-plugin 96 | ${vaadin.plugin.version} 97 | 98 | -Xmx1G 99 | 100 | 101 | 102 | 103 | resources 104 | update-widgetset 105 | compile 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | org.eclipse.jetty 115 | jetty-maven-plugin 116 | ${jetty.plugin.version} 117 | 118 | 2 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /filteringtable-addon/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | org.tepi.filtertable 6 | filteringtable 7 | jar 8 | 1.0.1.v8 9 | FilteringTable 10 | 11 | 12 | 3 13 | 14 | 15 | 16 | UTF-8 17 | 1.8 18 | 1.8 19 | 8.6.4 20 | 8.6.4 21 | 22 | 23 | ${project.version} 24 | 25 | ${project.name} 26 | ${project.organization.name} 27 | Apache License 2.0 28 | ${project.artifactId}-${project.version}.jar 29 | 30 | 31 | 32 | 33 | Apache 2 34 | http://www.apache.org/licenses/LICENSE-2.0.txt 35 | repo 36 | 37 | 38 | 39 | 40 | 41 | vaadin-addons 42 | http://maven.vaadin.com/vaadin-addons 43 | 44 | 45 | 46 | 47 | 48 | com.vaadin 49 | vaadin-compatibility-server 50 | ${vaadin.version} 51 | 52 | 53 | com.vaadin 54 | vaadin-compatibility-client 55 | ${vaadin.version} 56 | provided 57 | 58 | 59 | org.vaadin.addons 60 | popupbutton 61 | 3.0.0 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-jar-plugin 70 | 2.6 71 | 72 | 73 | true 74 | 75 | true 76 | true 77 | 78 | 79 | 80 | 1 81 | ${Vaadin-License-Title} 82 | org.tepi.filtertable.WidgetSet 83 | 84 | 85 | 86 | 87 | 88 | 89 | org.apache.maven.plugins 90 | maven-javadoc-plugin 91 | 2.10.3 92 | 93 | 94 | attach-javadoc 95 | 96 | jar 97 | 98 | 99 | 100 | 101 | 102 | 103 | org.apache.maven.plugins 104 | maven-source-plugin 105 | 3.0.0 106 | 107 | 108 | attach-sources 109 | 110 | jar 111 | 112 | 113 | 114 | 115 | 116 | 117 | org.apache.maven.plugins 118 | maven-assembly-plugin 119 | 120 | false 121 | 122 | assembly/assembly.xml 123 | 124 | 125 | 126 | 127 | 128 | single 129 | 130 | install 131 | 132 | 133 | 134 | 135 | 136 | 138 | 139 | 140 | src/main/java 141 | 142 | rebel.xml 143 | 144 | 145 | 146 | src/main/resources 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/FilterDecorator.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable; 2 | 3 | import java.io.Serializable; 4 | import java.util.Locale; 5 | 6 | import org.tepi.filtertable.numberfilter.NumberFilterPopupConfig; 7 | 8 | import com.vaadin.server.Resource; 9 | import com.vaadin.v7.shared.ui.datefield.Resolution; 10 | import com.vaadin.v7.ui.AbstractTextField.TextChangeEventMode; 11 | import com.vaadin.v7.ui.DateField; 12 | 13 | /** 14 | * Interface for decorating the UI of the filter components contained in 15 | * FilterTable. Implement this interface to provide proper display names and 16 | * icons for enums and booleans shown in the filter components. 17 | * 18 | * @author Teppo Kurki 19 | * 20 | */ 21 | @SuppressWarnings({ "deprecation" }) 22 | public interface FilterDecorator extends Serializable { 23 | 24 | /** 25 | * Returns the filter display name for the given enum value when filtering 26 | * the given property id. 27 | * 28 | * @param propertyId 29 | * ID of the property the filter is attached to. 30 | * @param value 31 | * Value of the enum the display name is requested for. 32 | * @return UI Display name for the enum value. 33 | */ 34 | public String getEnumFilterDisplayName(Object propertyId, Object value); 35 | 36 | /** 37 | * Returns the filter icon for the given enum value when filtering the given 38 | * property id. 39 | * 40 | * @param propertyId 41 | * ID of the property the filter is attached to. 42 | * @param value 43 | * Value of the enum the icon is requested for. 44 | * @return Resource for the icon of the enum value. 45 | */ 46 | public Resource getEnumFilterIcon(Object propertyId, Object value); 47 | 48 | /** 49 | * Returns the filter display name for the given boolean value when 50 | * filtering the given property id. 51 | * 52 | * @param propertyId 53 | * ID of the property the filter is attached to. 54 | * @param value 55 | * Value of boolean the display name is requested for. 56 | * @return UI Display name for the given boolean value. 57 | */ 58 | public String getBooleanFilterDisplayName(Object propertyId, boolean value); 59 | 60 | /** 61 | * Returns the filter icon for the given boolean value when filtering the 62 | * given property id. 63 | * 64 | * @param propertyId 65 | * ID of the property the filter is attached to. 66 | * @param value 67 | * Value of boolean the icon is requested for. 68 | * @return Resource for the icon of the given boolean value. 69 | */ 70 | public Resource getBooleanFilterIcon(Object propertyId, boolean value); 71 | 72 | /** 73 | * Returns whether the text filter should update as the user types. This 74 | * uses {@link TextChangeEventMode#LAZY} 75 | * 76 | * @return true if the text field should use a TextChangeListener. 77 | */ 78 | public boolean isTextFilterImmediate(Object propertyId); 79 | 80 | /** 81 | * The text change timeout dictates how often text change events are 82 | * communicated to the application, and thus how often are the filter values 83 | * updated. 84 | * 85 | * @return the timeout in milliseconds 86 | */ 87 | public int getTextChangeTimeout(Object propertyId); 88 | 89 | /** 90 | * Return display caption for the From field 91 | * 92 | * @return caption for From field 93 | */ 94 | public String getFromCaption(); 95 | 96 | /** 97 | * Return display caption for the To field 98 | * 99 | * @return caption for To field 100 | */ 101 | public String getToCaption(); 102 | 103 | /** 104 | * Return display caption for the Set button 105 | * 106 | * @return caption for Set button 107 | */ 108 | public String getSetCaption(); 109 | 110 | /** 111 | * Return display caption for the Clear button 112 | * 113 | * @return caption for Clear button 114 | */ 115 | public String getClearCaption(); 116 | 117 | /** 118 | * Return DateField resolution for the Date filtering of the property ID. 119 | * This will only be called for Date -typed properties. Filtering values 120 | * output by the FilteringTable will also be truncated to this resolution. 121 | * 122 | * @param propertyId 123 | * ID of the property the resolution will be applied to 124 | * @return A resolution defined in {@link DateField} 125 | */ 126 | public Resolution getDateFieldResolution(Object propertyId); 127 | 128 | /** 129 | * Returns a date format pattern to be used for formatting the date/time 130 | * values shown in the filtering field of the given property ID. Note that 131 | * this is completely independent from the resolution set for the property, 132 | * and is used for display purposes only. 133 | * 134 | * See SimpleDateFormat for the pattern definition 135 | * 136 | * @param propertyId 137 | * ID of the property the format will be applied to 138 | * @return A date format pattern or null to use the default formatting 139 | */ 140 | public String getDateFormatPattern(Object propertyId); 141 | 142 | /** 143 | * Returns the locale to be used with Date filters. If none is provided, 144 | * reverts to default locale of the system. 145 | * 146 | * @return Desired locale for the dates 147 | */ 148 | public Locale getLocale(); 149 | 150 | /** 151 | * Return the string that should be used as an "input prompt" when no 152 | * filtering is made on a filter component. 153 | * 154 | * @return String to show for no filter defined 155 | */ 156 | public String getAllItemsVisibleString(); 157 | 158 | /** 159 | * Return configuration for the numeric filter field popup 160 | * 161 | * @return Configuration for numeric filter 162 | */ 163 | public NumberFilterPopupConfig getNumberFilterPopupConfig(); 164 | 165 | /** 166 | * Defines whether a popup-style numeric filter should be used for the 167 | * property with the given ID. 168 | * 169 | * The types Integer, Long, Float and Double are considered to be 'numeric' 170 | * within this context. 171 | * 172 | * @param propertyId 173 | * ID of the property the popup will be applied to 174 | * @return true to use popup-style, false to use a TextField 175 | */ 176 | public boolean usePopupForNumericProperty(Object propertyId); 177 | 178 | /** 179 | * Returns the error message that is shown when an invalid number is entered 180 | * into any of the fields in a number filter popup. 181 | * 182 | * @return Error message 183 | */ 184 | public String getNumberValidationErrorMessage(); 185 | } 186 | -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/paged/PagedFilterTableContainer.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable.paged; 2 | 3 | import java.util.Collection; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import com.vaadin.v7.data.Container; 8 | import com.vaadin.v7.data.Item; 9 | import com.vaadin.v7.data.Property; 10 | import com.vaadin.v7.data.util.filter.UnsupportedFilterException; 11 | 12 | @SuppressWarnings({ "deprecation" }) 13 | public class PagedFilterTableContainer 14 | implements Container, Container.Indexed, Container.Sortable, Container.Filterable, 15 | Container.ItemSetChangeNotifier { 16 | private static final long serialVersionUID = -2134233618583099046L; 17 | 18 | private final T container; 19 | private int pageLength = 25; 20 | private int startIndex = 0; 21 | 22 | public PagedFilterTableContainer(T container) { 23 | this.container = container; 24 | } 25 | 26 | public T getContainer() { 27 | return container; 28 | } 29 | 30 | public int getPageLength() { 31 | return pageLength; 32 | } 33 | 34 | public void setPageLength(int pageLength) { 35 | this.pageLength = pageLength; 36 | } 37 | 38 | public int getStartIndex() { 39 | return startIndex; 40 | } 41 | 42 | public void setStartIndex(int startIndex) { 43 | if (startIndex < 0) { 44 | this.startIndex = 0; 45 | } else { 46 | this.startIndex = startIndex; 47 | } 48 | } 49 | 50 | /* 51 | * Overridden methods from the real container from here forward 52 | */ 53 | 54 | @Override 55 | public int size() { 56 | int rowsLeft = container.size() - startIndex; 57 | if (rowsLeft > pageLength) { 58 | return pageLength; 59 | } else { 60 | return rowsLeft; 61 | } 62 | } 63 | 64 | public int getRealSize() { 65 | return container.size(); 66 | } 67 | 68 | @Override 69 | public Object getIdByIndex(int index) { 70 | return container.getIdByIndex(index + startIndex); 71 | } 72 | 73 | /* 74 | * Delegate methods to real container from here on 75 | */ 76 | 77 | @Override 78 | public Item getItem(Object itemId) { 79 | return container.getItem(itemId); 80 | } 81 | 82 | @Override 83 | public Collection getContainerPropertyIds() { 84 | return container.getContainerPropertyIds(); 85 | } 86 | 87 | @Override 88 | public Collection getItemIds() { 89 | return container.getItemIds(); 90 | } 91 | 92 | @Override 93 | public List getItemIds(int startIndex, int numberOfItems) { 94 | return container.getItemIds(this.startIndex + startIndex, numberOfItems); 95 | } 96 | 97 | @Override 98 | public Property getContainerProperty(Object itemId, Object propertyId) { 99 | return container.getContainerProperty(itemId, propertyId); 100 | } 101 | 102 | @Override 103 | public Class getType(Object propertyId) { 104 | return container.getType(propertyId); 105 | } 106 | 107 | @Override 108 | public boolean containsId(Object itemId) { 109 | return container.containsId(itemId); 110 | } 111 | 112 | @Override 113 | public Item addItem(Object itemId) throws UnsupportedOperationException { 114 | return container.addItem(itemId); 115 | } 116 | 117 | @Override 118 | public Object addItem() throws UnsupportedOperationException { 119 | return container.addItem(); 120 | } 121 | 122 | @Override 123 | public boolean removeItem(Object itemId) throws UnsupportedOperationException { 124 | return container.removeItem(itemId); 125 | } 126 | 127 | @Override 128 | public boolean addContainerProperty(Object propertyId, Class type, Object defaultValue) 129 | throws UnsupportedOperationException { 130 | return container.addContainerProperty(propertyId, type, defaultValue); 131 | } 132 | 133 | @Override 134 | public boolean removeContainerProperty(Object propertyId) throws UnsupportedOperationException { 135 | return container.removeContainerProperty(propertyId); 136 | } 137 | 138 | @Override 139 | public boolean removeAllItems() throws UnsupportedOperationException { 140 | return container.removeAllItems(); 141 | } 142 | 143 | @Override 144 | public Object nextItemId(Object itemId) { 145 | return container.nextItemId(itemId); 146 | } 147 | 148 | @Override 149 | public Object prevItemId(Object itemId) { 150 | return container.prevItemId(itemId); 151 | } 152 | 153 | @Override 154 | public Object firstItemId() { 155 | return container.firstItemId(); 156 | } 157 | 158 | @Override 159 | public Object lastItemId() { 160 | return container.lastItemId(); 161 | } 162 | 163 | @Override 164 | public boolean isFirstId(Object itemId) { 165 | return container.isFirstId(itemId); 166 | } 167 | 168 | @Override 169 | public boolean isLastId(Object itemId) { 170 | return container.isLastId(itemId); 171 | } 172 | 173 | @Override 174 | public Object addItemAfter(Object previousItemId) throws UnsupportedOperationException { 175 | return container.addItemAfter(previousItemId); 176 | } 177 | 178 | @Override 179 | public Item addItemAfter(Object previousItemId, Object newItemId) throws UnsupportedOperationException { 180 | return container.addItemAfter(previousItemId, newItemId); 181 | } 182 | 183 | @Override 184 | public int indexOfId(Object itemId) { 185 | return container.indexOfId(itemId); 186 | } 187 | 188 | @Override 189 | public Object addItemAt(int index) throws UnsupportedOperationException { 190 | return container.addItemAt(index); 191 | } 192 | 193 | @Override 194 | public Item addItemAt(int index, Object newItemId) throws UnsupportedOperationException { 195 | return container.addItemAt(index, newItemId); 196 | } 197 | 198 | /* 199 | * Sorting interface from here on 200 | */ 201 | 202 | @Override 203 | public void sort(Object[] propertyId, boolean[] ascending) { 204 | if (container instanceof Container.Sortable) { 205 | ((Container.Sortable) container).sort(propertyId, ascending); 206 | } 207 | } 208 | 209 | @Override 210 | public Collection getSortableContainerPropertyIds() { 211 | if (container instanceof Container.Sortable) { 212 | return ((Container.Sortable) container).getSortableContainerPropertyIds(); 213 | } 214 | return Collections.EMPTY_LIST; 215 | } 216 | 217 | @Override 218 | public void addContainerFilter(Filter filter) throws UnsupportedFilterException { 219 | container.addContainerFilter(filter); 220 | } 221 | 222 | @Override 223 | public Collection getContainerFilters() { 224 | return container.getContainerFilters(); 225 | } 226 | 227 | @Override 228 | public void removeContainerFilter(Filter filter) { 229 | container.removeContainerFilter(filter); 230 | } 231 | 232 | @Override 233 | public void removeAllContainerFilters() { 234 | container.removeAllContainerFilters(); 235 | } 236 | 237 | @Override 238 | public void addItemSetChangeListener(ItemSetChangeListener listener) { 239 | ((Container.ItemSetChangeNotifier) container).addItemSetChangeListener(listener); 240 | } 241 | 242 | @Override 243 | public void removeItemSetChangeListener(ItemSetChangeListener listener) { 244 | ((Container.ItemSetChangeNotifier) container).removeItemSetChangeListener(listener); 245 | } 246 | 247 | @Override 248 | public void addListener(ItemSetChangeListener listener) { 249 | addItemSetChangeListener(listener); 250 | } 251 | 252 | @Override 253 | public void removeListener(ItemSetChangeListener listener) { 254 | removeItemSetChangeListener(listener); 255 | } 256 | } -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/datefilter/DateFilterPopup.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable.datefilter; 2 | 3 | import java.text.DateFormat; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Calendar; 6 | import java.util.Date; 7 | import java.util.Locale; 8 | 9 | import org.tepi.filtertable.FilterDecorator; 10 | import org.vaadin.hene.popupbutton.PopupButton; 11 | 12 | import com.vaadin.ui.Alignment; 13 | import com.vaadin.ui.Button; 14 | import com.vaadin.ui.Button.ClickListener; 15 | import com.vaadin.ui.Component; 16 | import com.vaadin.ui.HorizontalLayout; 17 | import com.vaadin.ui.VerticalLayout; 18 | import com.vaadin.v7.data.util.converter.Converter.ConversionException; 19 | import com.vaadin.v7.shared.ui.datefield.Resolution; 20 | import com.vaadin.v7.ui.CustomField; 21 | import com.vaadin.v7.ui.DateField; 22 | import com.vaadin.v7.ui.InlineDateField; 23 | 24 | /** 25 | * Extension of PopupButton used to implement filter UI for Date properties. 26 | * Users can select either start date, end date or both. The filter can also be 27 | * set or cleared via a button in the filter pop-up. 28 | * 29 | * @author Teppo Kurki 30 | * 31 | */ 32 | @SuppressWarnings({ "serial", "deprecation" }) 33 | public class DateFilterPopup extends CustomField { 34 | protected PopupButton content; 35 | 36 | protected DateField fromField, toField; 37 | private Date fromValue, toValue; 38 | private boolean cancelReset; 39 | private FilterDecorator decorator; 40 | protected Button set, clear; 41 | private final Object propertyId; 42 | private String dateFormatPattern; 43 | 44 | private static final String DEFAULT_FROM_CAPTION = "From"; 45 | private static final String DEFAULT_TO_CAPTION = "To"; 46 | private static final String DEFAULT_SET_CAPTION = "Set"; 47 | private static final String DEFAULT_CLEAR_CAPTION = "Clear"; 48 | private static final Resolution DEFAULT_RESOLUTION = Resolution.DAY; 49 | 50 | public DateFilterPopup(FilterDecorator decorator, Object propertyId) { 51 | this.decorator = decorator; 52 | this.propertyId = propertyId; 53 | /* 54 | * This call is needed for the value setting to function before attach 55 | */ 56 | getContent(); 57 | } 58 | 59 | @Override 60 | public void attach() { 61 | super.attach(); 62 | setFilterDecorator(decorator); 63 | } 64 | 65 | @Override 66 | public void setValue(DateInterval newFieldValue) 67 | throws com.vaadin.v7.data.Property.ReadOnlyException, ConversionException { 68 | if (newFieldValue == null) { 69 | newFieldValue = new DateInterval(null, null); 70 | } 71 | fromField.setValue(newFieldValue.getFrom()); 72 | toField.setValue(newFieldValue.getTo()); 73 | super.setValue(newFieldValue); 74 | updateCaption(newFieldValue.isNull()); 75 | } 76 | 77 | protected void buildPopup() { 78 | VerticalLayout content = new VerticalLayout(); 79 | content.setStyleName("datefilterpopupcontent"); 80 | content.setSpacing(true); 81 | content.setMargin(true); 82 | content.setSizeUndefined(); 83 | 84 | fromField = new InlineDateField(); 85 | toField = new InlineDateField(); 86 | fromField.setImmediate(true); 87 | toField.setImmediate(true); 88 | fromField.addValueChangeListener(e -> { 89 | if (e.getProperty().getValue() != null) { 90 | toField.setRangeStart((Date) e.getProperty().getValue()); 91 | } else { 92 | toField.setRangeStart(null); 93 | } 94 | }); 95 | toField.addValueChangeListener(e -> { 96 | if (e.getProperty().getValue() != null) { 97 | fromField.setRangeEnd((Date) e.getProperty().getValue()); 98 | } else { 99 | fromField.setRangeEnd(null); 100 | } 101 | }); 102 | 103 | set = new Button(); 104 | clear = new Button(); 105 | ClickListener buttonClickHandler = event -> updateValue(clear.equals(event.getButton())); 106 | set.addClickListener(buttonClickHandler); 107 | clear.addClickListener(buttonClickHandler); 108 | 109 | HorizontalLayout buttonBar = new HorizontalLayout(); 110 | buttonBar.setSizeUndefined(); 111 | buttonBar.setSpacing(true); 112 | buttonBar.addComponent(set); 113 | buttonBar.addComponent(clear); 114 | 115 | HorizontalLayout row = new HorizontalLayout(); 116 | row.setSizeUndefined(); 117 | row.setSpacing(true); 118 | row.addComponent(fromField); 119 | row.addComponent(toField); 120 | 121 | content.addComponent(row); 122 | content.addComponent(buttonBar); 123 | content.setComponentAlignment(buttonBar, Alignment.BOTTOM_RIGHT); 124 | 125 | this.content.setContent(content); 126 | } 127 | 128 | public void setFilterDecorator(FilterDecorator decorator) { 129 | this.decorator = decorator; 130 | 131 | /* Set DateField Locale */ 132 | fromField.setLocale(getLocaleFailsafe()); 133 | toField.setLocale(getLocaleFailsafe()); 134 | 135 | String fromCaption = DEFAULT_FROM_CAPTION; 136 | String toCaption = DEFAULT_TO_CAPTION; 137 | String setCaption = DEFAULT_SET_CAPTION; 138 | String clearCaption = DEFAULT_CLEAR_CAPTION; 139 | Resolution resolution = DEFAULT_RESOLUTION; 140 | dateFormatPattern = ((SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, 141 | getLocaleFailsafe())).toPattern(); 142 | 143 | if (decorator != null) { 144 | if (decorator.getFromCaption() != null) { 145 | fromCaption = decorator.getFromCaption(); 146 | } 147 | if (decorator.getToCaption() != null) { 148 | toCaption = decorator.getToCaption(); 149 | } 150 | if (decorator.getSetCaption() != null) { 151 | setCaption = decorator.getSetCaption(); 152 | } 153 | if (decorator.getClearCaption() != null) { 154 | clearCaption = decorator.getClearCaption(); 155 | } 156 | if (decorator.getDateFieldResolution(propertyId) != null) { 157 | resolution = decorator.getDateFieldResolution(propertyId); 158 | } 159 | String dateFormatPattern = decorator.getDateFormatPattern(propertyId); 160 | if (dateFormatPattern != null) { 161 | this.dateFormatPattern = dateFormatPattern; 162 | } 163 | } 164 | /* Set captions */ 165 | fromField.setCaption(fromCaption); 166 | toField.setCaption(toCaption); 167 | set.setCaption(setCaption); 168 | clear.setCaption(clearCaption); 169 | /* Set resolutions and date formats */ 170 | fromField.setResolution(resolution); 171 | toField.setResolution(resolution); 172 | fromField.setDateFormat(dateFormatPattern); 173 | toField.setDateFormat(dateFormatPattern); 174 | } 175 | 176 | private void updateCaption(boolean nullTheCaption) { 177 | if (nullTheCaption) { 178 | if (decorator != null && decorator.getAllItemsVisibleString() != null) { 179 | content.setCaption(decorator.getAllItemsVisibleString()); 180 | } else { 181 | content.setCaption(null); 182 | } 183 | } else { 184 | SimpleDateFormat sdf = new SimpleDateFormat(dateFormatPattern); 185 | content.setCaption((fromField.getValue() == null ? "" : sdf.format(fromField.getValue())) + " - " 186 | + (toField.getValue() == null ? "" : sdf.format(toField.getValue()))); 187 | } 188 | } 189 | 190 | private void updateValue(boolean nullTheValue) { 191 | if (nullTheValue) { 192 | fromField.setValue(null); 193 | toField.setValue(null); 194 | } else { 195 | cancelReset = true; 196 | } 197 | /* Truncate the from and to dates */ 198 | Resolution res = decorator != null ? decorator.getDateFieldResolution(propertyId) : DEFAULT_RESOLUTION; 199 | if (res == null) { 200 | res = DEFAULT_RESOLUTION; 201 | } 202 | fromValue = truncateDate(fromField.getValue(), res, true); 203 | toValue = truncateDate(toField.getValue(), res, false); 204 | setValue(new DateInterval(fromValue, toValue)); 205 | DateFilterPopup.this.content.setPopupVisible(false); 206 | } 207 | 208 | private Date truncateDate(Date date, Resolution resolution, boolean start) { 209 | if (date == null) { 210 | return null; 211 | } 212 | Calendar cal = Calendar.getInstance(getLocaleFailsafe()); 213 | cal.setTime(date); 214 | cal.set(Calendar.MILLISECOND, start ? 0 : 999); 215 | for (Resolution res : Resolution.getResolutionsLowerThan(resolution)) { 216 | if (res == Resolution.SECOND) { 217 | cal.set(Calendar.SECOND, start ? 0 : 59); 218 | } else if (res == Resolution.MINUTE) { 219 | cal.set(Calendar.MINUTE, start ? 0 : 59); 220 | } else if (res == Resolution.HOUR) { 221 | cal.set(Calendar.HOUR_OF_DAY, start ? 0 : 23); 222 | } else if (res == Resolution.DAY) { 223 | cal.set(Calendar.DAY_OF_MONTH, start ? 1 : cal.getActualMaximum(Calendar.DAY_OF_MONTH)); 224 | } else if (res == Resolution.MONTH) { 225 | cal.set(Calendar.MONTH, start ? 0 : cal.getActualMaximum(Calendar.MONTH)); 226 | } 227 | } 228 | return cal.getTime(); 229 | } 230 | 231 | private Locale getLocaleFailsafe() { 232 | /* First try the locale provided by the decorator */ 233 | if (decorator != null && decorator.getLocale() != null) { 234 | return decorator.getLocale(); 235 | } 236 | /* Then try application locale */ 237 | if (super.getLocale() != null) { 238 | return super.getLocale(); 239 | } 240 | /* Finally revert to system default locale */ 241 | return Locale.getDefault(); 242 | } 243 | 244 | @Override 245 | protected Component initContent() { 246 | if (content == null) { 247 | content = new PopupButton(null); 248 | content.setWidth(100, Unit.PERCENTAGE); 249 | setImmediate(true); 250 | buildPopup(); 251 | setStyleName("datefilterpopup"); 252 | setFilterDecorator(decorator); 253 | updateCaption(true); 254 | content.addPopupVisibilityListener(event -> { 255 | if (cancelReset || event.getPopupButton().isPopupVisible()) { 256 | fromValue = fromField.getValue(); 257 | toValue = toField.getValue(); 258 | cancelReset = false; 259 | return; 260 | } 261 | fromField.setValue(fromValue); 262 | toField.setValue(toValue); 263 | cancelReset = false; 264 | }); 265 | } 266 | return content; 267 | } 268 | 269 | @Override 270 | public Class getType() { 271 | return DateInterval.class; 272 | } 273 | 274 | @Override 275 | public void setReadOnly(boolean readOnly) { 276 | super.setReadOnly(readOnly); 277 | set.setEnabled(!readOnly); 278 | clear.setEnabled(!readOnly); 279 | fromField.setEnabled(!readOnly); 280 | toField.setEnabled(!readOnly); 281 | } 282 | } -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/numberfilter/NumberFilterPopup.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable.numberfilter; 2 | 3 | import java.math.BigInteger; 4 | 5 | import org.tepi.filtertable.FilterDecorator; 6 | import org.vaadin.hene.popupbutton.PopupButton; 7 | 8 | import com.vaadin.server.UserError; 9 | import com.vaadin.shared.ui.ValueChangeMode; 10 | import com.vaadin.ui.Alignment; 11 | import com.vaadin.ui.Button; 12 | import com.vaadin.ui.Button.ClickListener; 13 | import com.vaadin.ui.Component; 14 | import com.vaadin.ui.GridLayout; 15 | import com.vaadin.ui.HorizontalLayout; 16 | import com.vaadin.ui.Label; 17 | import com.vaadin.ui.TextField; 18 | import com.vaadin.v7.data.util.converter.Converter.ConversionException; 19 | import com.vaadin.v7.ui.CustomField; 20 | 21 | /** 22 | * Produces the number filter popup for the table 23 | * 24 | * @author Vimukthi 25 | * @author Teppo Kurki [adapted for V8] 26 | */ 27 | @SuppressWarnings({ "serial", "deprecation" }) 28 | public class NumberFilterPopup extends CustomField { 29 | 30 | private PopupButton content; 31 | private FilterDecorator decorator; 32 | private boolean settingValue; 33 | private String valueMarker; 34 | private NumberInterval interval; 35 | private boolean allowDecimalPlaces; 36 | 37 | /* Input fields */ 38 | private TextField ltInput; 39 | private TextField gtInput; 40 | private TextField eqInput; 41 | 42 | /* Default labels */ 43 | private static final String LT = "<"; 44 | private static final String GT = ">"; 45 | private static final String EQ = "="; 46 | private static final String DEFAULT_LT_PROMPT = "Less than"; 47 | private static final String DEFAULT_GT_PROMPT = "Greater than"; 48 | private static final String DEFAULT_EQ_PROMPT = "Equal to"; 49 | private static final String DEFAULT_OK_CAPTION = "Set"; 50 | private static final String DEFAULT_RESET_CAPTION = "Clear"; 51 | private static final String DEFAULT_VALUE_MARKER = "[x]"; 52 | 53 | /* Buttons */ 54 | private Button ok; 55 | private Button reset; 56 | 57 | public NumberFilterPopup(FilterDecorator decorator) { 58 | this.decorator = decorator; 59 | /* 60 | * This call is needed for the value setting to function before attach 61 | */ 62 | getContent(); 63 | } 64 | 65 | private void initPopup() { 66 | final GridLayout content = new GridLayout(2, 4); 67 | content.setStyleName("numberfilterpopupcontent"); 68 | content.setSpacing(true); 69 | content.setMargin(true); 70 | content.setSizeUndefined(); 71 | 72 | Label label = new Label(GT); 73 | content.addComponent(label, 0, 0); 74 | content.setComponentAlignment(label, Alignment.MIDDLE_RIGHT); 75 | label = new Label(LT); 76 | content.addComponent(label, 0, 1); 77 | content.setComponentAlignment(label, Alignment.MIDDLE_RIGHT); 78 | label = new Label(EQ); 79 | content.addComponent(label, 0, 2); 80 | content.setComponentAlignment(label, Alignment.MIDDLE_RIGHT); 81 | 82 | // greater than input field 83 | gtInput = new TextField(); 84 | content.addComponent(gtInput, 1, 0); 85 | 86 | // less than input field 87 | ltInput = new TextField(); 88 | content.addComponent(ltInput, 1, 1); 89 | 90 | // equals input field 91 | eqInput = new TextField(); 92 | content.addComponent(eqInput, 1, 2); 93 | 94 | // disable gt and lt fields when this activates 95 | eqInput.addValueChangeListener(e -> { 96 | if (e.getValue().equals("")) { 97 | gtInput.setEnabled(true); 98 | ltInput.setEnabled(true); 99 | } else { 100 | gtInput.setEnabled(false); 101 | ltInput.setEnabled(false); 102 | } 103 | }); 104 | eqInput.setValueChangeMode(ValueChangeMode.EAGER); 105 | 106 | ok = new Button(DEFAULT_OK_CAPTION, (ClickListener) event -> { 107 | String errorMsg = "Please enter a valid number"; 108 | if (decorator != null && decorator.getNumberValidationErrorMessage() != null) { 109 | errorMsg = decorator.getNumberValidationErrorMessage(); 110 | } 111 | 112 | String ltNow = ""; 113 | String gtNow = ""; 114 | String eqNow = ""; 115 | boolean valid = true; 116 | try { 117 | ltNow = parseInput(ltInput); 118 | } catch (NumberFormatException e1) { 119 | ltInput.setComponentError(new UserError(errorMsg)); 120 | valid = false; 121 | } 122 | try { 123 | gtNow = parseInput(gtInput); 124 | } catch (NumberFormatException e2) { 125 | gtInput.setComponentError(new UserError(errorMsg)); 126 | valid = false; 127 | } 128 | try { 129 | eqNow = parseInput(eqInput); 130 | } catch (NumberFormatException e3) { 131 | eqInput.setComponentError(new UserError(errorMsg)); 132 | valid = false; 133 | } 134 | if (!valid) { 135 | return; 136 | } 137 | setValue(new NumberInterval(ltNow, gtNow, eqNow)); 138 | NumberFilterPopup.this.content.setPopupVisible(false); 139 | }); 140 | 141 | reset = new Button(DEFAULT_RESET_CAPTION, (ClickListener) event -> { 142 | setValue(null); 143 | NumberFilterPopup.this.content.setPopupVisible(false); 144 | }); 145 | 146 | HorizontalLayout buttons = new HorizontalLayout(); 147 | buttons.setWidth("100%"); 148 | buttons.setSpacing(true); 149 | buttons.addComponent(ok); 150 | buttons.addComponent(reset); 151 | buttons.setExpandRatio(ok, 1); 152 | buttons.setComponentAlignment(ok, Alignment.MIDDLE_RIGHT); 153 | content.addComponent(buttons, 0, 3, 1, 3); 154 | 155 | this.content.setContent(content); 156 | } 157 | 158 | private String parseInput(TextField input) throws NumberFormatException { 159 | String value = input.getValue(); 160 | if (value == null || value.trim().isEmpty()) { 161 | return ""; 162 | } else { 163 | if (allowDecimalPlaces) { 164 | Double.valueOf(value); 165 | } else { 166 | new BigInteger(value); 167 | } 168 | return value; 169 | } 170 | } 171 | 172 | @Override 173 | public void setValue(NumberInterval newFieldValue) 174 | throws com.vaadin.v7.data.Property.ReadOnlyException, ConversionException { 175 | settingValue = true; 176 | boolean nullValue = false; 177 | if (newFieldValue == null || (newFieldValue.getEqualsValue() == null 178 | && newFieldValue.getGreaterThanValue() == null && newFieldValue.getLessThanValue() == null)) { 179 | nullValue = true; 180 | newFieldValue = null; 181 | gtInput.setEnabled(true); 182 | ltInput.setEnabled(true); 183 | } 184 | interval = newFieldValue; 185 | ltInput.setValue(nullValue ? "" : newFieldValue.getLessThanValue()); 186 | gtInput.setValue(nullValue ? "" : newFieldValue.getGreaterThanValue()); 187 | eqInput.setValue(nullValue ? "" : newFieldValue.getEqualsValue()); 188 | ltInput.setComponentError(null); 189 | gtInput.setComponentError(null); 190 | eqInput.setComponentError(null); 191 | super.setValue(newFieldValue); 192 | updateCaption(); 193 | settingValue = false; 194 | } 195 | 196 | private void updateCaption() { 197 | if (interval == null) { 198 | content.setCaption(decorator != null && decorator.getAllItemsVisibleString() != null 199 | ? decorator.getAllItemsVisibleString() 200 | : ""); 201 | } else { 202 | if (!interval.getEqualsValue().isEmpty()) { 203 | content.setCaption(valueMarker + " = " + interval.getEqualsValue()); 204 | } else if (!interval.getGreaterThanValue().isEmpty() && !interval.getLessThanValue().isEmpty()) { 205 | content.setCaption( 206 | interval.getGreaterThanValue() + " < " + valueMarker + " < " + interval.getLessThanValue()); 207 | } else if (!interval.getGreaterThanValue().isEmpty()) { 208 | content.setCaption(valueMarker + " > " + interval.getGreaterThanValue()); 209 | } else if (!interval.getLessThanValue().isEmpty()) { 210 | content.setCaption(valueMarker + " < " + interval.getLessThanValue()); 211 | } 212 | } 213 | } 214 | 215 | @Override 216 | public void attach() { 217 | super.attach(); 218 | setFilterDecorator(decorator); 219 | } 220 | 221 | public void setFilterDecorator(FilterDecorator decorator) { 222 | this.decorator = decorator; 223 | 224 | String eqP = DEFAULT_EQ_PROMPT; 225 | String ltP = DEFAULT_LT_PROMPT; 226 | String gtP = DEFAULT_GT_PROMPT; 227 | valueMarker = DEFAULT_VALUE_MARKER; 228 | String ok = DEFAULT_OK_CAPTION; 229 | String reset = DEFAULT_RESET_CAPTION; 230 | 231 | if (decorator != null && decorator.getNumberFilterPopupConfig() != null) { 232 | NumberFilterPopupConfig conf = decorator.getNumberFilterPopupConfig(); 233 | if (conf.getEqPrompt() != null) { 234 | eqP = conf.getEqPrompt(); 235 | } 236 | if (conf.getLtPrompt() != null) { 237 | ltP = conf.getLtPrompt(); 238 | } 239 | if (conf.getGtPrompt() != null) { 240 | gtP = conf.getGtPrompt(); 241 | } 242 | if (conf.getValueMarker() != null) { 243 | valueMarker = conf.getValueMarker(); 244 | } 245 | if (conf.getOkCaption() != null) { 246 | ok = conf.getOkCaption(); 247 | } 248 | if (conf.getResetCaption() != null) { 249 | reset = conf.getResetCaption(); 250 | } 251 | } 252 | 253 | gtInput.setPlaceholder(gtP); 254 | ltInput.setPlaceholder(ltP); 255 | eqInput.setPlaceholder(eqP); 256 | this.ok.setCaption(ok); 257 | this.reset.setCaption(reset); 258 | } 259 | 260 | @Override 261 | protected Component initContent() { 262 | if (content == null) { 263 | content = new PopupButton(); 264 | content.setWidth(100, Unit.PERCENTAGE); 265 | setImmediate(true); 266 | setStyleName("numberfilterpopup"); 267 | initPopup(); 268 | setFilterDecorator(decorator); 269 | content.addPopupVisibilityListener(event -> { 270 | if (settingValue) { 271 | settingValue = false; 272 | } else if (interval == null) { 273 | ltInput.setValue(""); 274 | gtInput.setValue(""); 275 | eqInput.setValue(""); 276 | } else { 277 | ltInput.setValue(interval.getLessThanValue()); 278 | gtInput.setValue(interval.getGreaterThanValue()); 279 | eqInput.setValue(interval.getEqualsValue()); 280 | } 281 | }); 282 | updateCaption(); 283 | } 284 | return content; 285 | } 286 | 287 | @Override 288 | public Class getType() { 289 | return NumberInterval.class; 290 | } 291 | 292 | @Override 293 | public void setReadOnly(boolean readOnly) { 294 | super.setReadOnly(readOnly); 295 | ok.setEnabled(!readOnly); 296 | reset.setEnabled(!readOnly); 297 | ltInput.setEnabled(!readOnly); 298 | gtInput.setEnabled(!readOnly); 299 | eqInput.setEnabled(!readOnly); 300 | } 301 | 302 | public void setDecimalPlacesAllowed(boolean allowDecimalPlaces) { 303 | this.allowDecimalPlaces = allowDecimalPlaces; 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/client/ui/VFilterTable.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable.client.ui; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.logging.Level; 6 | import java.util.logging.Logger; 7 | 8 | import com.google.gwt.dom.client.Element; 9 | import com.google.gwt.user.client.DOM; 10 | import com.google.gwt.user.client.ui.FlowPanel; 11 | import com.google.gwt.user.client.ui.SimplePanel; 12 | import com.google.gwt.user.client.ui.Widget; 13 | import com.vaadin.client.MeasuredSize; 14 | import com.vaadin.client.ValueMap; 15 | import com.vaadin.client.WidgetUtil; 16 | import com.vaadin.v7.client.ui.VScrollTable; 17 | import com.vaadin.v7.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow; 18 | 19 | /** 20 | * VFilterTable. 21 | * 22 | * @author Teppo Kurki 23 | * @since 27.10.2014 24 | */ 25 | public class VFilterTable extends VScrollTable { 26 | 27 | private static final Logger LOG = Logger.getLogger(VFilterTable.class.getName()); 28 | 29 | private static final String FILTER_PANEL_STYLE = "filters-panel"; 30 | private static final String FILTER_WRAPPER_STYLE = "filterwrapper"; 31 | private static final String FILTER_WRAPPER_FIRST_STYLE = "filterwrapper-first"; 32 | private static final String FILTER_WRAPPER_LAST_STYLE = "filterwrapper-last"; 33 | private static final String FILTER_PLACEHOLDER_STYLE = "filterplaceholder"; 34 | private static final String FILTER_TABLE_HEADER_WRAP_STYLE = "v-filter-table-header-wrap"; 35 | private static final String FILTER_TABLE_HEADER_STYLE = "v-filter-table-header"; 36 | private static final String FILTER_TABLE_HEADER_CELL_STYLE = "v-table-header-cell"; 37 | private static final String FILTER_TABLE_COLUMN_SELECTOR_STYLE = "v-filter-table-column-selector"; 38 | 39 | private final Element tHeadTBodyElement; 40 | private final Element tHeadTableHeaderDiv; 41 | private final Element tHeadColumnSelectorDiv; 42 | private final Element filterTrElement; 43 | 44 | /* Set to true to render the filter bar */ 45 | private boolean filtersVisible; 46 | /* Column filter components - mapped by column keys */ 47 | public Map filters = new HashMap(); 48 | 49 | private ValueMap columnHeaderStylenames; 50 | 51 | public VFilterTable() { 52 | super(); 53 | 54 | Element tHeadElement = tHead.getElement(); 55 | tHeadTBodyElement = findChildElement(tHeadElement, "tbody"); 56 | if (tHeadTBodyElement == null) { 57 | assert tHeadTBodyElement != null; 58 | tHeadTableHeaderDiv = tHeadColumnSelectorDiv = filterTrElement = null; 59 | if (LOG.isLoggable(Level.WARNING)) 60 | LOG.warning("Unable to find tBody element in table's header. Filter won't work!"); 61 | } else { 62 | if (tHeadElement.getChildCount() > 1) { 63 | tHeadTableHeaderDiv = DOM.getChild(tHeadElement, 0); 64 | tHeadColumnSelectorDiv = DOM.getChild(tHeadElement, 1); 65 | } else { 66 | tHeadColumnSelectorDiv = tHeadTableHeaderDiv = null; 67 | if (LOG.isLoggable(Level.WARNING)) 68 | LOG.warning("Unable to find table header div and column selector div in table's header."); 69 | } 70 | 71 | tHeadTBodyElement.appendChild(filterTrElement = DOM.createTR()); 72 | filterTrElement.addClassName(FILTER_PANEL_STYLE); 73 | setFiltersVisible(true); 74 | } 75 | } 76 | 77 | @Override 78 | protected void setColWidth(int colIndex, int w, boolean isDefinedWidth) { 79 | super.setColWidth(colIndex, w, isDefinedWidth); 80 | setFilterWidth(colIndex); 81 | } 82 | 83 | @Override 84 | protected void reOrderColumn(String columnKey, int newIndex) { 85 | super.reOrderColumn(columnKey, newIndex); 86 | reRenderFilterComponents(); 87 | } 88 | 89 | @Override 90 | public void onUnregister() { 91 | super.onUnregister(); 92 | filters.clear(); 93 | } 94 | 95 | /** 96 | * Changes the visibility of the table filters. 97 | * 98 | * @param filtersVisible 99 | * {@code true} to display filters. Otherwise {@code false} 100 | */ 101 | public void setFiltersVisible(boolean filtersVisible) { 102 | if (this.filtersVisible != filtersVisible) { 103 | this.filtersVisible = filtersVisible; 104 | 105 | if (filterTrElement != null) { 106 | if (this.filtersVisible) { 107 | tHeadTBodyElement.appendChild(filterTrElement); 108 | tHead.addStyleName(FILTER_TABLE_HEADER_WRAP_STYLE); 109 | if (tHeadTableHeaderDiv != null) { 110 | tHeadTableHeaderDiv.addClassName(FILTER_TABLE_HEADER_STYLE); 111 | tHeadColumnSelectorDiv.addClassName(FILTER_TABLE_COLUMN_SELECTOR_STYLE); 112 | } 113 | } else { 114 | tHeadTBodyElement.removeChild(filterTrElement); 115 | tHead.removeStyleName(FILTER_TABLE_HEADER_WRAP_STYLE); 116 | if (tHeadTableHeaderDiv != null) { 117 | tHeadTableHeaderDiv.removeClassName(FILTER_TABLE_HEADER_STYLE); 118 | tHeadColumnSelectorDiv.addClassName(FILTER_TABLE_COLUMN_SELECTOR_STYLE); 119 | } 120 | } 121 | } 122 | } 123 | } 124 | 125 | /** 126 | * Removes all filters from the filter row and re-adds them. 127 | */ 128 | public void reRenderFilterComponents() { 129 | filterTrElement.removeAllChildren(); 130 | 131 | /* Remember height */ 132 | MeasuredSize ms = new MeasuredSize(); 133 | ms.measure(filterTrElement); 134 | int height = (int) ms.getInnerHeight(); 135 | 136 | int visibleCellCount = tHead.getVisibleCellCount(); 137 | for (int i = 0; i < visibleCellCount; i++) { 138 | String key = tHead.getHeaderCell(i).getColKey(); 139 | if (key != null) { 140 | Widget widget = filters.get(key); 141 | 142 | SimplePanel wrapper = new SimplePanel(); 143 | wrapper.addStyleName(FILTER_WRAPPER_STYLE); 144 | if (i == 0) { 145 | wrapper.addStyleName(FILTER_WRAPPER_FIRST_STYLE); 146 | } else if (i == visibleCellCount - 1) { 147 | wrapper.addStyleName(FILTER_WRAPPER_LAST_STYLE); 148 | } 149 | 150 | if (widget == null) { 151 | /* 152 | * No filter defined -> Use a place holder of the correct 153 | * width 154 | */ 155 | widget = new FlowPanel(); 156 | widget.addStyleName(FILTER_PLACEHOLDER_STYLE); 157 | filters.put(key, widget); 158 | } 159 | 160 | wrapper.setWidget(widget); 161 | 162 | Element filterColumn = DOM.createTD(); 163 | filterColumn.addClassName(FILTER_TABLE_HEADER_CELL_STYLE); 164 | filterTrElement.appendChild(filterColumn); 165 | 166 | wrapper.removeFromParent(); 167 | filterColumn.appendChild(wrapper.getElement()); 168 | adopt(wrapper); 169 | 170 | /* deal with wrapper height */ 171 | MeasuredSize wrapperSize = new MeasuredSize(); 172 | wrapperSize.measure(wrapper.getElement()); 173 | int correction = wrapperSize.getMarginHeight() + wrapperSize.getBorderHeight() 174 | + wrapperSize.getPaddingHeight(); 175 | /* ensure no negative heights */ 176 | int wrapperHeight = Math.max(height - correction, 0); 177 | wrapper.setHeight(wrapperHeight + "px"); 178 | 179 | setFilterWidth(i); 180 | 181 | if (columnHeaderStylenames != null) { 182 | String styleName = columnHeaderStylenames.getString(key); 183 | if (styleName != null && !styleName.trim().isEmpty()) { 184 | wrapper.addStyleName(columnHeaderStylenames.getString(key)); 185 | } 186 | } 187 | } 188 | } 189 | } 190 | 191 | /** 192 | * Recalculates and re-sets the width of all table filters. 193 | */ 194 | public void resetFilterWidths() { 195 | for (int i = 0; i < tHead.getVisibleCellCount(); i++) { 196 | setFilterWidth(i); 197 | } 198 | } 199 | 200 | private void setFilterWidth(int index) { 201 | if (headerChangedDuringUpdate) { 202 | return; 203 | } 204 | 205 | HeaderCell headerCell = tHead.getHeaderCell(index); 206 | if (headerCell != null) { 207 | Widget widget = filters.get(headerCell.getColKey()); 208 | if (null != widget) { 209 | /* 210 | * try to get width from first rendered row -> fixes 1px bug in 211 | * GC 212 | */ 213 | final VScrollTableRow firstRow = scrollBody.getRowByRowIndex(scrollBody.getFirstRendered()); 214 | int wrapperWidth = -1; 215 | if (firstRow != null) { 216 | final Element cell = DOM.getChild(firstRow.getElement(), index); 217 | wrapperWidth = WidgetUtil.getRequiredWidth(cell); 218 | } 219 | if (wrapperWidth <= 0) { 220 | wrapperWidth = WidgetUtil.getRequiredWidth(headerCell); 221 | } 222 | 223 | Widget wrapper = widget.getParent(); 224 | MeasuredSize wrapperSize = new MeasuredSize(); 225 | wrapperSize.measure(wrapper.getElement()); 226 | int wrapperCorrections = wrapperSize.getMarginWidth() + wrapperSize.getBorderWidth() 227 | + wrapperSize.getPaddingWidth(); 228 | wrapperWidth = wrapperWidth - wrapperCorrections; 229 | wrapper.setWidth((wrapperWidth > 0 ? wrapperWidth : 0) + "px"); 230 | 231 | if (0 < wrapperWidth) { 232 | int widgetWidth = wrapperWidth; 233 | MeasuredSize widgetSize = new MeasuredSize(); 234 | widgetSize.measure(widget.getElement()); 235 | widgetWidth -= widgetSize.getMarginWidth(); 236 | widget.setWidth((widgetWidth > 0 ? widgetWidth : 0) + "px"); 237 | } 238 | } 239 | } 240 | } 241 | 242 | public void setColumnHeaderStylenames(ValueMap valueMap) { 243 | this.columnHeaderStylenames = valueMap; 244 | } 245 | 246 | /** 247 | * Helper method to find first instance of given child element {@code type} 248 | * found by traversing DOM downwards from given {@code element}. If no 249 | * matching child can be found {@code null} is returned. 250 | * 251 | * @param parent 252 | * the element where to start seeking of child element, not 253 | * {@code null} 254 | * @param type 255 | * type of child element to seek for, not {@code null} 256 | * @return first child of {@code type} or {@code null} if none was found 257 | */ 258 | public static Element findChildElement(final Element parent, final String type) { 259 | if (parent != null && type != null) { 260 | Element child = null; 261 | int count = DOM.getChildCount(parent); 262 | for (int i = 0; child == null && i < count; i++) { 263 | Element element = DOM.getChild(parent, i); 264 | String nodeName = element.getPropertyString("nodeName"); 265 | child = type.equalsIgnoreCase(nodeName) ? element : findChildElement(element, type); 266 | } 267 | 268 | return child; 269 | } 270 | 271 | return null; 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/client/ui/VFilterTreeTable.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable.client.ui; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.logging.Level; 6 | import java.util.logging.Logger; 7 | 8 | import com.google.gwt.dom.client.Element; 9 | import com.google.gwt.user.client.DOM; 10 | import com.google.gwt.user.client.ui.FlowPanel; 11 | import com.google.gwt.user.client.ui.SimplePanel; 12 | import com.google.gwt.user.client.ui.Widget; 13 | import com.vaadin.client.MeasuredSize; 14 | import com.vaadin.client.ValueMap; 15 | import com.vaadin.client.WidgetUtil; 16 | import com.vaadin.v7.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow; 17 | import com.vaadin.v7.client.ui.VTreeTable; 18 | 19 | /** 20 | * VFilterTable. 21 | * 22 | * @author Teppo Kurki 23 | * @since 27.10.2014 24 | */ 25 | public class VFilterTreeTable extends VTreeTable { 26 | 27 | private static final Logger LOG = Logger.getLogger(VFilterTreeTable.class.getName()); 28 | 29 | private static final String FILTER_PANEL_STYLE = "filters-panel"; 30 | private static final String FILTER_WRAPPER_STYLE = "filterwrapper"; 31 | private static final String FILTER_WRAPPER_FIRST_STYLE = "filterwrapper-first"; 32 | private static final String FILTER_WRAPPER_LAST_STYLE = "filterwrapper-last"; 33 | private static final String FILTER_PLACEHOLDER_STYLE = "filterplaceholder"; 34 | private static final String FILTER_TABLE_HEADER_WRAP_STYLE = "v-filter-table-header-wrap"; 35 | private static final String FILTER_TABLE_HEADER_STYLE = "v-filter-table-header"; 36 | private static final String FILTER_TABLE_HEADER_CELL_STYLE = "v-table-header-cell"; 37 | private static final String FILTER_TABLE_COLUMN_SELECTOR_STYLE = "v-filter-table-column-selector"; 38 | 39 | private final Element tHeadTBodyElement; 40 | private final Element tHeadTableHeaderDiv; 41 | private final Element tHeadColumnSelectorDiv; 42 | private final Element filterTrElement; 43 | 44 | /* Set to true to render the filter bar */ 45 | private boolean filtersVisible; 46 | /* Column filter components - mapped by column keys */ 47 | public Map filters = new HashMap(); 48 | 49 | private ValueMap columnHeaderStylenames; 50 | 51 | public VFilterTreeTable() { 52 | super(); 53 | 54 | Element tHeadElement = tHead.getElement(); 55 | tHeadTBodyElement = findChildElement(tHeadElement, "tbody"); 56 | if (tHeadTBodyElement == null) { 57 | assert tHeadTBodyElement != null; 58 | tHeadTableHeaderDiv = tHeadColumnSelectorDiv = filterTrElement = null; 59 | if (LOG.isLoggable(Level.WARNING)) 60 | LOG.warning("Unable to find tBody element in table's header. Filter won't work!"); 61 | } else { 62 | if (tHeadElement.getChildCount() > 1) { 63 | tHeadTableHeaderDiv = DOM.getChild(tHeadElement, 0); 64 | tHeadColumnSelectorDiv = DOM.getChild(tHeadElement, 1); 65 | } else { 66 | tHeadColumnSelectorDiv = tHeadTableHeaderDiv = null; 67 | if (LOG.isLoggable(Level.WARNING)) 68 | LOG.warning("Unable to find table header div and column selector div in table's header."); 69 | } 70 | 71 | tHeadTBodyElement.appendChild(filterTrElement = DOM.createTR()); 72 | filterTrElement.addClassName(FILTER_PANEL_STYLE); 73 | setFiltersVisible(true); 74 | } 75 | } 76 | 77 | @Override 78 | protected void setColWidth(int colIndex, int w, boolean isDefinedWidth) { 79 | super.setColWidth(colIndex, w, isDefinedWidth); 80 | setFilterWidth(colIndex); 81 | } 82 | 83 | @Override 84 | protected void reOrderColumn(String columnKey, int newIndex) { 85 | super.reOrderColumn(columnKey, newIndex); 86 | reRenderFilterComponents(); 87 | } 88 | 89 | @Override 90 | public void onUnregister() { 91 | super.onUnregister(); 92 | filters.clear(); 93 | } 94 | 95 | /** 96 | * Changes the visibility of the table filters. 97 | * 98 | * @param filtersVisible 99 | * {@code true} to display filters. Otherwise {@code false} 100 | */ 101 | public void setFiltersVisible(boolean filtersVisible) { 102 | if (this.filtersVisible != filtersVisible) { 103 | this.filtersVisible = filtersVisible; 104 | 105 | if (filterTrElement != null) { 106 | if (this.filtersVisible) { 107 | tHeadTBodyElement.appendChild(filterTrElement); 108 | tHead.addStyleName(FILTER_TABLE_HEADER_WRAP_STYLE); 109 | if (tHeadTableHeaderDiv != null) { 110 | tHeadTableHeaderDiv.addClassName(FILTER_TABLE_HEADER_STYLE); 111 | tHeadColumnSelectorDiv.addClassName(FILTER_TABLE_COLUMN_SELECTOR_STYLE); 112 | } 113 | } else { 114 | tHeadTBodyElement.removeChild(filterTrElement); 115 | tHead.removeStyleName(FILTER_TABLE_HEADER_WRAP_STYLE); 116 | if (tHeadTableHeaderDiv != null) { 117 | tHeadTableHeaderDiv.removeClassName(FILTER_TABLE_HEADER_STYLE); 118 | tHeadColumnSelectorDiv.addClassName(FILTER_TABLE_COLUMN_SELECTOR_STYLE); 119 | } 120 | } 121 | } 122 | } 123 | } 124 | 125 | /** 126 | * Removes all filters from the filter row and re-adds them. 127 | */ 128 | public void reRenderFilterComponents() { 129 | filterTrElement.removeAllChildren(); 130 | 131 | /* Remember height */ 132 | MeasuredSize ms = new MeasuredSize(); 133 | ms.measure(filterTrElement); 134 | int height = (int) ms.getInnerHeight(); 135 | 136 | int visibleCellCount = tHead.getVisibleCellCount(); 137 | for (int i = 0; i < visibleCellCount; i++) { 138 | String key = tHead.getHeaderCell(i).getColKey(); 139 | if (key != null) { 140 | Widget widget = filters.get(key); 141 | 142 | SimplePanel wrapper = new SimplePanel(); 143 | wrapper.addStyleName(FILTER_WRAPPER_STYLE); 144 | if (i == 0) { 145 | wrapper.addStyleName(FILTER_WRAPPER_FIRST_STYLE); 146 | } else if (i == visibleCellCount - 1) { 147 | wrapper.addStyleName(FILTER_WRAPPER_LAST_STYLE); 148 | } 149 | 150 | if (widget == null) { 151 | /* 152 | * No filter defined -> Use a place holder of the correct 153 | * width 154 | */ 155 | widget = new FlowPanel(); 156 | widget.addStyleName(FILTER_PLACEHOLDER_STYLE); 157 | filters.put(key, widget); 158 | } 159 | 160 | wrapper.setWidget(widget); 161 | 162 | Element filterColumn = DOM.createTD(); 163 | filterColumn.addClassName(FILTER_TABLE_HEADER_CELL_STYLE); 164 | filterTrElement.appendChild(filterColumn); 165 | 166 | wrapper.removeFromParent(); 167 | filterColumn.appendChild(wrapper.getElement()); 168 | adopt(wrapper); 169 | 170 | /* deal with wrapper height */ 171 | MeasuredSize wrapperSize = new MeasuredSize(); 172 | wrapperSize.measure(wrapper.getElement()); 173 | int correction = wrapperSize.getMarginHeight() + wrapperSize.getBorderHeight() 174 | + wrapperSize.getPaddingHeight(); 175 | /* ensure no negative heights */ 176 | int wrapperHeight = Math.max(height - correction, 0); 177 | wrapper.setHeight(wrapperHeight + "px"); 178 | 179 | setFilterWidth(i); 180 | 181 | if (columnHeaderStylenames != null) { 182 | String styleName = columnHeaderStylenames.getString(key); 183 | if (styleName != null && !styleName.trim().isEmpty()) { 184 | wrapper.addStyleName(columnHeaderStylenames.getString(key)); 185 | } 186 | } 187 | } 188 | } 189 | } 190 | 191 | /** 192 | * Recalculates and re-sets the width of all table filters. 193 | */ 194 | public void resetFilterWidths() { 195 | for (int i = 0; i < tHead.getVisibleCellCount(); i++) { 196 | setFilterWidth(i); 197 | } 198 | } 199 | 200 | private void setFilterWidth(int index) { 201 | if (headerChangedDuringUpdate) { 202 | return; 203 | } 204 | 205 | HeaderCell headerCell = tHead.getHeaderCell(index); 206 | if (headerCell != null) { 207 | Widget widget = filters.get(headerCell.getColKey()); 208 | if (null != widget) { 209 | /* 210 | * try to get width from first rendered row -> fixes 1px bug in 211 | * GC 212 | */ 213 | final VScrollTableRow firstRow = scrollBody.getRowByRowIndex(scrollBody.getFirstRendered()); 214 | int wrapperWidth = -1; 215 | if (firstRow != null) { 216 | final Element cell = DOM.getChild(firstRow.getElement(), index); 217 | wrapperWidth = WidgetUtil.getRequiredWidth(cell); 218 | } 219 | if (wrapperWidth <= 0) { 220 | wrapperWidth = WidgetUtil.getRequiredWidth(headerCell); 221 | } 222 | 223 | Widget wrapper = widget.getParent(); 224 | MeasuredSize wrapperSize = new MeasuredSize(); 225 | wrapperSize.measure(wrapper.getElement()); 226 | int wrapperCorrections = wrapperSize.getMarginWidth() + wrapperSize.getBorderWidth() 227 | + wrapperSize.getPaddingWidth(); 228 | wrapperWidth = wrapperWidth - wrapperCorrections; 229 | wrapper.setWidth((wrapperWidth > 0 ? wrapperWidth : 0) + "px"); 230 | 231 | if (0 < wrapperWidth) { 232 | int widgetWidth = wrapperWidth; 233 | MeasuredSize widgetSize = new MeasuredSize(); 234 | widgetSize.measure(widget.getElement()); 235 | widgetWidth -= widgetSize.getMarginWidth(); 236 | widget.setWidth((widgetWidth > 0 ? widgetWidth : 0) + "px"); 237 | } 238 | } 239 | } 240 | } 241 | 242 | public void setColumnHeaderStylenames(ValueMap valueMap) { 243 | this.columnHeaderStylenames = valueMap; 244 | } 245 | 246 | /** 247 | * Helper method to find first instance of given child element {@code type} 248 | * found by traversing DOM downwards from given {@code element}. If no 249 | * matching child can be found {@code null} is returned. 250 | * 251 | * @param parent 252 | * the element where to start seeking of child element, not 253 | * {@code null} 254 | * @param type 255 | * type of child element to seek for, not {@code null} 256 | * @return first child of {@code type} or {@code null} if none was found 257 | */ 258 | public static Element findChildElement(final Element parent, final String type) { 259 | if (parent != null && type != null) { 260 | Element child = null; 261 | int count = DOM.getChildCount(parent); 262 | for (int i = 0; child == null && i < count; i++) { 263 | Element element = DOM.getChild(parent, i); 264 | String nodeName = element.getPropertyString("nodeName"); 265 | child = type.equalsIgnoreCase(nodeName) ? element : findChildElement(element, type); 266 | } 267 | 268 | return child; 269 | } 270 | 271 | return null; 272 | } 273 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/paged/PagedFilterTable.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable.paged; 2 | 3 | import java.io.Serializable; 4 | import java.text.NumberFormat; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Locale; 8 | 9 | import org.tepi.filtertable.FilterTable; 10 | 11 | import com.vaadin.shared.ui.ContentMode; 12 | import com.vaadin.ui.Alignment; 13 | import com.vaadin.ui.Button; 14 | import com.vaadin.ui.HorizontalLayout; 15 | import com.vaadin.ui.Label; 16 | import com.vaadin.ui.UI; 17 | import com.vaadin.ui.themes.ValoTheme; 18 | import com.vaadin.v7.data.Container; 19 | import com.vaadin.v7.data.util.converter.StringToIntegerConverter; 20 | import com.vaadin.v7.ui.ComboBox; 21 | import com.vaadin.v7.ui.TextField; 22 | 23 | @SuppressWarnings({ "serial", "deprecation" }) 24 | public class PagedFilterTable 25 | extends FilterTable { 26 | 27 | public interface PageChangeListener extends Serializable { 28 | public void pageChanged(PagedTableChangeEvent event); 29 | } 30 | 31 | private List listeners = null; 32 | 33 | private PagedFilterTableContainer container; 34 | 35 | public PagedFilterTable() { 36 | this(null); 37 | } 38 | 39 | public PagedFilterTable(String caption) { 40 | super(caption); 41 | setPageLength(25); 42 | addStyleName("pagedtable"); 43 | } 44 | 45 | public HorizontalLayout createControls(PagedFilterControlConfig config) { 46 | Label itemsPerPageLabel = new Label(config.getItemsPerPage(), ContentMode.HTML); 47 | itemsPerPageLabel.setSizeUndefined(); 48 | final ComboBox itemsPerPageSelect = new ComboBox(); 49 | 50 | for (Integer i : config.getPageLengths()) { 51 | itemsPerPageSelect.addItem(i); 52 | itemsPerPageSelect.setItemCaption(i, String.valueOf(i)); 53 | } 54 | itemsPerPageSelect.setImmediate(true); 55 | itemsPerPageSelect.setNullSelectionAllowed(false); 56 | itemsPerPageSelect.setWidth(null); 57 | itemsPerPageSelect.addValueChangeListener(e -> setPageLength((Integer) e.getProperty().getValue())); 58 | if (itemsPerPageSelect.containsId(getPageLength())) { 59 | itemsPerPageSelect.select(getPageLength()); 60 | } else { 61 | itemsPerPageSelect.select(itemsPerPageSelect.getItemIds().iterator().next()); 62 | } 63 | Label pageLabel = new Label(config.getPage(), ContentMode.HTML); 64 | final TextField currentPageTextField = new TextField(); 65 | currentPageTextField.setValue(String.valueOf(getCurrentPage())); 66 | currentPageTextField.setConverter(new StringToIntegerConverter() { 67 | @Override 68 | protected NumberFormat getFormat(Locale locale) { 69 | NumberFormat result = super.getFormat(UI.getCurrent().getLocale()); 70 | result.setGroupingUsed(false); 71 | return result; 72 | } 73 | }); 74 | Label separatorLabel = new Label(" / ", ContentMode.HTML); 75 | final Label totalPagesLabel = new Label(String.valueOf(getTotalAmountOfPages()), ContentMode.HTML); 76 | currentPageTextField.setStyleName(ValoTheme.TEXTFIELD_SMALL); 77 | currentPageTextField.setImmediate(true); 78 | currentPageTextField.addValueChangeListener(e -> { 79 | if (currentPageTextField.isValid() && currentPageTextField.getValue() != null) { 80 | int page = Integer.valueOf(String.valueOf(currentPageTextField.getValue())); 81 | setCurrentPage(page); 82 | } 83 | }); 84 | pageLabel.setWidth(null); 85 | currentPageTextField.setColumns(3); 86 | separatorLabel.setWidth(null); 87 | totalPagesLabel.setWidth(null); 88 | 89 | HorizontalLayout controlBar = new HorizontalLayout(); 90 | HorizontalLayout pageSize = new HorizontalLayout(); 91 | HorizontalLayout pageManagement = new HorizontalLayout(); 92 | final Button first = new Button(config.getFirst(), e -> setCurrentPage(0)); 93 | final Button previous = new Button(config.getPrevious(), e -> previousPage()); 94 | final Button next = new Button(config.getNext(), e -> nextPage()); 95 | final Button last = new Button(config.getLast(), e -> setCurrentPage(getTotalAmountOfPages())); 96 | first.setStyleName(ValoTheme.BUTTON_LINK); 97 | previous.setStyleName(ValoTheme.BUTTON_LINK); 98 | next.setStyleName(ValoTheme.BUTTON_LINK); 99 | last.setStyleName(ValoTheme.BUTTON_LINK); 100 | 101 | itemsPerPageLabel.addStyleName("pagedtable-itemsperpagecaption"); 102 | itemsPerPageSelect.addStyleName("pagedtable-itemsperpagecombobox"); 103 | pageLabel.addStyleName("pagedtable-pagecaption"); 104 | currentPageTextField.addStyleName("pagedtable-pagefield"); 105 | separatorLabel.addStyleName("pagedtable-separator"); 106 | totalPagesLabel.addStyleName("pagedtable-total"); 107 | first.addStyleName("pagedtable-first"); 108 | previous.addStyleName("pagedtable-previous"); 109 | next.addStyleName("pagedtable-next"); 110 | last.addStyleName("pagedtable-last"); 111 | 112 | itemsPerPageLabel.addStyleName("pagedtable-label"); 113 | itemsPerPageSelect.addStyleName("pagedtable-combobox"); 114 | pageLabel.addStyleName("pagedtable-label"); 115 | currentPageTextField.addStyleName("pagedtable-label"); 116 | separatorLabel.addStyleName("pagedtable-label"); 117 | totalPagesLabel.addStyleName("pagedtable-label"); 118 | first.addStyleName("pagedtable-button"); 119 | previous.addStyleName("pagedtable-button"); 120 | next.addStyleName("pagedtable-button"); 121 | last.addStyleName("pagedtable-button"); 122 | 123 | pageSize.addComponent(itemsPerPageLabel); 124 | pageSize.addComponent(itemsPerPageSelect); 125 | pageSize.setComponentAlignment(itemsPerPageLabel, Alignment.MIDDLE_LEFT); 126 | pageSize.setComponentAlignment(itemsPerPageSelect, Alignment.MIDDLE_LEFT); 127 | pageSize.setSpacing(true); 128 | pageManagement.addComponent(first); 129 | pageManagement.addComponent(previous); 130 | pageManagement.addComponent(pageLabel); 131 | pageManagement.addComponent(currentPageTextField); 132 | pageManagement.addComponent(separatorLabel); 133 | pageManagement.addComponent(totalPagesLabel); 134 | pageManagement.addComponent(next); 135 | pageManagement.addComponent(last); 136 | pageManagement.setComponentAlignment(first, Alignment.MIDDLE_LEFT); 137 | pageManagement.setComponentAlignment(previous, Alignment.MIDDLE_LEFT); 138 | pageManagement.setComponentAlignment(pageLabel, Alignment.MIDDLE_LEFT); 139 | pageManagement.setComponentAlignment(currentPageTextField, Alignment.MIDDLE_LEFT); 140 | pageManagement.setComponentAlignment(separatorLabel, Alignment.MIDDLE_LEFT); 141 | pageManagement.setComponentAlignment(totalPagesLabel, Alignment.MIDDLE_LEFT); 142 | pageManagement.setComponentAlignment(next, Alignment.MIDDLE_LEFT); 143 | pageManagement.setComponentAlignment(last, Alignment.MIDDLE_LEFT); 144 | pageManagement.setWidth(null); 145 | pageManagement.setSpacing(true); 146 | controlBar.addComponent(pageSize); 147 | controlBar.addComponent(pageManagement); 148 | controlBar.setComponentAlignment(pageManagement, Alignment.MIDDLE_CENTER); 149 | controlBar.setWidth(100, Unit.PERCENTAGE); 150 | controlBar.setExpandRatio(pageSize, 1); 151 | 152 | if (container != null) { 153 | first.setEnabled(container.getStartIndex() > 0); 154 | previous.setEnabled(container.getStartIndex() > 0); 155 | next.setEnabled(container.getStartIndex() < container.getRealSize() - getPageLength()); 156 | last.setEnabled(container.getStartIndex() < container.getRealSize() - getPageLength()); 157 | } 158 | 159 | addListener(new PageChangeListener() { 160 | private boolean inMiddleOfValueChange; 161 | 162 | @Override 163 | public void pageChanged(PagedTableChangeEvent event) { 164 | if (!inMiddleOfValueChange) { 165 | inMiddleOfValueChange = true; 166 | first.setEnabled(container.getStartIndex() > 0); 167 | previous.setEnabled(container.getStartIndex() > 0); 168 | next.setEnabled(container.getStartIndex() < container.getRealSize() - getPageLength()); 169 | last.setEnabled(container.getStartIndex() < container.getRealSize() - getPageLength()); 170 | currentPageTextField.setValue(String.valueOf(getCurrentPage())); 171 | totalPagesLabel.setValue(Integer.toString(getTotalAmountOfPages())); 172 | itemsPerPageSelect.setValue(getPageLength()); 173 | inMiddleOfValueChange = false; 174 | } 175 | } 176 | }); 177 | return controlBar; 178 | } 179 | 180 | @Override 181 | public PagedFilterTableContainer getContainerDataSource() { 182 | return container; 183 | } 184 | 185 | @Override 186 | @SuppressWarnings("unchecked") 187 | public void setContainerDataSource(Container newDataSource) { 188 | if (!(newDataSource instanceof Container.Indexed) || !(newDataSource instanceof Container.Filterable)) { 189 | throw new IllegalArgumentException( 190 | "PagedFilteringTable can only use containers that implement Container.Indexed AND Container.Filterable"); 191 | } 192 | PagedFilterTableContainer pagedFilteringTableContainer = new PagedFilterTableContainer((T) newDataSource); 193 | pagedFilteringTableContainer.setPageLength(getPageLength()); 194 | container = pagedFilteringTableContainer; 195 | super.setContainerDataSource(pagedFilteringTableContainer); 196 | firePagedChangedEvent(); 197 | } 198 | 199 | private void setPageFirstIndex(int firstIndex) { 200 | if (container != null) { 201 | if (firstIndex <= 0) { 202 | firstIndex = 0; 203 | } 204 | if (firstIndex > container.getRealSize() - 1) { 205 | int size = container.getRealSize() - 1; 206 | int pages = 0; 207 | if (getPageLength() != 0) { 208 | pages = (int) Math.floor(0.0 + size / getPageLength()); 209 | } 210 | firstIndex = pages * getPageLength(); 211 | } 212 | container.setStartIndex(firstIndex); 213 | containerItemSetChange(new Container.ItemSetChangeEvent() { 214 | private static final long serialVersionUID = -5083660879306951876L; 215 | 216 | @Override 217 | public Container getContainer() { 218 | return container; 219 | } 220 | }); 221 | if (alwaysRecalculateColumnWidths) { 222 | for (Object columnId : container.getContainerPropertyIds()) { 223 | setColumnWidth(columnId, -1); 224 | } 225 | } 226 | firePagedChangedEvent(); 227 | } 228 | } 229 | 230 | private void firePagedChangedEvent() { 231 | if (listeners != null) { 232 | PagedTableChangeEvent event = new PagedTableChangeEvent(this); 233 | for (PageChangeListener listener : listeners) { 234 | listener.pageChanged(event); 235 | } 236 | } 237 | } 238 | 239 | @Override 240 | public void setPageLength(int pageLength) { 241 | if (pageLength >= 0 && getPageLength() != pageLength) { 242 | container.setPageLength(pageLength); 243 | super.setPageLength(pageLength); 244 | firePagedChangedEvent(); 245 | } 246 | } 247 | 248 | public void nextPage() { 249 | setPageFirstIndex(container.getStartIndex() + getPageLength()); 250 | } 251 | 252 | public void previousPage() { 253 | setPageFirstIndex(container.getStartIndex() - getPageLength()); 254 | } 255 | 256 | public int getCurrentPage() { 257 | double pageLength = getPageLength(); 258 | int page = (int) Math.floor(container.getStartIndex() / pageLength) + 1; 259 | if (page < 1) { 260 | page = 1; 261 | } 262 | return page; 263 | } 264 | 265 | public void setCurrentPage(int page) { 266 | int newIndex = (page - 1) * getPageLength(); 267 | if (newIndex < 0) { 268 | newIndex = 0; 269 | } 270 | setPageFirstIndex(newIndex); 271 | } 272 | 273 | public int getTotalAmountOfPages() { 274 | int size = container.getContainer().size(); 275 | double pageLength = getPageLength(); 276 | int pageCount = (int) Math.ceil(size / pageLength); 277 | if (pageCount < 1) { 278 | pageCount = 1; 279 | } 280 | return pageCount; 281 | } 282 | 283 | public void addListener(PageChangeListener listener) { 284 | if (listeners == null) { 285 | listeners = new ArrayList(); 286 | } 287 | listeners.add(listener); 288 | } 289 | 290 | public void removeListener(PageChangeListener listener) { 291 | if (listeners == null) { 292 | listeners = new ArrayList(); 293 | } 294 | listeners.remove(listener); 295 | } 296 | 297 | @Override 298 | public void resetFilters() { 299 | super.resetFilters(); 300 | setCurrentPage(1); 301 | } 302 | 303 | @Override 304 | public void runFilters() { 305 | super.runFilters(); 306 | setCurrentPage(1); 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/FilterTable.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable; 2 | 3 | import java.util.Collection; 4 | import java.util.HashMap; 5 | import java.util.HashSet; 6 | import java.util.Iterator; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | import org.tepi.filtertable.FilterFieldGenerator.IFilterTable; 11 | import org.tepi.filtertable.client.ui.FilterTableConnector; 12 | import org.tepi.filtertable.datefilter.DateInterval; 13 | 14 | import com.vaadin.server.KeyMapper; 15 | import com.vaadin.server.LegacyPaint; 16 | import com.vaadin.server.PaintException; 17 | import com.vaadin.server.PaintTarget; 18 | import com.vaadin.ui.Component; 19 | import com.vaadin.ui.HasComponents; 20 | import com.vaadin.v7.data.Container; 21 | import com.vaadin.v7.data.util.converter.Converter.ConversionException; 22 | import com.vaadin.v7.ui.AbstractField; 23 | import com.vaadin.v7.ui.Table; 24 | import com.vaadin.v7.ui.TextField; 25 | 26 | /** 27 | * FilterTable is an extension of the Vaadin Table component that provides 28 | * automatically generated filter fields for each column. 29 | * 30 | * @author Teppo Kurki 31 | * 32 | */ 33 | @SuppressWarnings({ "serial", "deprecation" }) 34 | public class FilterTable extends Table implements IFilterTable { 35 | /* Maps property id's to column filter components */ 36 | private final Map columnIdToFilterMap = new HashMap(); 37 | /* Internal list of currently collapsed column id:s */ 38 | private final Set collapsedColumnIds = new HashSet(); 39 | /* Set to true to show the filter components */ 40 | private boolean filtersVisible; 41 | /* Filter Generator and Decorator */ 42 | private FilterGenerator filterGenerator; 43 | private FilterDecorator decorator; 44 | /* FilterFieldGenerator instance */ 45 | private final FilterFieldGenerator generator; 46 | /* Is initialization done */ 47 | private final boolean initDone; 48 | /* Force-render filter fields */ 49 | private boolean reRenderFilterFields; 50 | /* Are filters run immediately, or only on demand? */ 51 | private boolean filtersRunOnDemand = false; 52 | /* Custom column header style names */ 53 | private final HashMap columnHeaderStylenames = new HashMap(); 54 | /* Fields from Table accessed via reflection */ 55 | private KeyMapper _columnIdMap; 56 | private HashSet _visibleComponents; 57 | 58 | /** 59 | * Creates a new empty FilterTable 60 | */ 61 | public FilterTable() { 62 | this(null); 63 | } 64 | 65 | /** 66 | * Creates a new empty FilterTable with the given caption 67 | * 68 | * @param caption 69 | * Caption to set for the FilterTable 70 | */ 71 | @SuppressWarnings("unchecked") 72 | public FilterTable(String caption) { 73 | super(caption); 74 | try { 75 | java.lang.reflect.Field field = com.vaadin.v7.ui.Table.class.getDeclaredField("columnIdMap"); 76 | field.setAccessible(true); 77 | _columnIdMap = (KeyMapper) field.get(this); 78 | } catch (Exception exception) { 79 | throw new IllegalArgumentException("Unable to get columnIdMap or visibleComponents", exception); 80 | } 81 | generator = new FilterFieldGenerator(this); 82 | initDone = true; 83 | } 84 | 85 | @Override 86 | protected void refreshRenderedCells() { 87 | super.refreshRenderedCells(); 88 | 89 | // NOTE: 'visibleComponents' HashSet is (re)created by method getVisibleCellsNoCache(...) 90 | // But only when method refreshRenderedCells() calls it. 91 | try { 92 | java.lang.reflect.Field field = com.vaadin.v7.ui.Table.class.getDeclaredField("visibleComponents"); 93 | field.setAccessible(true); 94 | _visibleComponents = (HashSet) field.get(this); 95 | } catch (Exception exception) { 96 | throw new IllegalArgumentException("Unable to get visibleComponents", exception); 97 | } 98 | } 99 | 100 | @Override 101 | public void paintContent(PaintTarget target) throws PaintException { 102 | super.paintContent(target); 103 | /* Add filter components to UIDL */ 104 | target.startTag(FilterTableConnector.TAG_FILTERS); 105 | target.addAttribute(FilterTableConnector.ATTRIBUTE_FILTERS_VISIBLE, filtersVisible); 106 | target.addAttribute(FilterTableConnector.ATTRIBUTE_FORCE_RENDER, reRenderFilterFields); 107 | reRenderFilterFields = false; 108 | for (Object key : getColumnIdToFilterMap().keySet()) { 109 | /* Make sure parent is set properly */ 110 | if (columnIdToFilterMap.get(key) != null && columnIdToFilterMap.get(key).getParent() == null) { 111 | continue; 112 | } 113 | /* Paint the filter field */ 114 | target.startTag(FilterTableConnector.TAG_FILTER_COMPONENT + _columnIdMap.key(key)); 115 | target.addAttribute(FilterTableConnector.ATTRIBUTE_COLUMN_ID, _columnIdMap.key(key)); 116 | Component c = getColumnIdToFilterMap().get(key); 117 | LegacyPaint.paint(c, target); 118 | target.endTag(FilterTableConnector.TAG_FILTER_COMPONENT + _columnIdMap.key(key)); 119 | } 120 | Map headerStylenames = getColumnHeaderStylenamesForPaint(); 121 | if (headerStylenames != null) { 122 | target.addAttribute(FilterTableConnector.ATTRIBUTE_COLUMN_HEADER_STYLE_NAMES, headerStylenames); 123 | } 124 | target.endTag(FilterTableConnector.TAG_FILTERS); 125 | } 126 | 127 | @Override 128 | public void setColumnCollapsed(Object propertyId, boolean collapsed) throws IllegalStateException { 129 | super.setColumnCollapsed(propertyId, collapsed); 130 | Component c = getColumnIdToFilterMap().get(propertyId); 131 | if (collapsed) { 132 | collapsedColumnIds.add(propertyId); 133 | if (c != null) { 134 | c.setParent(null); 135 | if (c instanceof TextField) { 136 | ((TextField) c).setValue(""); 137 | } else if (c instanceof AbstractField) { 138 | ((AbstractField) c).setValue(null); 139 | } 140 | } 141 | } else { 142 | if (c != null) { 143 | c.setParent(this); 144 | } 145 | collapsedColumnIds.remove(propertyId); 146 | } 147 | reRenderFilterFields = true; 148 | markAsDirty(); 149 | } 150 | 151 | @Override 152 | public void setContainerDataSource(Container newDataSource) { 153 | super.setContainerDataSource(newDataSource); 154 | } 155 | 156 | @Override 157 | public void setContainerDataSource(Container newDataSource, Collection visibleIds) { 158 | super.setContainerDataSource(newDataSource, visibleIds); 159 | resetFilters(); 160 | } 161 | 162 | /** 163 | * Resets all filters. 164 | * 165 | * Note: Recreates the filter fields also! 166 | */ 167 | public void resetFilters() { 168 | if (initDone) { 169 | disableContentRefreshing(); 170 | for (Component c : columnIdToFilterMap.values()) { 171 | c.setParent(null); 172 | } 173 | collapsedColumnIds.clear(); 174 | columnIdToFilterMap.clear(); 175 | generator.destroyFilterComponents(); 176 | generator.initializeFilterFields(); 177 | reRenderFilterFields = true; 178 | enableContentRefreshing(true); 179 | } 180 | } 181 | 182 | /** 183 | * Clears all filters without recreating the filter fields. 184 | */ 185 | public void clearFilters() { 186 | if (initDone) { 187 | generator.clearFilterData(); 188 | } 189 | } 190 | 191 | /** 192 | * Sets the FilterDecorator for this FilterTable. FilterDecorator may be 193 | * used to provide proper translated display names and icons for the enum, 194 | * boolean and date values used in the filters. 195 | * 196 | * Note: Recreates the filter fields also! 197 | * 198 | * @param decorator 199 | * An implementation of FilterDecorator to use with this 200 | * FilterTable. Remove by giving null as this parameter. 201 | */ 202 | public void setFilterDecorator(FilterDecorator decorator) { 203 | this.decorator = decorator; 204 | resetFilters(); 205 | } 206 | 207 | /** 208 | * Sets the FilterGenerator to use for providing custom Filters to the 209 | * container for one or more properties. 210 | * 211 | * @param generator 212 | * FilterGenerator to use with this FilterTable. Remove by giving 213 | * null as this parameter. 214 | */ 215 | public void setFilterGenerator(FilterGenerator generator) { 216 | filterGenerator = generator; 217 | } 218 | 219 | /** 220 | * Sets the Filter bar visible or hidden. 221 | * 222 | * @param filtersVisible 223 | * true to set the Filter bar visible. 224 | */ 225 | public void setFilterBarVisible(boolean filtersVisible) { 226 | this.filtersVisible = filtersVisible; 227 | for (Object key : columnIdToFilterMap.keySet()) { 228 | columnIdToFilterMap.get(key).setParent(filtersVisible ? this : null); 229 | } 230 | reRenderFilterFields = true; 231 | markAsDirty(); 232 | } 233 | 234 | /** 235 | * Returns the current visibility state of the filter bar. 236 | * 237 | * @return true if the filter bar is visible 238 | */ 239 | public boolean isFilterBarVisible() { 240 | return filtersVisible; 241 | } 242 | 243 | /** 244 | * Toggles the visibility of the filter field defined for the give column 245 | * ID. 246 | * 247 | * @param columnId 248 | * Column/Property ID of the filter to toggle 249 | * @param visible 250 | * true to set visible, false to set hidden 251 | */ 252 | public void setFilterFieldVisible(Object columnId, boolean visible) { 253 | Component component = columnIdToFilterMap.get(columnId); 254 | if (component != null) { 255 | component.setVisible(visible); 256 | reRenderFilterFields = true; 257 | markAsDirty(); 258 | } 259 | } 260 | 261 | /** 262 | * Returns visibility state of the filter field for the given column ID 263 | * 264 | * @param columnId 265 | * Column/Property ID of the filter field to query 266 | * @return true if filter is visible, false if it's hidden 267 | */ 268 | public boolean isFilterFieldVisible(Object columnId) { 269 | Component component = columnIdToFilterMap.get(columnId); 270 | if (component != null) { 271 | return component.isVisible(); 272 | } 273 | return false; 274 | } 275 | 276 | /** 277 | * Set a value of a filter field. Note that for Date filters you need to 278 | * provide a value of {@link DateInterval} type. 279 | * 280 | * @param propertyId 281 | * Property id for which to set the value 282 | * @param value 283 | * New value 284 | * @return true if setting succeeded, false if field was not found 285 | * @throws ConversionException 286 | * exception from the underlying field 287 | */ 288 | public boolean setFilterFieldValue(Object propertyId, Object value) throws ConversionException { 289 | Component field = getColumnIdToFilterMap().get(propertyId); 290 | boolean retVal = field != null; 291 | if (field != null) { 292 | ((AbstractField) field).setConvertedValue(value); 293 | } 294 | return retVal; 295 | } 296 | 297 | /** 298 | * Get the current value of a filter field 299 | * 300 | * @param propertyId 301 | * Property id from which to get the value 302 | * @return Current value 303 | */ 304 | public Object getFilterFieldValue(Object propertyId) { 305 | Component field = getColumnIdToFilterMap().get(propertyId); 306 | if (field != null) { 307 | return ((AbstractField) field).getValue(); 308 | } else { 309 | return null; 310 | } 311 | } 312 | 313 | /** 314 | * Returns the filter component instance associated with the given property 315 | * ID. 316 | * 317 | * @param propertyId 318 | * Property id for which to find the filter component. 319 | * @return Related component instance or null if not found. 320 | */ 321 | public Component getFilterField(Object propertyId) { 322 | return getColumnIdToFilterMap().get(propertyId); 323 | } 324 | 325 | @Override 326 | public Filterable getFilterable() { 327 | return getContainerDataSource() instanceof Filterable ? (Filterable) getContainerDataSource() : null; 328 | } 329 | 330 | @Override 331 | public FilterGenerator getFilterGenerator() { 332 | return filterGenerator; 333 | } 334 | 335 | @Override 336 | public FilterDecorator getFilterDecorator() { 337 | return decorator; 338 | } 339 | 340 | @Override 341 | public Map getColumnIdToFilterMap() { 342 | return columnIdToFilterMap; 343 | } 344 | 345 | @Override 346 | public HasComponents getAsComponent() { 347 | return this; 348 | } 349 | 350 | @Override 351 | public Iterator iterator() { 352 | Set children = new HashSet(); 353 | if (_visibleComponents != null) { 354 | children.addAll(_visibleComponents); 355 | } 356 | if (initDone && filtersVisible) { 357 | for (Object key : columnIdToFilterMap.keySet()) { 358 | Component filter = columnIdToFilterMap.get(key); 359 | if (equals(filter.getParent()) && filter.isVisible()) { 360 | children.add(filter); 361 | } 362 | } 363 | } 364 | return children.iterator(); 365 | } 366 | 367 | @Override 368 | public void setVisibleColumns(Object... visibleColumns) { 369 | reRenderFilterFields = true; 370 | if (visibleColumns != null && columnIdToFilterMap != null) { 371 | /* First clear all parent references */ 372 | for (Object key : columnIdToFilterMap.keySet()) { 373 | columnIdToFilterMap.get(key).setParent(null); 374 | } 375 | /* Set this as parent to visible columns */ 376 | for (Object key : visibleColumns) { 377 | Component filter = columnIdToFilterMap.get(key); 378 | if (filter != null && isFilterBarVisible()) { 379 | filter.setParent(this); 380 | } 381 | } 382 | } 383 | super.setVisibleColumns(visibleColumns); 384 | } 385 | 386 | @Override 387 | public void setRefreshingEnabled(boolean enabled) { 388 | if (enabled) { 389 | enableContentRefreshing(true); 390 | } else { 391 | disableContentRefreshing(); 392 | } 393 | } 394 | 395 | public void setFilterOnDemand(boolean filterOnDemand) { 396 | if (filtersRunOnDemand == filterOnDemand) { 397 | return; 398 | } else { 399 | filtersRunOnDemand = filterOnDemand; 400 | reRenderFilterFields = true; 401 | generator.setFilterOnDemandMode(filtersRunOnDemand); 402 | } 403 | 404 | } 405 | 406 | public boolean isFilterOnDemand() { 407 | return filtersRunOnDemand; 408 | } 409 | 410 | public void runFilters() { 411 | if (!filtersRunOnDemand) { 412 | throw new IllegalStateException("Can't run filters on demand when filtersRunOnDemand is set to false"); 413 | } 414 | generator.runFiltersNow(); 415 | } 416 | 417 | private Map getColumnHeaderStylenamesForPaint() { 418 | String[] allStyleNames = getColumnHeaderStylenames(); 419 | if (allStyleNames == null) { 420 | return null; 421 | } 422 | Map stylenamesForPaint = new HashMap<>(); 423 | Object[] visibleColumns = getVisibleColumns(); 424 | 425 | for (int i = 0; i < allStyleNames.length; i++) { 426 | Object colId = visibleColumns[i]; 427 | String stylename = allStyleNames[i]; 428 | // don't add collapsed columns 429 | if (!collapsedColumnIds.contains(colId) && stylename != null) { 430 | stylenamesForPaint.put(_columnIdMap.key(colId), stylename); 431 | } 432 | } 433 | 434 | return stylenamesForPaint; 435 | 436 | } 437 | 438 | /** 439 | * Gets the column filter wrapper style names of the columns. 440 | * 441 | * @return an array of the column filter wrapper style names or null if there aren't 442 | * style names set. 443 | */ 444 | public String[] getColumnHeaderStylenames() { 445 | if (columnHeaderStylenames.size() == 0) { 446 | return null; 447 | } 448 | 449 | Object[] visibleColumns = getVisibleColumns(); 450 | final String[] headerStylenames = new String[visibleColumns.length]; 451 | 452 | for (int i = 0; i < visibleColumns.length; i++) { 453 | headerStylenames[i] = columnHeaderStylenames.get(visibleColumns[i]); 454 | } 455 | return headerStylenames; 456 | } 457 | 458 | /** 459 | * Sets the column filter wrapper style names of columns. 460 | * 461 | * @param headerStylenames 462 | * an array of the column filter wrapper style names that match the 463 | * {@link #getVisibleColumns()} method 464 | */ 465 | public void setColumnHeaderStylenames(String... headerStylenames) { 466 | Object[] visibleColumns = getVisibleColumns(); 467 | 468 | if (headerStylenames.length != visibleColumns.length) { 469 | throw new IllegalArgumentException( 470 | "The length of the header style names array must match the number of visible columns"); 471 | } 472 | 473 | columnHeaderStylenames.clear(); 474 | for (int i = 0; i < visibleColumns.length; i++) { 475 | columnHeaderStylenames.put(visibleColumns[i], headerStylenames[i]); 476 | } 477 | 478 | markAsDirty(); 479 | } 480 | 481 | /** 482 | * Sets the column filter wrapper style name for the specified column. 483 | * 484 | * @param propertyId 485 | * the propertyId identifying the column 486 | * @param headerStylename 487 | * the column filter wrapper style name to set 488 | */ 489 | public void setColumnHeaderStylename(Object propertyId, String headerStylename) { 490 | 491 | if (headerStylename == null) { 492 | columnHeaderStylenames.remove(propertyId); 493 | } else { 494 | columnHeaderStylenames.put(propertyId, headerStylename); 495 | } 496 | 497 | markAsDirty(); 498 | } 499 | 500 | /** 501 | * Gets the column filter wrapper style name for the specified column. 502 | * 503 | * @param propertyId 504 | * the propertyId identifying the column. 505 | * @return the column filter wrapper style name for the specified column if it has one. 506 | */ 507 | public String getColumnHeaderStylename(Object propertyId) { 508 | if (getColumnHeaderMode() == ColumnHeaderMode.HIDDEN) { 509 | return null; 510 | } 511 | return columnHeaderStylenames.get(propertyId); 512 | } 513 | } -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/FilterTreeTable.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.HashMap; 6 | import java.util.HashSet; 7 | import java.util.Iterator; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | import org.tepi.filtertable.FilterFieldGenerator.IFilterTable; 13 | import org.tepi.filtertable.client.ui.FilterTableConnector; 14 | import org.tepi.filtertable.datefilter.DateInterval; 15 | 16 | import com.vaadin.server.KeyMapper; 17 | import com.vaadin.server.LegacyPaint; 18 | import com.vaadin.server.PaintException; 19 | import com.vaadin.server.PaintTarget; 20 | import com.vaadin.ui.Component; 21 | import com.vaadin.ui.HasComponents; 22 | import com.vaadin.v7.data.Container; 23 | import com.vaadin.v7.data.util.converter.Converter.ConversionException; 24 | import com.vaadin.v7.ui.AbstractField; 25 | import com.vaadin.v7.ui.TextField; 26 | import com.vaadin.v7.ui.TreeTable; 27 | 28 | /** 29 | * FilterTreeTable is an extension of the Vaadin TreeTable component that 30 | * provides automatically generated filter fields for each column. 31 | * 32 | * @author Teppo Kurki 33 | * 34 | */ 35 | @SuppressWarnings({ "serial", "deprecation" }) 36 | public class FilterTreeTable extends TreeTable implements IFilterTable { 37 | /* Maps property id's to column filter components */ 38 | private final Map columnIdToFilterMap = new HashMap(); 39 | /* Internal list of currently collapsed column id:s */ 40 | private final Set collapsedColumnIds = new HashSet(); 41 | /* Set to true to show the filter components */ 42 | private boolean filtersVisible; 43 | /* Filter Generator and Decorator */ 44 | private FilterGenerator filterGenerator; 45 | private FilterDecorator decorator; 46 | /* FilterFieldGenerator instance */ 47 | private final FilterFieldGenerator generator; 48 | /* Is initialization done */ 49 | private final boolean initDone; 50 | /* Force-render filter fields */ 51 | private boolean reRenderFilterFields; 52 | /* Wrap filters with additional div for styling? */ 53 | private boolean wrapFilters = false; 54 | /* Are filters run immediately, or only on demand? */ 55 | private boolean filtersRunOnDemand = false; 56 | /* Custom column header style names */ 57 | private final HashMap columnHeaderStylenames = new HashMap(); 58 | /* Fields from Table accessed via reflection */ 59 | private KeyMapper _columnIdMap; 60 | private HashSet _visibleComponents; 61 | 62 | /** 63 | * Creates a new empty FilterTable 64 | */ 65 | public FilterTreeTable() { 66 | this(null); 67 | } 68 | 69 | /** 70 | * Creates a new empty FilterTable with the given caption 71 | * 72 | * @param caption 73 | * Caption to set for the FilterTable 74 | */ 75 | @SuppressWarnings("unchecked") 76 | public FilterTreeTable(String caption) { 77 | super(caption); 78 | try { 79 | java.lang.reflect.Field field = com.vaadin.v7.ui.Table.class.getDeclaredField("columnIdMap"); 80 | field.setAccessible(true); 81 | _columnIdMap = (KeyMapper) field.get(this); 82 | field = com.vaadin.v7.ui.Table.class.getDeclaredField("visibleComponents"); 83 | field.setAccessible(true); 84 | _visibleComponents = (HashSet) field.get(this); 85 | } catch (Exception exception) { 86 | throw new IllegalArgumentException("Unable to get columnIdMap or visibleComponents", exception); 87 | } 88 | generator = new FilterFieldGenerator(this); 89 | initDone = true; 90 | } 91 | 92 | @Override 93 | public void paintContent(PaintTarget target) throws PaintException { 94 | super.paintContent(target); 95 | /* Add filter components to UIDL */ 96 | target.startTag(FilterTableConnector.TAG_FILTERS); 97 | target.addAttribute(FilterTableConnector.ATTRIBUTE_FILTERS_VISIBLE, filtersVisible); 98 | target.addAttribute(FilterTableConnector.ATTRIBUTE_FORCE_RENDER, reRenderFilterFields); 99 | reRenderFilterFields = false; 100 | for (Object key : getColumnIdToFilterMap().keySet()) { 101 | /* Make sure parent is set properly */ 102 | if (columnIdToFilterMap.get(key) != null && columnIdToFilterMap.get(key).getParent() == null) { 103 | continue; 104 | } 105 | /* Paint the filter field */ 106 | target.startTag(FilterTableConnector.TAG_FILTER_COMPONENT + _columnIdMap.key(key)); 107 | target.addAttribute(FilterTableConnector.ATTRIBUTE_COLUMN_ID, _columnIdMap.key(key)); 108 | Component c = getColumnIdToFilterMap().get(key); 109 | LegacyPaint.paint(c, target); 110 | target.endTag(FilterTableConnector.TAG_FILTER_COMPONENT + _columnIdMap.key(key)); 111 | } 112 | target.endTag(FilterTableConnector.TAG_FILTERS); 113 | 114 | String[] headerStylenames = getColumnHeaderStylenamesForPaint(); 115 | if (headerStylenames != null) { 116 | target.addAttribute(FilterTableConnector.ATTRIBUTE_COLUMN_HEADER_STYLE_NAMES, headerStylenames); 117 | } 118 | } 119 | 120 | @Override 121 | public void setColumnCollapsed(Object propertyId, boolean collapsed) throws IllegalStateException { 122 | super.setColumnCollapsed(propertyId, collapsed); 123 | Component c = getColumnIdToFilterMap().get(propertyId); 124 | if (collapsed) { 125 | collapsedColumnIds.add(propertyId); 126 | if (c != null) { 127 | c.setParent(null); 128 | if (c instanceof TextField) { 129 | ((TextField) c).setValue(""); 130 | } else if (c instanceof AbstractField) { 131 | ((AbstractField) c).setValue(null); 132 | } 133 | } 134 | } else { 135 | if (c != null) { 136 | c.setParent(this); 137 | } 138 | collapsedColumnIds.remove(propertyId); 139 | } 140 | reRenderFilterFields = true; 141 | markAsDirty(); 142 | } 143 | 144 | @Override 145 | public void setContainerDataSource(Container newDataSource) { 146 | super.setContainerDataSource(newDataSource); 147 | } 148 | 149 | @Override 150 | public void setContainerDataSource(Container newDataSource, Collection visibleIds) { 151 | super.setContainerDataSource(newDataSource, visibleIds); 152 | resetFilters(); 153 | } 154 | 155 | /** 156 | * Resets all filters. 157 | * 158 | * Note: Recreates the filter fields also! 159 | */ 160 | public void resetFilters() { 161 | if (initDone) { 162 | disableContentRefreshing(); 163 | for (Component c : columnIdToFilterMap.values()) { 164 | c.setParent(null); 165 | } 166 | collapsedColumnIds.clear(); 167 | columnIdToFilterMap.clear(); 168 | generator.destroyFilterComponents(); 169 | generator.initializeFilterFields(); 170 | reRenderFilterFields = true; 171 | enableContentRefreshing(true); 172 | } 173 | } 174 | 175 | /** 176 | * Clears all filters without recreating the filter fields. 177 | */ 178 | public void clearFilters() { 179 | if (initDone) { 180 | generator.clearFilterData(); 181 | } 182 | } 183 | 184 | /** 185 | * Sets the FilterDecorator for this FilterTable. FilterDecorator may be 186 | * used to provide proper translated display names and icons for the enum, 187 | * boolean and date values used in the filters. 188 | * 189 | * Note: Recreates the filter fields also! 190 | * 191 | * @param decorator 192 | * An implementation of FilterDecorator to use with this 193 | * FilterTable. Remove by giving null as this parameter. 194 | */ 195 | public void setFilterDecorator(FilterDecorator decorator) { 196 | this.decorator = decorator; 197 | resetFilters(); 198 | } 199 | 200 | /** 201 | * Sets the FilterGenerator to use for providing custom Filters to the 202 | * container for one or more properties. 203 | * 204 | * @param generator 205 | * FilterGenerator to use with this FilterTable. Remove by giving 206 | * null as this parameter. 207 | */ 208 | public void setFilterGenerator(FilterGenerator generator) { 209 | filterGenerator = generator; 210 | } 211 | 212 | /** 213 | * Sets the Filter bar visible or hidden. 214 | * 215 | * @param filtersVisible 216 | * true to set the Filter bar visible. 217 | */ 218 | public void setFilterBarVisible(boolean filtersVisible) { 219 | this.filtersVisible = filtersVisible; 220 | reRenderFilterFields = true; 221 | markAsDirty(); 222 | } 223 | 224 | /** 225 | * Returns the current visibility state of the filter bar. 226 | * 227 | * @return true if the filter bar is visible 228 | */ 229 | public boolean isFilterBarVisible() { 230 | return filtersVisible; 231 | } 232 | 233 | /** 234 | * Toggles the visibility of the filter field defined for the give column 235 | * ID. 236 | * 237 | * @param columnId 238 | * Column/Property ID of the filter to toggle 239 | * @param visible 240 | * true to set visible, false to set hidden 241 | */ 242 | public void setFilterFieldVisible(Object columnId, boolean visible) { 243 | Component component = columnIdToFilterMap.get(columnId); 244 | if (component != null) { 245 | component.setVisible(visible); 246 | reRenderFilterFields = true; 247 | markAsDirty(); 248 | } 249 | } 250 | 251 | /** 252 | * Returns visibility state of the filter field for the given column ID 253 | * 254 | * @param columnId 255 | * Column/Property ID of the filter field to query 256 | * @return true if filter is visible, false if it's hidden 257 | */ 258 | public boolean isFilterFieldVisible(Object columnId) { 259 | Component component = columnIdToFilterMap.get(columnId); 260 | if (component != null) { 261 | return component.isVisible(); 262 | } 263 | return false; 264 | } 265 | 266 | /** 267 | * Set a value of a filter field. Note that for Date filters you need to 268 | * provide a value of {@link DateInterval} type. 269 | * 270 | * @param propertyId 271 | * Property id for which to set the value 272 | * @param value 273 | * New value 274 | * @return true if setting succeeded, false if field was not found 275 | * @throws ConversionException 276 | * exception from the underlying field 277 | */ 278 | public boolean setFilterFieldValue(Object propertyId, Object value) throws ConversionException { 279 | Component field = getColumnIdToFilterMap().get(propertyId); 280 | boolean retVal = field != null; 281 | if (field != null) { 282 | ((AbstractField) field).setConvertedValue(value); 283 | } 284 | return retVal; 285 | } 286 | 287 | /** 288 | * Get the current value of a filter field 289 | * 290 | * @param propertyId 291 | * Property id from which to get the value 292 | * @return Current value 293 | */ 294 | public Object getFilterFieldValue(Object propertyId) { 295 | Component field = getColumnIdToFilterMap().get(propertyId); 296 | if (field != null) { 297 | return ((AbstractField) field).getValue(); 298 | } else { 299 | return null; 300 | } 301 | } 302 | 303 | /** 304 | * Returns the filter component instance associated with the given property 305 | * ID. 306 | * 307 | * @param propertyId 308 | * Property id for which to find the filter component. 309 | * @return Related component instance or null if not found. 310 | */ 311 | public Component getFilterField(Object propertyId) { 312 | return getColumnIdToFilterMap().get(propertyId); 313 | } 314 | 315 | @Override 316 | public Filterable getFilterable() { 317 | return getContainerDataSource() instanceof Filterable ? (Filterable) getContainerDataSource() : null; 318 | } 319 | 320 | @Override 321 | public FilterGenerator getFilterGenerator() { 322 | return filterGenerator; 323 | } 324 | 325 | @Override 326 | public FilterDecorator getFilterDecorator() { 327 | return decorator; 328 | } 329 | 330 | @Override 331 | public Map getColumnIdToFilterMap() { 332 | return columnIdToFilterMap; 333 | } 334 | 335 | @Override 336 | public HasComponents getAsComponent() { 337 | return this; 338 | } 339 | 340 | @Override 341 | public Iterator iterator() { 342 | Set children = new HashSet(); 343 | if (_visibleComponents != null) { 344 | children.addAll(_visibleComponents); 345 | } 346 | if (initDone) { 347 | for (Object key : columnIdToFilterMap.keySet()) { 348 | Component filter = columnIdToFilterMap.get(key); 349 | if (equals(filter.getParent())) { 350 | children.add(filter); 351 | } 352 | } 353 | } 354 | return children.iterator(); 355 | } 356 | 357 | @Override 358 | public void setVisibleColumns(Object... visibleColumns) { 359 | reRenderFilterFields = true; 360 | if (visibleColumns != null && columnIdToFilterMap != null) { 361 | /* First clear all parent references */ 362 | for (Object key : columnIdToFilterMap.keySet()) { 363 | columnIdToFilterMap.get(key).setParent(null); 364 | } 365 | /* Set this as parent to visible columns */ 366 | for (Object key : visibleColumns) { 367 | Component filter = columnIdToFilterMap.get(key); 368 | if (filter != null) { 369 | filter.setParent(this); 370 | } 371 | } 372 | } 373 | super.setVisibleColumns(visibleColumns); 374 | resetFilters(); 375 | } 376 | 377 | @Override 378 | public void setRefreshingEnabled(boolean enabled) { 379 | if (enabled) { 380 | enableContentRefreshing(true); 381 | } else { 382 | disableContentRefreshing(); 383 | } 384 | } 385 | 386 | public void setWrapFilters(boolean wrapFilters) { 387 | if (this.wrapFilters == wrapFilters) { 388 | return; 389 | } else { 390 | this.wrapFilters = wrapFilters; 391 | reRenderFilterFields = true; 392 | markAsDirty(); 393 | } 394 | } 395 | 396 | public boolean isWrapFilters() { 397 | return wrapFilters; 398 | } 399 | 400 | public void setFilterOnDemand(boolean filterOnDemand) { 401 | if (filtersRunOnDemand == filterOnDemand) { 402 | return; 403 | } else { 404 | filtersRunOnDemand = filterOnDemand; 405 | reRenderFilterFields = true; 406 | generator.setFilterOnDemandMode(filtersRunOnDemand); 407 | } 408 | 409 | } 410 | 411 | public boolean isFilterOnDemand() { 412 | return filtersRunOnDemand; 413 | } 414 | 415 | public void runFilters() { 416 | if (!filtersRunOnDemand) { 417 | throw new IllegalStateException("Can't run filters on demand when filtersRunOnDemand is set to false"); 418 | } 419 | generator.runFiltersNow(); 420 | } 421 | 422 | private String[] getColumnHeaderStylenamesForPaint() { 423 | String[] allStyleNames = getColumnHeaderStylenames(); 424 | if (allStyleNames == null) { 425 | return null; 426 | } 427 | List stylenamesForPaint = new ArrayList(); 428 | Object[] visibleColumns = getVisibleColumns(); 429 | 430 | for (int i = 0; i < allStyleNames.length; i++) { 431 | Object colId = visibleColumns[i]; 432 | String stylename = allStyleNames[i]; 433 | // don't add collapsed columns 434 | if (!collapsedColumnIds.contains(colId)) { 435 | if (stylename == null) { 436 | // replace nulls with empty strings 437 | stylenamesForPaint.add(""); 438 | } else { 439 | stylenamesForPaint.add(stylename); 440 | } 441 | } 442 | } 443 | 444 | return stylenamesForPaint.toArray(new String[stylenamesForPaint.size()]); 445 | 446 | } 447 | 448 | /** 449 | * Gets the column filter wrapper style names of the columns. 450 | * 451 | * @return an array of the column filter wrapper style names or null if 452 | * there aren't style names set. 453 | */ 454 | public String[] getColumnHeaderStylenames() { 455 | if (columnHeaderStylenames.size() == 0) { 456 | return null; 457 | } 458 | 459 | Object[] visibleColumns = getVisibleColumns(); 460 | final String[] headerStylenames = new String[visibleColumns.length]; 461 | 462 | for (int i = 0; i < visibleColumns.length; i++) { 463 | headerStylenames[i] = columnHeaderStylenames.get(visibleColumns[i]); 464 | } 465 | return headerStylenames; 466 | } 467 | 468 | /** 469 | * Sets the column filter wrapper style names of columns. 470 | * 471 | * @param headerStylenames 472 | * an array of the column filter wrapper style names that match 473 | * the {@link #getVisibleColumns()} method 474 | */ 475 | public void setColumnHeaderStylenames(String... headerStylenames) { 476 | Object[] visibleColumns = getVisibleColumns(); 477 | 478 | if (headerStylenames.length != visibleColumns.length) { 479 | throw new IllegalArgumentException( 480 | "The length of the header style names array must match the number of visible columns"); 481 | } 482 | 483 | columnHeaderStylenames.clear(); 484 | for (int i = 0; i < visibleColumns.length; i++) { 485 | columnHeaderStylenames.put(visibleColumns[i], headerStylenames[i]); 486 | } 487 | 488 | markAsDirty(); 489 | } 490 | 491 | /** 492 | * Sets the column filter wrapper style name for the specified column. 493 | * 494 | * @param propertyId 495 | * the propertyId identifying the column 496 | * @param headerStylename 497 | * the column filter wrapper style name to set 498 | */ 499 | public void setColumnHeaderStylename(Object propertyId, String headerStylename) { 500 | 501 | if (headerStylename == null) { 502 | columnHeaderStylenames.remove(propertyId); 503 | } else { 504 | columnHeaderStylenames.put(propertyId, headerStylename); 505 | } 506 | 507 | markAsDirty(); 508 | } 509 | 510 | /** 511 | * Gets the column filter wrapper style name for the specified column. 512 | * 513 | * @param propertyId 514 | * the propertyId identifying the column. 515 | * @return the column filter wrapper style name for the specified column if 516 | * it has one. 517 | */ 518 | public String getColumnHeaderStylename(Object propertyId) { 519 | if (getColumnHeaderMode() == ColumnHeaderMode.HIDDEN) { 520 | return null; 521 | } 522 | return columnHeaderStylenames.get(propertyId); 523 | } 524 | } 525 | -------------------------------------------------------------------------------- /filteringtable-demo/src/main/java/org/tepi/filtertable/demo/FilterTableDemoUI.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable.demo; 2 | 3 | import java.sql.Timestamp; 4 | import java.util.Calendar; 5 | import java.util.Date; 6 | import java.util.Locale; 7 | import java.util.Random; 8 | 9 | import javax.servlet.annotation.WebServlet; 10 | 11 | import org.tepi.filtertable.FilterTable; 12 | import org.tepi.filtertable.FilterTreeTable; 13 | import org.tepi.filtertable.paged.PagedFilterControlConfig; 14 | import org.tepi.filtertable.paged.PagedFilterTable; 15 | 16 | import com.vaadin.annotations.StyleSheet; 17 | import com.vaadin.annotations.Theme; 18 | import com.vaadin.annotations.Title; 19 | import com.vaadin.annotations.VaadinServletConfiguration; 20 | import com.vaadin.server.VaadinRequest; 21 | import com.vaadin.server.VaadinServlet; 22 | import com.vaadin.shared.ui.MarginInfo; 23 | import com.vaadin.ui.Alignment; 24 | import com.vaadin.ui.Button; 25 | import com.vaadin.ui.Button.ClickEvent; 26 | import com.vaadin.ui.CheckBox; 27 | import com.vaadin.ui.Component; 28 | import com.vaadin.ui.HorizontalLayout; 29 | import com.vaadin.ui.Label; 30 | import com.vaadin.ui.Panel; 31 | import com.vaadin.ui.TabSheet; 32 | import com.vaadin.ui.UI; 33 | import com.vaadin.ui.VerticalLayout; 34 | import com.vaadin.ui.themes.ValoTheme; 35 | import com.vaadin.v7.data.Container; 36 | import com.vaadin.v7.data.Container.Filter; 37 | import com.vaadin.v7.data.Container.Filterable; 38 | import com.vaadin.v7.data.Item; 39 | import com.vaadin.v7.data.util.HierarchicalContainer; 40 | import com.vaadin.v7.data.util.IndexedContainer; 41 | import com.vaadin.v7.ui.Table; 42 | import com.vaadin.v7.ui.Table.RowHeaderMode; 43 | 44 | @SuppressWarnings("serial") 45 | @Title("FilterTable Demo Application") 46 | @Theme("valo") 47 | public class FilterTableDemoUI extends UI { 48 | 49 | @WebServlet(value = "/*", asyncSupported = true) 50 | @VaadinServletConfiguration(productionMode = false, ui = FilterTableDemoUI.class, widgetset = "org.tepi.filtertable.demo.FilterTableDemoWidgetset") 51 | public static class Servlet extends VaadinServlet { 52 | } 53 | 54 | /** 55 | * Example enum for enum filtering feature 56 | */ 57 | enum State { 58 | CREATED, PROCESSING, PROCESSED, FINISHED; 59 | } 60 | 61 | @Override 62 | protected void init(VaadinRequest request) { 63 | setLocale(new Locale("fi", "FI")); 64 | VerticalLayout mainLayout = new VerticalLayout(); 65 | mainLayout.setMargin(new MarginInfo(true, false, false, false)); 66 | mainLayout.setSizeFull(); 67 | 68 | final TabSheet ts = new TabSheet(); 69 | ts.setStyleName(ValoTheme.TABSHEET_COMPACT_TABBAR); 70 | ts.setSizeFull(); 71 | mainLayout.addComponent(ts); 72 | mainLayout.setExpandRatio(ts, 1); 73 | 74 | ts.addTab(buildTableTab(), "Normal Table"); 75 | ts.addTab(buildNormalTableTab(), "Normal FilterTable"); 76 | ts.addTab(buildPagedTableTab(), "Paged FilterTable"); 77 | ts.addTab(buildTreeTableTab(), "FilterTreeTable"); 78 | 79 | setContent(mainLayout); 80 | } 81 | 82 | private Component buildTableTab() { 83 | /* Create FilterTable */ 84 | final Table normalFilterTable = buildTable(); 85 | final VerticalLayout mainLayout = new VerticalLayout(); 86 | mainLayout.setSizeFull(); 87 | mainLayout.setSpacing(true); 88 | mainLayout.setMargin(true); 89 | mainLayout.addComponent(normalFilterTable); 90 | mainLayout.setExpandRatio(normalFilterTable, 1); 91 | 92 | Button addFilter = new Button("Add a filter"); 93 | addFilter.addClickListener(event -> { 94 | Filterable f = ((Filterable) normalFilterTable.getContainerDataSource()); 95 | f.addContainerFilter(new Filter() { 96 | 97 | @Override 98 | public boolean passesFilter(Object itemId, Item item) throws UnsupportedOperationException { 99 | return item != null && ((String) item.getItemProperty("name").getValue()).contains("5"); 100 | } 101 | 102 | @Override 103 | public boolean appliesToProperty(Object propertyId) { 104 | return "name".equals(propertyId); 105 | } 106 | }); 107 | }); 108 | mainLayout.addComponent(addFilter); 109 | 110 | Panel p = new Panel(); 111 | p.setSizeFull(); 112 | p.setContent(mainLayout); 113 | 114 | return p; 115 | } 116 | 117 | private Component buildNormalTableTab() { 118 | /* Create FilterTable */ 119 | FilterTable normalFilterTable = buildFilterTable(); 120 | final VerticalLayout mainLayout = new VerticalLayout(); 121 | mainLayout.setSizeFull(); 122 | mainLayout.setSpacing(true); 123 | mainLayout.setMargin(true); 124 | mainLayout.addComponent(normalFilterTable); 125 | mainLayout.setExpandRatio(normalFilterTable, 1); 126 | mainLayout.addComponent(buildButtons(normalFilterTable)); 127 | 128 | Panel p = new Panel(); 129 | p.setSizeFull(); 130 | p.setContent(mainLayout); 131 | 132 | return p; 133 | } 134 | 135 | private Table buildTable() { 136 | Table filterTable = new Table(); 137 | filterTable.setSizeFull(); 138 | 139 | filterTable.setSelectable(true); 140 | filterTable.setImmediate(true); 141 | filterTable.setMultiSelect(true); 142 | 143 | filterTable.setColumnCollapsingAllowed(true); 144 | 145 | filterTable.setColumnReorderingAllowed(true); 146 | 147 | filterTable.setContainerDataSource(buildContainer()); 148 | 149 | // filterTable.setColumnCollapsed("state", true); 150 | 151 | filterTable 152 | .setVisibleColumns((Object[]) new String[] { "name", "id", "state", "date", "validated", "checked", "comp" }); 153 | 154 | filterTable 155 | .setItemDescriptionGenerator((source, itemId, propertyId) -> "Just testing ItemDescriptionGenerator"); 156 | 157 | return filterTable; 158 | } 159 | 160 | private FilterTable buildFilterTable() { 161 | FilterTable filterTable = new FilterTable(); 162 | filterTable.setSizeFull(); 163 | filterTable.setFilterDecorator(new DemoFilterDecorator()); 164 | filterTable.setFilterGenerator(new DemoFilterGenerator()); 165 | 166 | filterTable.setFilterBarVisible(true); 167 | 168 | filterTable.setSelectable(true); 169 | filterTable.setImmediate(true); 170 | filterTable.setMultiSelect(true); 171 | 172 | filterTable.setRowHeaderMode(RowHeaderMode.INDEX); 173 | 174 | filterTable.setColumnCollapsingAllowed(true); 175 | 176 | filterTable.setColumnReorderingAllowed(true); 177 | 178 | filterTable.setContainerDataSource(buildContainer()); 179 | 180 | // filterTable.setColumnCollapsed("state", true); 181 | 182 | filterTable 183 | .setVisibleColumns((Object[]) new String[] { "name", "id", "state", "date", "validated", "checked", "comp" }); 184 | 185 | filterTable 186 | .setItemDescriptionGenerator((source, itemId, propertyId) -> "Just testing ItemDescriptionGenerator"); 187 | filterTable.setColumnHeaderStylename("id", "headerStylename-testing"); 188 | return filterTable; 189 | } 190 | 191 | private Component buildPagedTableTab() { 192 | /* Create FilterTable */ 193 | PagedFilterTable pagedFilterTable = buildPagedFilterTable(); 194 | 195 | final VerticalLayout mainLayout = new VerticalLayout(); 196 | mainLayout.setSpacing(true); 197 | mainLayout.setMargin(true); 198 | mainLayout.addComponent(pagedFilterTable); 199 | mainLayout.addComponent(pagedFilterTable.createControls(new PagedFilterControlConfig())); 200 | mainLayout.addComponent(buildButtons(pagedFilterTable)); 201 | return mainLayout; 202 | } 203 | 204 | private PagedFilterTable buildPagedFilterTable() { 205 | PagedFilterTable filterTable = new PagedFilterTable(); 206 | filterTable.setWidth("100%"); 207 | 208 | filterTable.setFilterDecorator(new DemoFilterDecorator()); 209 | filterTable.setFilterGenerator(new DemoFilterGenerator()); 210 | 211 | filterTable.setFilterBarVisible(true); 212 | 213 | filterTable.setSelectable(true); 214 | filterTable.setImmediate(true); 215 | filterTable.setMultiSelect(true); 216 | 217 | filterTable.setRowHeaderMode(RowHeaderMode.INDEX); 218 | 219 | filterTable.setColumnCollapsingAllowed(true); 220 | 221 | filterTable.setColumnReorderingAllowed(true); 222 | 223 | filterTable.setContainerDataSource(buildContainer()); 224 | 225 | filterTable.setPageLength(10); 226 | 227 | // filterTable.setColumnCollapsed("state", true); 228 | 229 | filterTable 230 | .setVisibleColumns((Object[]) new String[] { "name", "id", "state", "date", "validated", "checked" }); 231 | 232 | return filterTable; 233 | } 234 | 235 | private Component buildTreeTableTab() { 236 | /* Create FilterTable */ 237 | FilterTreeTable filterTreeTable = buildFilterTreeTable(); 238 | 239 | final VerticalLayout mainLayout = new VerticalLayout(); 240 | mainLayout.setSizeFull(); 241 | mainLayout.setSpacing(true); 242 | mainLayout.setMargin(true); 243 | mainLayout.addComponent(filterTreeTable); 244 | mainLayout.setExpandRatio(filterTreeTable, 1); 245 | mainLayout.addComponent(buildButtons(filterTreeTable)); 246 | 247 | Panel p = new Panel(); 248 | p.setSizeFull(); 249 | p.setContent(mainLayout); 250 | 251 | return p; 252 | } 253 | 254 | private FilterTreeTable buildFilterTreeTable() { 255 | FilterTreeTable filterTable = new FilterTreeTable(); 256 | filterTable.setSizeFull(); 257 | 258 | filterTable.setFilterDecorator(new DemoFilterDecorator()); 259 | filterTable.setFilterGenerator(new DemoFilterGenerator()); 260 | 261 | filterTable.setFilterBarVisible(true); 262 | 263 | filterTable.setSelectable(true); 264 | filterTable.setImmediate(true); 265 | filterTable.setMultiSelect(true); 266 | 267 | filterTable.setRowHeaderMode(RowHeaderMode.INDEX); 268 | 269 | filterTable.setColumnCollapsingAllowed(true); 270 | 271 | filterTable.setColumnReorderingAllowed(true); 272 | 273 | filterTable.setContainerDataSource(buildHierarchicalContainer()); 274 | // filterTable.setColumnCollapsed("state", true); 275 | 276 | filterTable 277 | .setVisibleColumns((Object[]) new String[] { "name", "id", "state", "date", "validated", "checked" }); 278 | 279 | return filterTable; 280 | } 281 | 282 | private Component buildButtons(final FilterTable relatedFilterTable) { 283 | HorizontalLayout buttonLayout = new HorizontalLayout(); 284 | buttonLayout.setHeight(null); 285 | buttonLayout.setWidth("100%"); 286 | buttonLayout.setSpacing(true); 287 | 288 | Label hideFilters = new Label("Show Filters:"); 289 | hideFilters.setSizeUndefined(); 290 | buttonLayout.addComponent(hideFilters); 291 | buttonLayout.setComponentAlignment(hideFilters, Alignment.MIDDLE_LEFT); 292 | 293 | for (Object propId : relatedFilterTable.getContainerPropertyIds()) { 294 | Component t = createToggle(relatedFilterTable, propId); 295 | buttonLayout.addComponent(t); 296 | buttonLayout.setComponentAlignment(t, Alignment.MIDDLE_LEFT); 297 | } 298 | 299 | CheckBox showFilters = new CheckBox("Toggle Filter Bar visibility"); 300 | showFilters.setValue(relatedFilterTable.isFilterBarVisible()); 301 | showFilters.addValueChangeListener(event -> relatedFilterTable.setFilterBarVisible((Boolean) event.getValue())); 302 | buttonLayout.addComponent(showFilters); 303 | buttonLayout.setComponentAlignment(showFilters, Alignment.MIDDLE_RIGHT); 304 | buttonLayout.setExpandRatio(showFilters, 1); 305 | 306 | final Button runNow = new Button("Filter now"); 307 | runNow.addClickListener(new Button.ClickListener() { 308 | 309 | @Override 310 | public void buttonClick(ClickEvent event) { 311 | relatedFilterTable.runFilters(); 312 | } 313 | }); 314 | 315 | CheckBox runOnDemand = new CheckBox("Filter lazily"); 316 | runOnDemand.setValue(relatedFilterTable.isFilterOnDemand()); 317 | runNow.setEnabled(relatedFilterTable.isFilterOnDemand()); 318 | runOnDemand.addValueChangeListener(event -> { 319 | boolean value = event.getValue(); 320 | relatedFilterTable.setFilterOnDemand(value); 321 | runNow.setEnabled(value); 322 | }); 323 | buttonLayout.addComponent(runOnDemand); 324 | buttonLayout.setComponentAlignment(runOnDemand, Alignment.MIDDLE_RIGHT); 325 | buttonLayout.addComponent(runNow); 326 | 327 | Button setVal = new Button("Set the State filter to 'Processed'"); 328 | setVal.addClickListener(event -> relatedFilterTable.setFilterFieldValue("state", State.PROCESSED)); 329 | buttonLayout.addComponent(setVal); 330 | 331 | Button reset = new Button("Reset"); 332 | reset.addClickListener(event -> relatedFilterTable.resetFilters()); 333 | buttonLayout.addComponent(reset); 334 | 335 | Button clear = new Button("Clear"); 336 | clear.addClickListener(event -> relatedFilterTable.clearFilters()); 337 | buttonLayout.addComponent(clear); 338 | 339 | return buttonLayout; 340 | } 341 | 342 | private Component buildButtons(final FilterTreeTable relatedFilterTable) { 343 | HorizontalLayout buttonLayout = new HorizontalLayout(); 344 | buttonLayout.setHeight(null); 345 | buttonLayout.setWidth("100%"); 346 | buttonLayout.setSpacing(true); 347 | 348 | Label hideFilters = new Label("Show Filters:"); 349 | hideFilters.setSizeUndefined(); 350 | buttonLayout.addComponent(hideFilters); 351 | buttonLayout.setComponentAlignment(hideFilters, Alignment.MIDDLE_LEFT); 352 | 353 | for (Object propId : relatedFilterTable.getContainerPropertyIds()) { 354 | Component t = createToggle(relatedFilterTable, propId); 355 | buttonLayout.addComponent(t); 356 | buttonLayout.setComponentAlignment(t, Alignment.MIDDLE_LEFT); 357 | } 358 | 359 | CheckBox showFilters = new CheckBox("Toggle Filter Bar visibility"); 360 | showFilters.setValue(relatedFilterTable.isFilterBarVisible()); 361 | showFilters.addValueChangeListener(event -> relatedFilterTable.setFilterBarVisible(event.getValue())); 362 | buttonLayout.addComponent(showFilters); 363 | buttonLayout.setComponentAlignment(showFilters, Alignment.MIDDLE_RIGHT); 364 | buttonLayout.setExpandRatio(showFilters, 1); 365 | 366 | CheckBox wrapFilters = new CheckBox("Wrap Filter Fields"); 367 | wrapFilters.setValue(relatedFilterTable.isWrapFilters()); 368 | wrapFilters.addValueChangeListener(event -> relatedFilterTable.setWrapFilters(event.getValue())); 369 | buttonLayout.addComponent(wrapFilters); 370 | buttonLayout.setComponentAlignment(wrapFilters, Alignment.MIDDLE_RIGHT); 371 | 372 | final Button runNow = new Button("Filter now"); 373 | runNow.addClickListener(new Button.ClickListener() { 374 | 375 | @Override 376 | public void buttonClick(ClickEvent event) { 377 | relatedFilterTable.runFilters(); 378 | } 379 | }); 380 | 381 | CheckBox runOnDemand = new CheckBox("Filter lazily"); 382 | runOnDemand.setValue(relatedFilterTable.isFilterOnDemand()); 383 | runNow.setEnabled(relatedFilterTable.isFilterOnDemand()); 384 | runOnDemand.addValueChangeListener(event -> { 385 | boolean value = event.getValue(); 386 | relatedFilterTable.setFilterOnDemand(value); 387 | runNow.setEnabled(value); 388 | }); 389 | buttonLayout.addComponent(runOnDemand); 390 | buttonLayout.setComponentAlignment(runOnDemand, Alignment.MIDDLE_RIGHT); 391 | buttonLayout.addComponent(runNow); 392 | 393 | Button setVal = new Button("Set the State filter to 'Processed'"); 394 | setVal.addClickListener(event -> relatedFilterTable.setFilterFieldValue("state", State.PROCESSED)); 395 | buttonLayout.addComponent(setVal); 396 | 397 | Button reset = new Button("Reset"); 398 | reset.addClickListener(event -> relatedFilterTable.resetFilters()); 399 | buttonLayout.addComponent(reset); 400 | 401 | Button clear = new Button("Clear"); 402 | clear.addClickListener(event -> relatedFilterTable.clearFilters()); 403 | buttonLayout.addComponent(clear); 404 | 405 | return buttonLayout; 406 | } 407 | 408 | @SuppressWarnings("unchecked") 409 | private Container buildContainer() { 410 | IndexedContainer cont = new IndexedContainer(); 411 | Calendar c = Calendar.getInstance(); 412 | 413 | cont.addContainerProperty("name", String.class, null); 414 | cont.addContainerProperty("id", Integer.class, null); 415 | cont.addContainerProperty("state", State.class, null); 416 | cont.addContainerProperty("date", Timestamp.class, null); 417 | cont.addContainerProperty("validated", Boolean.class, null); 418 | cont.addContainerProperty("checked", Boolean.class, null); 419 | cont.addContainerProperty("comp", Component.class, null); 420 | 421 | Random random = new Random(); 422 | for (int i = 0; i < 10000; i++) { 423 | cont.addItem(i); 424 | /* Set name and id properties */ 425 | cont.getContainerProperty(i, "name").setValue("Order " + i); 426 | cont.getContainerProperty(i, "id").setValue(i); 427 | /* Set state property */ 428 | int rndInt = random.nextInt(4); 429 | State stateToSet = State.CREATED; 430 | if (rndInt == 0) { 431 | stateToSet = State.PROCESSING; 432 | } else if (rndInt == 1) { 433 | stateToSet = State.PROCESSED; 434 | } else if (rndInt == 2) { 435 | stateToSet = State.FINISHED; 436 | } 437 | cont.getContainerProperty(i, "state").setValue(stateToSet); 438 | /* Set date property */ 439 | cont.getContainerProperty(i, "date").setValue(new Timestamp(c.getTimeInMillis())); 440 | c.add(Calendar.DAY_OF_MONTH, 1); 441 | /* Set validated property */ 442 | cont.getContainerProperty(i, "validated").setValue(random.nextBoolean()); 443 | /* Set checked property */ 444 | cont.getContainerProperty(i, "checked").setValue(random.nextBoolean()); 445 | cont.getContainerProperty(i, "comp").setValue(new CheckBox()); 446 | } 447 | return cont; 448 | } 449 | 450 | @SuppressWarnings("unchecked") 451 | private Container buildHierarchicalContainer() { 452 | HierarchicalContainer cont = new HierarchicalContainer(); 453 | Calendar c = Calendar.getInstance(); 454 | 455 | cont.addContainerProperty("name", String.class, null); 456 | cont.addContainerProperty("id", Integer.class, null); 457 | cont.addContainerProperty("state", State.class, null); 458 | cont.addContainerProperty("date", Date.class, null); 459 | cont.addContainerProperty("validated", Boolean.class, null); 460 | cont.addContainerProperty("checked", Boolean.class, null); 461 | 462 | Random random = new Random(); 463 | int previousItemId = 0; 464 | for (int i = 0; i < 10000; i++) { 465 | cont.addItem(i); 466 | /* Setup parent/child relations */ 467 | if (i % 5 == 0) { 468 | previousItemId = i; 469 | } 470 | cont.setChildrenAllowed(i, i == 0 || i % 5 == 0); 471 | if (previousItemId != i) { 472 | cont.setParent(i, previousItemId); 473 | } 474 | /* Set name and id properties */ 475 | cont.getContainerProperty(i, "name").setValue("Order " + i); 476 | cont.getContainerProperty(i, "id").setValue(i); 477 | /* Set state property */ 478 | int rndInt = random.nextInt(4); 479 | State stateToSet = State.CREATED; 480 | if (rndInt == 0) { 481 | stateToSet = State.PROCESSING; 482 | } else if (rndInt == 1) { 483 | stateToSet = State.PROCESSED; 484 | } else if (rndInt == 2) { 485 | stateToSet = State.FINISHED; 486 | } 487 | cont.getContainerProperty(i, "state").setValue(stateToSet); 488 | /* Set date property */ 489 | cont.getContainerProperty(i, "date").setValue(c.getTime()); 490 | c.add(Calendar.DAY_OF_MONTH, 1); 491 | /* Set validated property */ 492 | cont.getContainerProperty(i, "validated").setValue(random.nextBoolean()); 493 | /* Set checked property */ 494 | cont.getContainerProperty(i, "checked").setValue(random.nextBoolean()); 495 | } 496 | return cont; 497 | } 498 | 499 | private Component createToggle(final FilterTable relatedFilterTable, final Object propId) { 500 | CheckBox toggle = new CheckBox(propId.toString()); 501 | toggle.setValue(relatedFilterTable.isFilterFieldVisible(propId)); 502 | toggle.addValueChangeListener(event -> relatedFilterTable.setFilterFieldVisible(propId, 503 | !relatedFilterTable.isFilterFieldVisible(propId))); 504 | return toggle; 505 | } 506 | 507 | private Component createToggle(final FilterTreeTable relatedFilterTable, final Object propId) { 508 | CheckBox toggle = new CheckBox(propId.toString()); 509 | toggle.setValue(relatedFilterTable.isFilterFieldVisible(propId)); 510 | toggle.addValueChangeListener(event -> relatedFilterTable.setFilterFieldVisible(propId, 511 | !relatedFilterTable.isFilterFieldVisible(propId))); 512 | return toggle; 513 | } 514 | } -------------------------------------------------------------------------------- /filteringtable-addon/src/main/java/org/tepi/filtertable/FilterFieldGenerator.java: -------------------------------------------------------------------------------- 1 | package org.tepi.filtertable; 2 | 3 | import java.io.Serializable; 4 | import java.lang.reflect.InvocationTargetException; 5 | import java.math.BigDecimal; 6 | import java.math.BigInteger; 7 | import java.sql.Timestamp; 8 | import java.util.ArrayList; 9 | import java.util.Collection; 10 | import java.util.Date; 11 | import java.util.EnumSet; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | import org.tepi.filtertable.datefilter.DateFilterPopup; 17 | import org.tepi.filtertable.datefilter.DateInterval; 18 | import org.tepi.filtertable.numberfilter.NumberFilterPopup; 19 | import org.tepi.filtertable.numberfilter.NumberInterval; 20 | import org.tepi.filtertable.paged.PagedFilterTable; 21 | 22 | import com.vaadin.server.Resource; 23 | import com.vaadin.server.Sizeable.Unit; 24 | import com.vaadin.ui.Component; 25 | import com.vaadin.ui.HasComponents; 26 | import com.vaadin.v7.data.Container; 27 | import com.vaadin.v7.data.Container.Filter; 28 | import com.vaadin.v7.data.Container.Filterable; 29 | import com.vaadin.v7.data.Property; 30 | import com.vaadin.v7.data.Property.ValueChangeListener; 31 | import com.vaadin.v7.data.util.filter.And; 32 | import com.vaadin.v7.data.util.filter.Between; 33 | import com.vaadin.v7.data.util.filter.Compare; 34 | import com.vaadin.v7.data.util.filter.Compare.Equal; 35 | import com.vaadin.v7.data.util.filter.SimpleStringFilter; 36 | import com.vaadin.v7.event.FieldEvents.TextChangeEvent; 37 | import com.vaadin.v7.event.FieldEvents.TextChangeListener; 38 | import com.vaadin.v7.ui.AbstractField; 39 | import com.vaadin.v7.ui.ComboBox; 40 | import com.vaadin.v7.ui.Field; 41 | import com.vaadin.v7.ui.TextField; 42 | 43 | @SuppressWarnings({ "serial", "deprecation" }) 44 | class FilterFieldGenerator implements Serializable { 45 | private final IFilterTable owner; 46 | 47 | /* Mapping for property IDs, filters and components */ 48 | private final Map filters = new HashMap(); 49 | private final Map, Object> customFields = new HashMap, Object>(); 50 | private final Map texts = new HashMap(); 51 | private final Map enums = new HashMap(); 52 | private final Map booleans = new HashMap(); 53 | private final Map dates = new HashMap(); 54 | private final Map numbers = new HashMap(); 55 | 56 | private And lastOnDemandFilter; 57 | 58 | /* ValueChangeListener for filter components */ 59 | private final ValueChangeListener listener = initializeListener(); 60 | 61 | private boolean runFiltersOnDemand; 62 | 63 | FilterFieldGenerator(IFilterTable owner) { 64 | this.owner = owner; 65 | } 66 | 67 | void destroyFilterComponents() { 68 | owner.setRefreshingEnabled(false); 69 | /* Remove all filters from container */ 70 | for (Object propertyId : filters.keySet()) { 71 | if (owner.getFilterable() != null) { 72 | owner.getFilterable().removeContainerFilter(filters.get(propertyId)); 73 | } 74 | if (owner.getFilterGenerator() != null) { 75 | owner.getFilterGenerator().filterRemoved(propertyId); 76 | } 77 | } 78 | /* Remove listeners */ 79 | removeValueChangeListeners(); 80 | /* Clear the data related to filters */ 81 | customFields.clear(); 82 | filters.clear(); 83 | texts.clear(); 84 | enums.clear(); 85 | booleans.clear(); 86 | dates.clear(); 87 | numbers.clear(); 88 | 89 | /* also clear on-demand data */ 90 | if (owner.getFilterable() != null) { 91 | owner.getFilterable().removeContainerFilter(lastOnDemandFilter); 92 | } 93 | 94 | owner.setRefreshingEnabled(true); 95 | } 96 | 97 | void clearFilterData() { 98 | owner.setRefreshingEnabled(false); 99 | /* Remove all filters from container */ 100 | for (Object propertyId : filters.keySet()) { 101 | if (owner.getFilterable() != null) { 102 | owner.getFilterable().removeContainerFilter(filters.get(propertyId)); 103 | } 104 | if (owner.getFilterGenerator() != null) { 105 | owner.getFilterGenerator().filterRemoved(propertyId); 106 | } 107 | } 108 | 109 | filters.clear(); 110 | 111 | for (AbstractField f : customFields.keySet()) { 112 | f.setValue(null); 113 | } 114 | for (AbstractField f : texts.keySet()) { 115 | f.setValue(null); 116 | } 117 | for (AbstractField f : enums.keySet()) { 118 | f.setValue(null); 119 | } 120 | for (AbstractField f : booleans.keySet()) { 121 | f.setValue(null); 122 | } 123 | for (AbstractField f : dates.keySet()) { 124 | f.setValue(null); 125 | } 126 | for (NumberFilterPopup f : numbers.keySet()) { 127 | f.setValue(null); 128 | } 129 | 130 | /* also clear on-demand data */ 131 | if (owner.getFilterable() != null) { 132 | owner.getFilterable().removeContainerFilter(lastOnDemandFilter); 133 | } 134 | 135 | owner.setRefreshingEnabled(true); 136 | } 137 | 138 | private void removeValueChangeListeners() { 139 | for (AbstractField af : customFields.keySet()) { 140 | af.removeValueChangeListener(listener); 141 | } 142 | for (TextField tf : texts.keySet()) { 143 | tf.removeValueChangeListener(listener); 144 | } 145 | for (ComboBox cb : enums.keySet()) { 146 | cb.removeValueChangeListener(listener); 147 | } 148 | for (ComboBox cb : booleans.keySet()) { 149 | cb.removeValueChangeListener(listener); 150 | } 151 | for (DateFilterPopup dfp : dates.keySet()) { 152 | dfp.removeValueChangeListener(listener); 153 | } 154 | for (NumberFilterPopup nfp : numbers.keySet()) { 155 | nfp.removeValueChangeListener(listener); 156 | } 157 | } 158 | 159 | private void addValueChangeListeners() { 160 | for (AbstractField af : customFields.keySet()) { 161 | af.addValueChangeListener(listener); 162 | } 163 | for (TextField tf : texts.keySet()) { 164 | tf.addValueChangeListener(listener); 165 | } 166 | for (ComboBox cb : enums.keySet()) { 167 | cb.addValueChangeListener(listener); 168 | } 169 | for (ComboBox cb : booleans.keySet()) { 170 | cb.addValueChangeListener(listener); 171 | } 172 | for (DateFilterPopup dfp : dates.keySet()) { 173 | dfp.addValueChangeListener(listener); 174 | } 175 | for (NumberFilterPopup nfp : numbers.keySet()) { 176 | nfp.addValueChangeListener(listener); 177 | } 178 | } 179 | 180 | void initializeFilterFields() { 181 | /* Create new filters only if Filterable */ 182 | if (owner.getFilterable() != null) { 183 | for (Object property : owner.getVisibleColumns()) { 184 | if (owner.getContainerPropertyIds().contains(property)) { 185 | AbstractField filter = createField(property, owner.getContainerDataSource().getType(property)); 186 | addFilterColumn(property, filter); 187 | } else { 188 | AbstractField filter = createField(property, null); 189 | addFilterColumn(property, filter); 190 | } 191 | } 192 | if (!runFiltersOnDemand) { 193 | addValueChangeListeners(); 194 | } 195 | } 196 | } 197 | 198 | private Filter generateFilter(Property field, Object propertyId, Object value) { 199 | try { 200 | /* First try to get custom filter based on the field */ 201 | if (owner.getFilterGenerator() != null && field instanceof Field) { 202 | Filter newFilter = owner.getFilterGenerator().generateFilter(propertyId, (Field) field); 203 | if (newFilter != null) { 204 | return newFilter; 205 | } 206 | } 207 | if (field instanceof NumberFilterPopup) { 208 | return generateNumberFilter(field, propertyId, value); 209 | } else if (field instanceof DateFilterPopup) { 210 | return generateDateFilter(field, propertyId, value); 211 | } else if (value != null && !value.equals("")) { 212 | return generateGenericFilter(field, propertyId, value); 213 | } 214 | return null; 215 | } catch (Exception reason) { 216 | /* Clear the field on failure during generating filter */ 217 | field.setValue(null); 218 | /* Notify FilterGenerator, or re-throw if not available */ 219 | if (owner.getFilterGenerator() != null) { 220 | return owner.getFilterGenerator().filterGeneratorFailed(reason, propertyId, value); 221 | } else { 222 | throw new RuntimeException( 223 | "Creating a filter for property '" + propertyId + "' with value '" + value + "'has failed.", 224 | reason); 225 | } 226 | } 227 | } 228 | 229 | private Filter generateGenericFilter(Property field, Object propertyId, Object value) { 230 | /* Handle filtering for other data */ 231 | if (owner.getFilterGenerator() != null) { 232 | Filter newFilter = owner.getFilterGenerator().generateFilter(propertyId, value); 233 | if (newFilter != null) { 234 | return newFilter; 235 | } 236 | } 237 | /* Special handling for ComboBox (= enum properties) */ 238 | if (field instanceof ComboBox) { 239 | return new Equal(propertyId, value); 240 | } else { 241 | return new SimpleStringFilter(propertyId, String.valueOf(value), true, false); 242 | } 243 | } 244 | 245 | private Filter generateNumberFilter(Property field, Object propertyId, Object value) throws SecurityException, 246 | NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { 247 | /* Handle number filtering */ 248 | NumberInterval interval = ((NumberFilterPopup) field).getValue(); 249 | if (interval == null) { 250 | /* Number interval is empty -> no filter */ 251 | return null; 252 | } 253 | if (owner.getFilterGenerator() != null) { 254 | Filter newFilter = owner.getFilterGenerator().generateFilter(propertyId, interval); 255 | if (newFilter != null) { 256 | return newFilter; 257 | } 258 | } 259 | String ltValue = interval.getLessThanValue(); 260 | String gtValue = interval.getGreaterThanValue(); 261 | String eqValue = interval.getEqualsValue(); 262 | Class typeClass = owner.getContainerDataSource().getType(propertyId); 263 | 264 | if (!eqValue.isEmpty()) { 265 | return new Compare.Equal(propertyId, parseNumberValue(typeClass, eqValue)); 266 | } else if (!ltValue.isEmpty() && !gtValue.isEmpty()) { 267 | return new And(new Compare.Less(propertyId, parseNumberValue(typeClass, ltValue)), 268 | new Compare.Greater(propertyId, parseNumberValue(typeClass, gtValue))); 269 | } else if (!ltValue.isEmpty()) { 270 | return new Compare.Less(propertyId, parseNumberValue(typeClass, ltValue)); 271 | } else if (!gtValue.isEmpty()) { 272 | return new Compare.Greater(propertyId, parseNumberValue(typeClass, gtValue)); 273 | } 274 | return null; 275 | } 276 | 277 | private static Object parseNumberValue(Class typeClass, String value) { 278 | if (typeClass == BigDecimal.class) 279 | return new BigDecimal(value); 280 | if (typeClass == BigInteger.class) 281 | return new BigInteger(value); 282 | if (typeClass == byte.class || typeClass == Byte.class) 283 | return Byte.valueOf(value); 284 | if (typeClass == short.class || typeClass == Short.class) 285 | return Short.valueOf(value); 286 | if (typeClass == int.class || typeClass == Integer.class) 287 | return Integer.valueOf(value); 288 | if (typeClass == long.class || typeClass == Long.class) 289 | return Long.valueOf(value); 290 | if (typeClass == float.class || typeClass == Float.class) 291 | return Float.valueOf(value); 292 | if (typeClass == double.class || typeClass == Double.class) 293 | return Double.valueOf(value); 294 | 295 | throw new UnsupportedOperationException("Unsupported number type; " + typeClass.getName()); 296 | } 297 | 298 | private Filter generateDateFilter(Property field, Object propertyId, Object value) { 299 | /* Handle date filtering */ 300 | DateInterval interval = ((DateFilterPopup) field).getValue(); 301 | if (interval == null || interval.isNull()) { 302 | /* Date interval is empty -> no filter */ 303 | return null; 304 | } 305 | /* Try to get a custom filter from a provided filter generator */ 306 | if (owner.getFilterGenerator() != null) { 307 | Filter newFilter = owner.getFilterGenerator().generateFilter(propertyId, interval); 308 | if (newFilter != null) { 309 | return newFilter; 310 | } 311 | } 312 | /* On failure we generate the default filter */ 313 | Comparable actualFrom = interval.getFrom(), actualTo = interval.getTo(); 314 | Class type = owner.getType(propertyId); 315 | if (java.sql.Date.class.equals(type)) { 316 | actualFrom = actualFrom == null ? null : new java.sql.Date(interval.getFrom().getTime()); 317 | actualTo = actualTo == null ? null : new java.sql.Date(interval.getTo().getTime()); 318 | } else if (Timestamp.class.equals(type)) { 319 | actualFrom = actualFrom == null ? null : new Timestamp(interval.getFrom().getTime()); 320 | actualTo = actualTo == null ? null : new Timestamp(interval.getTo().getTime()); 321 | } 322 | if (actualFrom != null && actualTo != null) { 323 | return new Between(propertyId, actualFrom, actualTo); 324 | } else if (actualFrom != null) { 325 | return new Compare.GreaterOrEqual(propertyId, actualFrom); 326 | } else { 327 | return new Compare.LessOrEqual(propertyId, actualTo); 328 | } 329 | } 330 | 331 | private void addFilterColumn(Object propertyId, Component filter) { 332 | owner.getColumnIdToFilterMap().put(propertyId, filter); 333 | filter.setParent(owner.getAsComponent()); 334 | } 335 | 336 | private void removeFilter(Object propertyId) { 337 | if (filters.get(propertyId) != null) { 338 | if (owner.getFilterable() != null) { 339 | owner.getFilterable().removeContainerFilter(filters.get(propertyId)); 340 | } 341 | filters.remove(propertyId); 342 | } 343 | } 344 | 345 | private void setFilter(Filter filter, Object propertyId) { 346 | if (owner.getFilterable() != null) { 347 | owner.getFilterable().addContainerFilter(filter); 348 | } 349 | filters.put(propertyId, filter); 350 | } 351 | 352 | private AbstractField createField(Object property, Class type) { 353 | AbstractField field = null; 354 | if (owner.getFilterGenerator() != null) { 355 | field = owner.getFilterGenerator().getCustomFilterComponent(property); 356 | } 357 | if (field != null) { 358 | customFields.put(field, property); 359 | } else if (type == null) { 360 | field = new TextField(); 361 | ((TextField) field).setNullRepresentation(""); 362 | texts.put(((TextField) field), property); 363 | } else if (type == boolean.class || type == Boolean.class) { 364 | field = createBooleanField(property); 365 | } else if (type.isEnum()) { 366 | field = createEnumField(type, property); 367 | } else if (type == Date.class || type == Timestamp.class || type == java.sql.Date.class) { 368 | DateFilterPopup dfp = createDateField(property); 369 | dfp.setWidth(100, Unit.PERCENTAGE); 370 | dfp.setImmediate(true); 371 | return dfp; 372 | } else if ((type == Integer.class || type == Long.class || type == Float.class || type == Double.class 373 | || type == Short.class || type == Byte.class || type == int.class || type == long.class 374 | || type == float.class || type == double.class || type == short.class || type == byte.class 375 | || type == BigDecimal.class || type == BigInteger.class) && owner.getFilterDecorator() != null 376 | && owner.getFilterDecorator().usePopupForNumericProperty(property)) { 377 | NumberFilterPopup nfp = createNumericField(type, property); 378 | nfp.setWidth(100, Unit.PERCENTAGE); 379 | nfp.setImmediate(true); 380 | return nfp; 381 | } else { 382 | field = createTextField(property); 383 | } 384 | field.setWidth(null); 385 | field.setImmediate(true); 386 | return field; 387 | } 388 | 389 | private AbstractField createTextField(Object propertyId) { 390 | final TextField textField = new TextField(); 391 | if (owner.getFilterDecorator() != null) { 392 | if (owner.getFilterDecorator().isTextFilterImmediate(propertyId)) { 393 | textField.addTextChangeListener(new TextChangeListener() { 394 | 395 | @Override 396 | public void textChange(TextChangeEvent event) { 397 | textField.setValue(event.getText()); 398 | } 399 | }); 400 | textField.setTextChangeTimeout(owner.getFilterDecorator().getTextChangeTimeout(propertyId)); 401 | } 402 | if (owner.getFilterDecorator().getAllItemsVisibleString() != null) { 403 | textField.setInputPrompt(owner.getFilterDecorator().getAllItemsVisibleString()); 404 | } 405 | } 406 | textField.setNullRepresentation(""); 407 | texts.put(textField, propertyId); 408 | return textField; 409 | } 410 | 411 | @SuppressWarnings({ "rawtypes", "unchecked" }) 412 | private AbstractField createEnumField(Class type, Object propertyId) { 413 | ComboBox enumSelect = new ComboBox(); 414 | /* Add possible 'view all' item */ 415 | if (owner.getFilterDecorator() != null && owner.getFilterDecorator().getAllItemsVisibleString() != null) { 416 | Object nullItem = enumSelect.addItem(); 417 | enumSelect.setNullSelectionItemId(nullItem); 418 | enumSelect.setItemCaption(nullItem, owner.getFilterDecorator().getAllItemsVisibleString()); 419 | } 420 | /* Add items from enumeration */ 421 | for (Object o : EnumSet.allOf((Class) type)) { 422 | enumSelect.addItem(o); 423 | if (owner.getFilterDecorator() != null) { 424 | String caption = owner.getFilterDecorator().getEnumFilterDisplayName(propertyId, o); 425 | enumSelect.setItemCaption(o, caption == null ? o.toString() : caption); 426 | Resource icon = owner.getFilterDecorator().getEnumFilterIcon(propertyId, o); 427 | if (icon != null) { 428 | enumSelect.setItemIcon(o, icon); 429 | } 430 | } else { 431 | enumSelect.setItemCaption(o, o.toString()); 432 | } 433 | } 434 | enums.put(enumSelect, propertyId); 435 | return enumSelect; 436 | } 437 | 438 | private AbstractField createBooleanField(Object propertyId) { 439 | ComboBox booleanSelect = new ComboBox(); 440 | booleanSelect.addItem(true); 441 | booleanSelect.addItem(false); 442 | if (owner.getFilterDecorator() != null) { 443 | /* Add possible 'view all' item */ 444 | if (owner.getFilterDecorator().getAllItemsVisibleString() != null) { 445 | Object nullItem = booleanSelect.addItem(); 446 | booleanSelect.setNullSelectionItemId(nullItem); 447 | booleanSelect.setItemCaption(nullItem, owner.getFilterDecorator().getAllItemsVisibleString()); 448 | } 449 | String caption = owner.getFilterDecorator().getBooleanFilterDisplayName(propertyId, true); 450 | booleanSelect.setItemCaption(true, caption == null ? "true" : caption); 451 | Resource icon = owner.getFilterDecorator().getBooleanFilterIcon(propertyId, true); 452 | if (icon != null) { 453 | booleanSelect.setItemIcon(true, icon); 454 | } 455 | caption = owner.getFilterDecorator().getBooleanFilterDisplayName(propertyId, false); 456 | booleanSelect.setItemCaption(false, caption == null ? "false" : caption); 457 | icon = owner.getFilterDecorator().getBooleanFilterIcon(propertyId, false); 458 | if (icon != null) { 459 | booleanSelect.setItemIcon(false, icon); 460 | } 461 | } else { 462 | booleanSelect.setItemCaption(true, "true"); 463 | booleanSelect.setItemCaption(false, "false"); 464 | } 465 | booleans.put(booleanSelect, propertyId); 466 | return booleanSelect; 467 | } 468 | 469 | private DateFilterPopup createDateField(Object propertyId) { 470 | DateFilterPopup dateFilterPopup = new DateFilterPopup(owner.getFilterDecorator(), propertyId); 471 | dates.put(dateFilterPopup, propertyId); 472 | return dateFilterPopup; 473 | } 474 | 475 | private NumberFilterPopup createNumericField(Class type, Object propertyId) { 476 | NumberFilterPopup numberFilterPopup = new NumberFilterPopup(owner.getFilterDecorator()); 477 | boolean typeHasDecimalPlaces = (type == float.class || type == Float.class || type == double.class 478 | || type == Double.class || type == BigDecimal.class); 479 | numberFilterPopup.setDecimalPlacesAllowed(typeHasDecimalPlaces); 480 | numbers.put(numberFilterPopup, propertyId); 481 | return numberFilterPopup; 482 | } 483 | 484 | private ValueChangeListener initializeListener() { 485 | return new Property.ValueChangeListener() { 486 | @Override 487 | public void valueChange(Property.ValueChangeEvent event) { 488 | if (owner.getFilterable() == null) { 489 | return; 490 | } 491 | Property field = event.getProperty(); 492 | updateFilterForField(field); 493 | } 494 | }; 495 | } 496 | 497 | private void updateFilterForField(Property field) { 498 | Object value = field.getValue(); 499 | Object propertyId = null; 500 | if (customFields.containsKey(field)) { 501 | propertyId = customFields.get(field); 502 | } else if (texts.containsKey(field)) { 503 | propertyId = texts.get(field); 504 | } else if (dates.containsKey(field)) { 505 | propertyId = dates.get(field); 506 | } else if (numbers.containsKey(field)) { 507 | propertyId = numbers.get(field); 508 | } else if (enums.containsKey(field)) { 509 | propertyId = enums.get(field); 510 | } else if (booleans.containsKey(field)) { 511 | propertyId = booleans.get(field); 512 | } 513 | 514 | owner.setRefreshingEnabled(false); 515 | 516 | // Generate a new filter 517 | Filter newFilter = generateFilter(field, propertyId, value); 518 | 519 | // Check if the filter is already set 520 | Filter possiblyExistingFilter = filters.get(propertyId); 521 | if (possiblyExistingFilter != null && newFilter != null && possiblyExistingFilter.equals(newFilter)) { 522 | return; 523 | } 524 | 525 | /* Remove the old filter and set the new filter */ 526 | removeFilter(propertyId); 527 | if (newFilter != null) { 528 | setFilter(newFilter, propertyId); 529 | if (owner.getFilterGenerator() != null) { 530 | owner.getFilterGenerator().filterAdded(propertyId, newFilter.getClass(), value); 531 | } 532 | } else { 533 | if (owner.getFilterGenerator() != null) { 534 | owner.getFilterGenerator().filterRemoved(propertyId); 535 | } 536 | } 537 | 538 | /* 539 | * If the owner is a PagedFilteringTable, move to the first page 540 | */ 541 | if (owner instanceof PagedFilterTable) { 542 | ((PagedFilterTable) owner).setCurrentPage(1); 543 | } 544 | 545 | owner.setRefreshingEnabled(true); 546 | } 547 | 548 | private Filter generateFilterForField(Property field) { 549 | Object value = field.getValue(); 550 | Object propertyId = null; 551 | if (customFields.containsKey(field)) { 552 | propertyId = customFields.get(field); 553 | } else if (texts.containsKey(field)) { 554 | propertyId = texts.get(field); 555 | } else if (dates.containsKey(field)) { 556 | propertyId = dates.get(field); 557 | } else if (numbers.containsKey(field)) { 558 | propertyId = numbers.get(field); 559 | } else if (enums.containsKey(field)) { 560 | propertyId = enums.get(field); 561 | } else if (booleans.containsKey(field)) { 562 | propertyId = booleans.get(field); 563 | } 564 | 565 | return generateFilter(field, propertyId, value); 566 | } 567 | 568 | public void runFiltersNow() { 569 | owner.setRefreshingEnabled(false); 570 | if (owner.getFilterable() != null && lastOnDemandFilter != null) { 571 | owner.getFilterable().removeContainerFilter(lastOnDemandFilter); 572 | } 573 | List filters = new ArrayList(); 574 | for (AbstractField f : customFields.keySet()) { 575 | addNonNullFilter(filters, f); 576 | } 577 | for (AbstractField f : texts.keySet()) { 578 | addNonNullFilter(filters, f); 579 | } 580 | for (AbstractField f : dates.keySet()) { 581 | addNonNullFilter(filters, f); 582 | } 583 | for (AbstractField f : numbers.keySet()) { 584 | addNonNullFilter(filters, f); 585 | } 586 | for (AbstractField f : enums.keySet()) { 587 | addNonNullFilter(filters, f); 588 | } 589 | for (AbstractField f : booleans.keySet()) { 590 | addNonNullFilter(filters, f); 591 | } 592 | 593 | Filter[] filtersArray = filters.toArray(new Filter[0]); 594 | lastOnDemandFilter = filters.isEmpty() ? null : new And(filtersArray); 595 | if (owner.getFilterable() != null && lastOnDemandFilter != null) { 596 | owner.getFilterable().addContainerFilter(lastOnDemandFilter); 597 | } 598 | owner.setRefreshingEnabled(true); 599 | 600 | } 601 | 602 | private void addNonNullFilter(List filters, AbstractField f) { 603 | Filter filter = generateFilterForField(f); 604 | if (null != filter) { 605 | filters.add(filter); 606 | } 607 | } 608 | 609 | public interface IFilterTable { 610 | 611 | public Filterable getFilterable(); 612 | 613 | public FilterGenerator getFilterGenerator(); 614 | 615 | public Object[] getVisibleColumns(); 616 | 617 | public Collection getContainerPropertyIds(); 618 | 619 | public Container getContainerDataSource(); 620 | 621 | public Class getType(Object propertyId); 622 | 623 | public Map getColumnIdToFilterMap(); 624 | 625 | public FilterDecorator getFilterDecorator(); 626 | 627 | public int getPageLength(); 628 | 629 | public HasComponents getAsComponent(); 630 | 631 | public void setRefreshingEnabled(boolean enabled); 632 | 633 | } 634 | 635 | void setFilterOnDemandMode(boolean filterOnDemand) { 636 | if (runFiltersOnDemand == filterOnDemand) { 637 | return; 638 | } else { 639 | runFiltersOnDemand = filterOnDemand; 640 | destroyFilterComponents(); 641 | initializeFilterFields(); 642 | } 643 | } 644 | } 645 | --------------------------------------------------------------------------------