1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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.time.format.FormatStyle;
43 import java.util.List;
44 import java.util.Locale;
45
46 import org.apache.commons.collections4.CollectionUtils;
47 import org.vaadin.addons.ComboBoxMultiselect;
48
49 import com.vaadin.addon.daterangefield.DateRangeField;
50 import com.vaadin.ui.Alignment;
51 import com.vaadin.ui.Button;
52 import com.vaadin.ui.Component;
53 import com.vaadin.ui.CssLayout;
54 import com.vaadin.ui.HorizontalLayout;
55 import com.vaadin.ui.Label;
56 import com.vaadin.ui.VerticalLayout;
57
58
59
60
61
62 public class FilterLayout extends CssLayout {
63
64 private final DateRangeField dateField;
65 private final List<ComboBoxMultiselect<String>> fields;
66
67 private final HorizontalLayout periscopeFilterOptions;
68 private final SimpleTranslator i18n;
69 private CssLayout globalTokenLayout;
70 private Label titleLabel;
71
72 public FilterLayout(DateRangeField dateField, SimpleTranslator i18n, List<ComboBoxMultiselect<String>> fields) {
73 this.setStyleName("periscope-filter-wrapper");
74 this.setVisible(true);
75
76 this.dateField = dateField;
77 this.i18n = i18n;
78 this.fields = fields;
79
80 VerticalLayout periscopeFilter = new VerticalLayout();
81 periscopeFilter.setWidth(100, Unit.PERCENTAGE);
82 periscopeFilter.setMargin(false);
83 periscopeFilter.setSpacing(false);
84 periscopeFilter.addStyleName("periscope-filter");
85
86 HorizontalLayout periscopeFilterHeader = initPeriscopeFilterHeader();
87
88 this.periscopeFilterOptions = initPeriscopeFilterOptions();
89
90 this.globalTokenLayout = initGlobalTokenLayout();
91
92 this.fields.forEach(field -> this.periscopeFilterOptions.addComponent(initFieldLayout(field)));
93
94 VerticalLayout editedFieldLayout = initRangeDateFieldLayout(dateField);
95 this.periscopeFilterOptions.addComponent(editedFieldLayout);
96
97 this.periscopeFilterOptions.forEach(component -> component.addStyleName("filter-section"));
98
99 periscopeFilter.addComponents(periscopeFilterHeader, periscopeFilterOptions, globalTokenLayout);
100 this.addComponent(periscopeFilter);
101 }
102
103 private HorizontalLayout initPeriscopeFilterHeader() {
104 HorizontalLayout periscopeFilterHeader = new HorizontalLayout();
105 periscopeFilterHeader.setStyleName("periscope-filter-header-wrapper");
106 periscopeFilterHeader.setWidth(100, Unit.PERCENTAGE);
107 periscopeFilterHeader.setDefaultComponentAlignment(Alignment.BOTTOM_LEFT);
108
109 titleLabel = new Label(i18n.translate("findbar.suggestions"));
110 titleLabel.setStyleName("periscope-filter-header-title");
111
112 Label spacer = new Label();
113
114 Button expandFilterOptionsButton = new Button(MagnoliaIcons.SEARCH_RESULT_FILTERS);
115 expandFilterOptionsButton.addStyleName(ResurfaceTheme.BUTTON_ICON);
116
117 periscopeFilterHeader.addComponents(titleLabel, spacer, expandFilterOptionsButton);
118 periscopeFilterHeader.setComponentAlignment(expandFilterOptionsButton, Alignment.BOTTOM_RIGHT);
119 periscopeFilterHeader.setExpandRatio(titleLabel, 0);
120 periscopeFilterHeader.setExpandRatio(spacer, 1f);
121 periscopeFilterHeader.setExpandRatio(expandFilterOptionsButton, 0);
122
123 expandFilterOptionsButton.addClickListener(click -> {
124 boolean optionsVisible = !periscopeFilterOptions.isVisible();
125 boolean noFiltersSet = fields.stream().allMatch(field -> CollectionUtils.isEmpty(field.getValue()))
126 && dateField.getBeginDateField().getValue() == null
127 && dateField.getEndDateField().getValue() == null;
128
129 periscopeFilterOptions.setVisible(optionsVisible);
130 globalTokenLayout.removeAllComponents();
131 globalTokenLayout.setVisible(!optionsVisible && !noFiltersSet);
132 if (!optionsVisible && !noFiltersSet) {
133 this.fields.forEach(field -> populateTokenLayout(globalTokenLayout, field));
134 populateDateTokenLayout(globalTokenLayout, dateField);
135 }
136
137 if (optionsVisible) {
138 expandFilterOptionsButton.addStyleName("active");
139 } else {
140 expandFilterOptionsButton.removeStyleName("active");
141 }
142 });
143 return periscopeFilterHeader;
144 }
145
146 private CssLayout initGlobalTokenLayout() {
147 CssLayout globalTokenLayout = new CssLayout() {
148
149 @Override
150 public void removeComponent(Component c) {
151 super.removeComponent(c);
152 if (getComponentCount() == 0) {
153 setVisible(false);
154 }
155 }
156 };
157
158 globalTokenLayout.setStyleName("periscope-filter-global-token-layout");
159 globalTokenLayout.setWidth(100, Unit.PERCENTAGE);
160 globalTokenLayout.setVisible(false);
161 return globalTokenLayout;
162 }
163
164 private HorizontalLayout initPeriscopeFilterOptions() {
165 HorizontalLayout periscopeFilterOptions = new HorizontalLayout();
166 periscopeFilterOptions.setSpacing(false);
167 periscopeFilterOptions.setStyleName("periscope-filter-options-wrapper");
168 periscopeFilterOptions.setWidth(100, Unit.PERCENTAGE);
169 periscopeFilterOptions.setVisible(false);
170 return periscopeFilterOptions;
171 }
172
173 private VerticalLayout initFieldLayout(ComboBoxMultiselect<String> field) {
174 VerticalLayout fieldLayout = new VerticalLayout();
175 fieldLayout.setMargin(false);
176 field.setStyleGenerator(item -> "periscope-filter-item");
177 field.setWidth(100, Unit.PERCENTAGE);
178 field.setPlaceholder(i18n.translate("findbar.filter.placeholder"));
179 field.setPageLength(5);
180 CssLayout tokensLayout = new CssLayout();
181 tokensLayout.setStyleName("token-layout");
182 field.addValueChangeListener(event -> {
183 tokensLayout.removeAllComponents();
184 populateTokenLayout(tokensLayout, field);
185 });
186 fieldLayout.addComponents(field, tokensLayout);
187 return fieldLayout;
188 }
189
190 private VerticalLayout initRangeDateFieldLayout(DateRangeField field) {
191 VerticalLayout fieldLayout = new VerticalLayout();
192 fieldLayout.setMargin(false);
193 field.setWidth(100, Unit.PERCENTAGE);
194 field.setStyleName("date-range-field");
195 field.getBeginDateField().setPlaceholder(i18n.translate("findbar.filter.date.begin"));
196 field.getEndDateField().setPlaceholder(i18n.translate("findbar.filter.date.end"));
197 LocalDate today = LocalDate.now();
198 field.addShortcut(i18n.translate("findbar.filter.date.today"), item -> item.setRange(today, today));
199 field.addShortcut(i18n.translate("findbar.filter.date.yesterday"), item -> item.setRange(today.minusDays(1), today.minusDays(1)));
200 field.addShortcut(i18n.translate("findbar.filter.date.pastweek"), item -> item.setRange(today.minusWeeks(1), today));
201 field.addShortcut(i18n.translate("findbar.filter.date.pastmonth"), item -> item.setRange(today.minusMonths(1), today));
202 field.addShortcut(i18n.translate("findbar.filter.date.pastyear"), item -> item.setRange(today.minusYears(1), today));
203 field.addShortcutSeparator();
204 field.addShortcut(i18n.translate("findbar.filter.clear"), DateRangeField::clear);
205 CssLayout tokensLayout = new CssLayout();
206 tokensLayout.setStyleName("token-layout");
207 field.addValueChangeListener(event -> {
208 tokensLayout.removeAllComponents();
209 populateDateTokenLayout(tokensLayout, field);
210 });
211 fieldLayout.addComponents(field, tokensLayout);
212 return fieldLayout;
213 }
214
215 private void populateTokenLayout(CssLayout privateTokenLayout, ComboBoxMultiselect<String> field) {
216 field.getValue().forEach(token -> {
217 Button tokenButton = new Button(token, click -> {
218 privateTokenLayout.removeComponent(click.getButton());
219 field.deselect(click.getButton().getCaption());
220 });
221 tokenButton.setIcon(MagnoliaIcons.CLOSE);
222 tokenButton.addStyleName("periscope-token-button");
223 privateTokenLayout.addComponent(tokenButton);
224 });
225 }
226
227 private void populateDateTokenLayout(CssLayout dateTokenLayout, DateRangeField dateRangeField) {
228 if (dateRangeField.getBeginDate() == null && dateRangeField.getEndDate() == null) {
229 return;
230 }
231 String label;
232 if (dateRangeField.getBeginDate() != null && dateRangeField.getEndDate() != null) {
233 label = dateRangeField.getBeginDate().format(createTimeFormatter(dateRangeField.getBeginDateField().getLocale()));
234 label += " - ";
235 label += dateRangeField.getEndDate().format(createTimeFormatter(dateRangeField.getEndDateField().getLocale()));
236 } else if (dateRangeField.getBeginDate() != null) {
237 label = "> ";
238 label += dateRangeField.getBeginDate().format(createTimeFormatter(dateRangeField.getBeginDateField().getLocale()));
239 } else {
240 label = "< ";
241 label += dateRangeField.getEndDate().format(createTimeFormatter(dateRangeField.getEndDateField().getLocale()));
242 }
243 Button tokenButton = new Button(label, click -> {
244 dateTokenLayout.removeComponent(click.getButton());
245 dateRangeField.clear();
246 });
247 tokenButton.setIcon(MagnoliaIcons.CLOSE);
248 tokenButton.addStyleName("periscope-token-button");
249 dateTokenLayout.addComponent(tokenButton);
250 }
251
252 private DateTimeFormatter createTimeFormatter(Locale locale) {
253 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
254 if (locale != null) {
255 return dateTimeFormatter.withLocale(locale);
256 }
257 return dateTimeFormatter;
258 }
259
260
261
262
263 public void updateGlobalTokenLayout() {
264 globalTokenLayout.removeAllComponents();
265 fields.forEach(field -> populateTokenLayout(globalTokenLayout, field));
266 populateDateTokenLayout(globalTokenLayout, dateField);
267 globalTokenLayout.setVisible(!periscopeFilterOptions.isVisible() && globalTokenLayout.getComponentCount() > 0);
268 }
269
270 public void updateTitleLabel(boolean areResultsSuggestions, int count) {
271
272 if (!areResultsSuggestions) {
273 titleLabel.setValue(i18n.translate("findbar.results", count));
274 } else {
275 titleLabel.setValue(i18n.translate("findbar.suggestions"));
276 }
277 }
278 }