View Javadoc
1   /**
2    * This file Copyright (c) 2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.admincentral.findbar.layout;
35  
36  import info.magnolia.i18nsystem.SimpleTranslator;
37  import info.magnolia.icons.MagnoliaIcons;
38  import info.magnolia.ui.theme.ResurfaceTheme;
39  
40  import java.time.LocalDate;
41  import java.time.format.DateTimeFormatter;
42  import java.util.Arrays;
43  import java.util.List;
44  
45  import org.apache.commons.collections4.CollectionUtils;
46  import org.vaadin.addons.ComboBoxMultiselect;
47  
48  import com.vaadin.addon.daterangefield.DateRangeField;
49  import com.vaadin.ui.Button;
50  import com.vaadin.ui.Component;
51  import com.vaadin.ui.CssLayout;
52  import com.vaadin.ui.HorizontalLayout;
53  import com.vaadin.ui.Label;
54  import com.vaadin.ui.VerticalLayout;
55  
56  
57  /**
58   * Filter options layout for {@link info.magnolia.admincentral.findbar.FindBar}.
59   */
60  public class FilterLayout extends CssLayout {
61  
62      private final DateRangeField dateField;
63      private final List<ComboBoxMultiselect<String>> fields;
64  
65      private final HorizontalLayout periscopeFilterOptions;
66      private final SimpleTranslator i18n;
67      private CssLayout globalTokenLayout;
68      private Label searchLabel;
69  
70      public FilterLayout(DateRangeField dateField, SimpleTranslator i18n, ComboBoxMultiselect<String>... fields) {
71          this.setStyleName("periscope-filter-wrapper");
72          this.setVisible(true);
73  
74          this.fields = Arrays.asList(fields);
75  
76          this.dateField = dateField;
77          this.i18n = i18n;
78  
79          VerticalLayout periscopeFilter = new VerticalLayout();
80          periscopeFilter.setWidth(100, Unit.PERCENTAGE);
81          periscopeFilter.setMargin(false);
82          periscopeFilter.addStyleName("periscope-filter");
83  
84          HorizontalLayout periscopeFilterHeader = initPeriscopeFilterHeader();
85  
86          this.periscopeFilterOptions = initPeriscopeFilterOptions();
87  
88          this.fields.forEach(field -> this.periscopeFilterOptions.addComponent(initFieldLayout(field)));
89  
90          VerticalLayout editedFieldLayout = initRangeDateFieldLayout(dateField);
91          this.periscopeFilterOptions.addComponent(editedFieldLayout);
92  
93          this.periscopeFilterOptions.forEach(component -> component.addStyleName("filter-section"));
94  
95          periscopeFilter.addComponents(periscopeFilterHeader, periscopeFilterOptions);
96          this.addComponent(periscopeFilter);
97      }
98  
99      private HorizontalLayout initPeriscopeFilterHeader() {
100         HorizontalLayout periscopeFilterHeader = new HorizontalLayout();
101         periscopeFilterHeader.setStyleName("periscope-filter-header-wrapper");
102         Button expandFilterOptionsButton = new Button(MagnoliaIcons.SEARCH_RESULT_FILTERS);
103         expandFilterOptionsButton.addStyleName(ResurfaceTheme.BUTTON_ICON);
104 
105         searchLabel = new Label(i18n.translate("findbar.filter.refine"));
106         searchLabel.setVisible(false);
107         globalTokenLayout = new CssLayout() {
108 
109             @Override
110             public void removeComponent(Component c) {
111                 super.removeComponent(c);
112                 if (getComponentCount() == 0) {
113                     searchLabel.setVisible(false);
114                 }
115             }
116         };
117 
118         periscopeFilterHeader.addComponents(expandFilterOptionsButton, searchLabel, globalTokenLayout);
119 
120         expandFilterOptionsButton.addClickListener(click -> {
121             boolean optionsVisible = !periscopeFilterOptions.isVisible(); // negation because property represents new state after click
122             boolean noFiltersSet = fields.stream().allMatch(field -> CollectionUtils.isEmpty(field.getValue()))
123                     && dateField.getBeginDateField().getValue() == null
124                     && dateField.getEndDateField().getValue() == null;
125 
126             periscopeFilterOptions.setVisible(optionsVisible);
127             globalTokenLayout.setVisible(!optionsVisible);
128             globalTokenLayout.removeAllComponents();
129             if (!optionsVisible && !noFiltersSet) {
130                 this.fields.forEach(field -> populateTokenLayout(globalTokenLayout, field));
131                 populateDateTokenLayout(globalTokenLayout, dateField);
132             }
133             searchLabel.setVisible(optionsVisible || !noFiltersSet);
134         });
135         return periscopeFilterHeader;
136     }
137 
138     private HorizontalLayout initPeriscopeFilterOptions() {
139         HorizontalLayout periscopeFilterOptions = new HorizontalLayout();
140         periscopeFilterOptions.setSpacing(false);
141         periscopeFilterOptions.setStyleName("periscope-filter-options-wrapper");
142         periscopeFilterOptions.setWidth(100, Unit.PERCENTAGE);
143         periscopeFilterOptions.setVisible(false);
144         return periscopeFilterOptions;
145     }
146 
147     private VerticalLayout initFieldLayout(ComboBoxMultiselect<String> field) {
148         VerticalLayout fieldLayout = new VerticalLayout();
149         fieldLayout.setMargin(false);
150         field.setStyleGenerator(item -> "periscope-filter-item");
151         field.setWidth(100, Unit.PERCENTAGE);
152         field.setPlaceholder(i18n.translate("findbar.filter.placeholder"));
153         field.setPageLength(5);
154         CssLayout tokensLayout = new CssLayout();
155         tokensLayout.setStyleName("token-layout");
156         field.addValueChangeListener(event -> {
157             tokensLayout.removeAllComponents();
158             populateTokenLayout(tokensLayout, field);
159         });
160         fieldLayout.addComponents(field, tokensLayout);
161         return fieldLayout;
162     }
163 
164     private VerticalLayout initRangeDateFieldLayout(DateRangeField field) {
165         VerticalLayout fieldLayout = new VerticalLayout();
166         fieldLayout.setMargin(false);
167         field.setWidth(100, Unit.PERCENTAGE);
168         field.setStyleName("date-range-field");
169         field.getBeginDateField().setPlaceholder(i18n.translate("findbar.filter.date.begin"));
170         field.getEndDateField().setPlaceholder(i18n.translate("findbar.filter.date.end"));
171         LocalDate today = LocalDate.now();
172         field.addShortcut(i18n.translate("findbar.filter.date.today"), item -> item.setRange(today, today));
173         field.addShortcut(i18n.translate("findbar.filter.date.lastweek"), item -> item.setRange(today.minusWeeks(1), today));
174         field.addShortcut(i18n.translate("findbar.filter.date.lastmonth"), item -> item.setRange(today.minusMonths(1), today));
175         field.addShortcut(i18n.translate("findbar.filter.date.lastyear"), item -> item.setRange(today.minusYears(1), today));
176         field.addShortcutSeparator();
177         field.addShortcut(i18n.translate("findbar.filter.clear"), DateRangeField::clear);
178         CssLayout tokensLayout = new CssLayout();
179         tokensLayout.setStyleName("token-layout");
180         field.addValueChangeListener(event -> {
181             tokensLayout.removeAllComponents();
182             populateDateTokenLayout(tokensLayout, field);
183         });
184         fieldLayout.addComponents(field, tokensLayout);
185         return fieldLayout;
186     }
187 
188     private void populateTokenLayout(CssLayout privateTokenLayout, ComboBoxMultiselect<String> field) {
189         field.getValue().forEach(token -> {
190             Button tokenButton = new Button(token, click -> {
191                 privateTokenLayout.removeComponent(click.getButton());
192                 field.deselect(click.getButton().getCaption());
193             });
194             tokenButton.setIcon(MagnoliaIcons.CLOSE);
195             tokenButton.addStyleName("periscope-token-button");
196             privateTokenLayout.addComponent(tokenButton);
197         });
198     }
199 
200     private void populateDateTokenLayout(CssLayout dateTokenLayout, DateRangeField dateRangeField) {
201         if (dateRangeField.getBeginDate() == null && dateRangeField.getEndDate() == null) {
202             return;
203         }
204         String label;
205         if (dateRangeField.getBeginDate() != null && dateRangeField.getEndDate() != null) {
206             label = dateRangeField.getBeginDate().format(DateTimeFormatter.ofPattern("dd.MM.yy"));
207             label += " - ";
208             label += dateRangeField.getEndDate().format(DateTimeFormatter.ofPattern("dd.MM.yy"));
209         } else if (dateRangeField.getBeginDate() != null) {
210             label = "> ";
211             label += dateRangeField.getBeginDate().format(DateTimeFormatter.ofPattern("dd.MM.yy"));
212         } else {
213             label = "< ";
214             label += dateRangeField.getEndDate().format(DateTimeFormatter.ofPattern("dd.MM.yy"));
215         }
216         Button tokenButton = new Button(label, click -> {
217             dateTokenLayout.removeComponent(click.getButton());
218             dateRangeField.clear();
219         });
220         tokenButton.setIcon(MagnoliaIcons.CLOSE);
221         tokenButton.addStyleName("periscope-token-button");
222         dateTokenLayout.addComponent(tokenButton);
223     }
224 
225 
226     /**
227      * Update global token layout.
228      */
229     public void updateGlobalTokenLayout() {
230         globalTokenLayout.removeAllComponents();
231         fields.forEach(field -> populateTokenLayout(globalTokenLayout, field));
232         populateDateTokenLayout(globalTokenLayout, dateField);
233     }
234 }