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.ui.contentapp.browser;
35
36 import static java.util.stream.Collectors.toList;
37
38 import java.lang.reflect.Field;
39 import java.util.Iterator;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Set;
43 import java.util.function.BiPredicate;
44 import java.util.function.Predicate;
45
46 import com.vaadin.data.Binder;
47 import com.vaadin.data.PropertySet;
48 import com.vaadin.data.provider.HierarchicalDataCommunicator;
49 import com.vaadin.data.provider.HierarchicalDataProvider;
50 import com.vaadin.data.provider.HierarchyMapper;
51 import com.vaadin.shared.Range;
52 import com.vaadin.ui.Grid;
53 import com.vaadin.ui.TreeGrid;
54 import com.vaadin.ui.components.grid.Editor;
55 import com.vaadin.ui.components.grid.EditorImpl;
56
57
58
59
60
61 public class MagnoliaTreeGrid<T> extends TreeGrid<T> {
62
63 private final Predicate<T> itemInteractionAvailability;
64 private final BiPredicate<Column, T> cellEditingAvailability;
65
66 public MagnoliaTreeGrid(PropertySet<T> propertySet, Predicate<T> itemInteractionAvailability, BiPredicate<Column, T> cellEditingAvailability) {
67 super(propertySet, new RemovedChildrenFilteringDataCommunicator<>(itemInteractionAvailability));
68 this.itemInteractionAvailability = itemInteractionAvailability;
69 this.cellEditingAvailability = cellEditingAvailability;
70 setPropertySet(propertySet);
71
72 getEditor().addOpenListener(e -> {
73 if (!this.itemInteractionAvailability.test(e.getBean())) {
74 getEditor().cancel();
75 }
76 });
77
78 addSelectionListener(e -> e.getAllSelectedItems().forEach(item -> {
79 if (!itemInteractionAvailability.test(item)) {
80 deselect(item);
81 }
82 }));
83 }
84
85 @Override
86 public void select(T item) {
87 if (itemInteractionAvailability.test(item)) {
88 super.select(item);
89 }
90 }
91
92 @Override
93 protected Editor<T> createEditor() {
94 return new EditableCellFilteringEditor<>(getPropertySet(), this, itemInteractionAvailability, cellEditingAvailability);
95 }
96
97
98
99
100
101
102
103
104
105
106 static class RemovedChildrenFilteringDataCommunicator<T> extends HierarchicalDataCommunicator<T> {
107
108 private final Predicate<T> itemInteractionAvailability;
109
110 RemovedChildrenFilteringDataCommunicator(Predicate<T> itemInteractionAvailability) {
111 this.itemInteractionAvailability = itemInteractionAvailability;
112 }
113
114 @Override
115 protected <F> HierarchyMapper<T, F> createHierarchyMapper(HierarchicalDataProvider<T, F> dataProvider) {
116 return new RemovedChildrenFilteringHierarchyMapper<>(dataProvider, itemInteractionAvailability);
117 }
118
119 @Override
120 public void refresh(T data) {
121 if (itemInteractionAvailability.test(data)) {
122 super.refresh(data);
123 }
124 }
125 }
126
127
128
129
130
131
132
133
134
135
136
137 static class RemovedChildrenFilteringHierarchyMapper<T, F> extends HierarchyMapper<T, F> {
138
139 private final Predicate<T> itemInteractionAvailability;
140
141 RemovedChildrenFilteringHierarchyMapper(HierarchicalDataProvider<T, F> provider, Predicate<T> itemInteractionAvailability) {
142 super(provider);
143 this.itemInteractionAvailability = itemInteractionAvailability;
144 }
145
146 @Override
147 public Range collapse(T item, Integer position) {
148 int removedChildren;
149 if (itemInteractionAvailability.test(item)) {
150 final Map<T, Set<T>> relationMap = accessChildMap();
151 final Set<T> children = relationMap.get(item);
152
153 removedChildren = (int) children.stream().filter(child -> !itemInteractionAvailability.test(child)).count();
154 } else {
155 getDataProvider().refreshAll();
156 return Range.emptyRange();
157 }
158
159 final Range toCollapse = super.collapse(item, position);
160 return Range.between(toCollapse.getStart(), toCollapse.getEnd() + removedChildren);
161 }
162
163 @SuppressWarnings("unchecked")
164 Map<T, Set<T>> accessChildMap() {
165 try {
166 final Field childMapField = HierarchyMapper.class.getDeclaredField("childMap");
167 childMapField.setAccessible(true);
168 return (Map<T, Set<T>>) childMapField.get(this);
169 } catch (NoSuchFieldException | IllegalAccessException e) {
170 throw new RuntimeException(e);
171 }
172 }
173 }
174
175
176
177
178
179
180
181
182
183 private static class EditableCellFilteringEditor<T> extends EditorImpl<T> {
184
185 private final Grid<T> grid;
186 private final Predicate<T> itemInteractionAvailability;
187 private final BiPredicate<Column, T> cellEditingAvailability;
188
189 EditableCellFilteringEditor(PropertySet<T> propertySet, Grid<T> grid, Predicate<T> itemInteractionAvailability, BiPredicate<Column, T> cellEditingAvailability) {
190 super(propertySet);
191 this.grid = grid;
192 this.itemInteractionAvailability = itemInteractionAvailability;
193 this.cellEditingAvailability = cellEditingAvailability;
194 setBinder(new BindingFilterableBinder<>(propertySet));
195 }
196
197 @Override
198 public BindingFilterableBinder<T> getBinder() {
199 return (BindingFilterableBinder<T>) super.getBinder();
200 }
201
202 @Override
203 @SuppressWarnings("unchecked")
204 protected void doEdit(T bean) {
205 if (!itemInteractionAvailability.test(bean)) {
206 cancel();
207 return;
208 }
209
210
211 final List<String> activeColumns = grid.getColumns().stream()
212 .filter(Column::isEditable)
213 .filter(column -> cellEditingAvailability.test(column, bean))
214 .map(Column::getId)
215 .collect(toList());
216
217
218 final List<Binder.Binding> binding = activeColumns.stream()
219 .map(grid::getColumn)
220 .map(Column::getId)
221 .map(id -> grid.getColumn(id).getEditorBinding())
222 .collect(toList());
223
224
225 getBinder().setBindings((List) binding);
226
227 super.doEdit(bean);
228
229
230 retainOnlyActiveCellComponents(activeColumns);
231 }
232
233 private void retainOnlyActiveCellComponents(List<String> activeColumns) {
234 final Iterator<Map.Entry<String, String>> componentMappings = getState().columnFields.entrySet().iterator();
235 final List<String> internalColumnIds = activeColumns.stream()
236 .map(grid::getColumn)
237 .map(this::getInternalIdForColumn)
238 .collect(toList());
239
240 while (componentMappings.hasNext()) {
241 final Map.Entry<String, String> mapping = componentMappings.next();
242 if (!internalColumnIds.contains(mapping.getKey())) {
243 componentMappings.remove();
244 }
245 }
246 }
247
248
249
250
251
252
253
254
255 static class BindingFilterableBinder<TYPE> extends Binder<TYPE> {
256
257 private BindingFilterableBinder(PropertySet<TYPE> propertySet) {
258 super(propertySet);
259 }
260
261 public void setBindings(List<Binding<TYPE, ?>> bindings) {
262 accessBindings().clear();
263 accessBindings().addAll(bindings);
264 }
265
266 @SuppressWarnings("unchecked")
267 List<Binding> accessBindings() {
268 try {
269 final Field childMapField = Binder.class.getDeclaredField("bindings");
270 childMapField.setAccessible(true);
271 return (List<Binding>) childMapField.get(this);
272 } catch (NoSuchFieldException | IllegalAccessException e) {
273 throw new RuntimeException(e);
274 }
275 }
276 }
277
278 }
279 }