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.admincentral.shellapp.pulse.item.list;
35
36 import info.magnolia.i18nsystem.SimpleTranslator;
37 import info.magnolia.ui.admincentral.shellapp.pulse.item.detail.PulseItemCategory;
38 import info.magnolia.ui.admincentral.shellapp.pulse.item.detail.PulseItemCategoryNavigator;
39 import info.magnolia.ui.api.shell.Shell;
40 import info.magnolia.ui.vaadin.grid.MagnoliaTreeTable;
41
42 import java.util.Collection;
43 import java.util.HashSet;
44 import java.util.Set;
45
46 import javax.inject.Inject;
47
48 import com.vaadin.data.Container;
49 import com.vaadin.data.Container.ItemSetChangeEvent;
50 import com.vaadin.data.Item;
51 import com.vaadin.data.Property;
52 import com.vaadin.data.Property.ValueChangeEvent;
53 import com.vaadin.data.Property.ValueChangeListener;
54 import com.vaadin.event.ItemClickEvent;
55 import com.vaadin.event.MouseEvents.ClickEvent;
56 import com.vaadin.shared.ui.label.ContentMode;
57 import com.vaadin.ui.Component;
58 import com.vaadin.ui.Label;
59 import com.vaadin.ui.Table;
60 import com.vaadin.ui.Table.GeneratedRow;
61 import com.vaadin.ui.TreeTable;
62 import com.vaadin.ui.VerticalLayout;
63
64
65
66
67 public abstract class AbstractPulseListView implements PulseListView {
68
69 public static final String GROUP_PLACEHOLDER_ITEMID = "##SUBSECTION##";
70
71 private final String[] order;
72
73 private final String[] headers;
74
75 private final TreeTable itemTable = new MagnoliaTreeTable();
76
77 private final VerticalLayout root = new VerticalLayout();
78
79 private final PulseItemCategoryNavigator navigator;
80
81 private final SimpleTranslator i18n;
82
83 private PulseListView.Listener listener;
84
85 private Label emptyPlaceHolder;
86
87 private PulseListFooter footer;
88
89 private PulseItemCategory currentlySelectedCategory = PulseItemCategory.UNCLAIMED;
90
91 private boolean categoryFilterAlreadyApplied;
92
93 private Property.ValueChangeListener selectionListener = new Property.ValueChangeListener() {
94
95 private Set<Object> prevSelected = new HashSet<Object>();
96
97 @Override
98 public void valueChange(ValueChangeEvent event) {
99
100
101
102
103 itemTable.removeValueChangeListener(this);
104
105 @SuppressWarnings("unchecked")
106 Set<Object> currSelected = new HashSet<Object>((Set<Object>) event.getProperty().getValue());
107 Set<Object> added = new HashSet<Object>(currSelected);
108 Set<Object> removed = new HashSet<Object>(prevSelected);
109
110 added.removeAll(prevSelected);
111 removed.removeAll(currSelected);
112
113 prevSelected = currSelected;
114
115
116
117
118
119 selectChildren(added, true);
120 selectChildren(removed, false);
121
122
123 for (Object child : removed) {
124 Object parent = listener.getParent(child);
125 if (parent != null) {
126 itemTable.unselect(parent);
127 prevSelected.remove(parent);
128 }
129 }
130
131
132
133
134 for (Object child : added) {
135 Object parent = listener.getParent(child);
136 if (isAllChildrenSelected(parent)) {
137 itemTable.select(parent);
138 prevSelected.add(parent);
139 } else {
140 itemTable.unselect(parent);
141 prevSelected.remove(parent);
142 }
143 }
144
145 itemTable.addValueChangeListener(this);
146 footer.updateStatus();
147 }
148
149 private boolean isAllChildrenSelected(Object parent) {
150 if (parent == null) {
151 return false;
152 }
153
154 Collection<?> siblings = listener.getGroup(parent);
155 boolean allSelected = true;
156
157 if (siblings != null) {
158 for (Object sibling : siblings) {
159 if (!itemTable.isSelected(sibling)) {
160 allSelected = false;
161 }
162 }
163 } else {
164 return false;
165 }
166
167 return allSelected;
168 }
169
170 private void selectChildren(Set<Object> parents, boolean check) {
171 for (Object parent : parents) {
172 Collection<?> group = listener.getGroup(parent);
173 if (group != null) {
174 for (Object child : group) {
175 if (check) {
176 itemTable.select(child);
177 prevSelected.add(child);
178 } else {
179 itemTable.unselect(child);
180 prevSelected.remove(child);
181 }
182 }
183 }
184 }
185 }
186 };
187
188 private ValueChangeListener groupingListener = new ValueChangeListener() {
189
190 @Override
191 public void valueChange(ValueChangeEvent event) {
192 boolean checked = event.getProperty().getValue().equals(Boolean.TRUE);
193 doGrouping(checked);
194 }
195 };
196
197
198
199
200 private Table.RowGenerator groupingRowGenerator = new Table.RowGenerator() {
201
202 @Override
203 public GeneratedRow generateRow(Table table, Object itemId) {
204
205
206
207
208
209
210 if (itemId.toString().startsWith(GROUP_PLACEHOLDER_ITEMID)) {
211 Item item = table.getItem(itemId);
212 return generateGroupingRow(item);
213 }
214
215 return null;
216 }
217 };
218
219 @Inject
220 public AbstractPulseListView(Shell shell, SimpleTranslator i18n, String[] order, String[] headers, String emptyMessage, PulseItemCategory... categories) {
221 this.i18n = i18n;
222 this.order = order;
223 this.headers = headers;
224 navigator = new PulseItemCategoryNavigator(i18n, true, false, categories);
225 root.setSizeFull();
226 construct(emptyMessage);
227 }
228
229 @Override
230 public void refresh() {
231
232 if ((currentlySelectedCategory != PulseItemCategory.ALL_MESSAGES || currentlySelectedCategory != PulseItemCategory.ALL_TASKS) && !categoryFilterAlreadyApplied) {
233 listener.filterByItemCategory(currentlySelectedCategory);
234 }
235
236 categoryFilterAlreadyApplied = false;
237 footer.updateStatus();
238 itemTable.sort();
239 doGrouping(false);
240 }
241
242 @Override
243 public void setDataSource(Container dataSource) {
244 itemTable.setContainerDataSource(dataSource);
245 itemTable.setVisibleColumns(order);
246 itemTable.setColumnHeaders(headers);
247 }
248
249 @Override
250 public void setListener(PulseListView.Listener listener) {
251 this.listener = listener;
252
253
254 this.footer.setMessagesListener(listener);
255 }
256
257 @Override
258 public void updateCategoryBadgeCount(PulseItemCategory category, int count) {
259 navigator.updateCategoryBadgeCount(category, count);
260 }
261
262 @Override
263 public Component asVaadinComponent() {
264 return root;
265 }
266
267 public void setFooter(PulseListFooter footer) {
268 this.footer = footer;
269 root.addComponent(footer);
270 footer.setHeight("60px");
271 }
272
273 private void construct(String emptyMessage) {
274 root.addComponent(navigator);
275 navigator.addCategoryChangeListener(new PulseItemCategoryNavigator.ItemCategoryChangedListener() {
276
277 @Override
278 public void itemCategoryChanged(PulseItemCategoryNavigator.CategoryChangedEvent event) {
279 final PulseItemCategory category = event.getCategory();
280 onItemCategoryChanged(category);
281 }
282 });
283
284 constructTable();
285
286 emptyPlaceHolder = new Label();
287 emptyPlaceHolder.setContentMode(ContentMode.HTML);
288 emptyPlaceHolder.setValue(String.format("<span class=\"icon-pulse\"></span><div class=\"message\">%s</div>", emptyMessage));
289 emptyPlaceHolder.addStyleName("emptyplaceholder");
290
291 root.addComponent(emptyPlaceHolder);
292
293
294 footer = new PulseListFooter(itemTable, i18n, false);
295 root.addComponent(footer);
296
297 setComponentVisibility(itemTable.getContainerDataSource());
298 }
299
300 private void constructTable() {
301 root.addComponent(itemTable);
302 root.setExpandRatio(itemTable, 1f);
303 itemTable.setSizeFull();
304 itemTable.addStyleName("message-table");
305 itemTable.setSelectable(true);
306 itemTable.setMultiSelect(true);
307 itemTable.setRowGenerator(groupingRowGenerator);
308
309 navigator.addGroupingListener(groupingListener);
310
311 itemTable.addItemClickListener(new ItemClickEvent.ItemClickListener() {
312 @Override
313 public void itemClick(ItemClickEvent event) {
314 onItemClicked(event, event.getItemId());
315 }
316 });
317
318 itemTable.addValueChangeListener(selectionListener);
319 itemTable.addItemSetChangeListener(new Container.ItemSetChangeListener() {
320 @Override
321 public void containerItemSetChange(ItemSetChangeEvent event) {
322 setComponentVisibility(event.getContainer());
323 }
324 });
325 }
326
327 private void doGrouping(boolean checked) {
328 listener.setGrouping(checked);
329
330 if (checked) {
331 for (Object itemId : itemTable.getItemIds()) {
332 itemTable.setCollapsed(itemId, false);
333 }
334 }
335 }
336
337 private void setComponentVisibility(Container container) {
338 boolean isEmptyList = container.getItemIds().size() == 0;
339 if (isEmptyList) {
340 root.setExpandRatio(emptyPlaceHolder, 1f);
341 } else {
342 root.setExpandRatio(emptyPlaceHolder, 0f);
343 }
344
345 itemTable.setVisible(!isEmptyList);
346 footer.setVisible(!isEmptyList);
347 emptyPlaceHolder.setVisible(isEmptyList);
348 }
349
350
351
352
353 abstract protected GeneratedRow generateGroupingRow(Item item);
354
355 protected SimpleTranslator getI18n() {
356 return i18n;
357 }
358
359 protected TreeTable getItemTable() {
360 return itemTable;
361 }
362
363 @Override
364 public void setTabActive(PulseItemCategory category) {
365 navigator.setActive(category);
366 onItemCategoryChanged(category);
367 }
368
369 protected PulseListFooter getFooter() {
370 return footer;
371 }
372
373 protected void onItemClicked(ClickEvent event, final Object itemId) {
374 String itemIdAsString = String.valueOf(itemId);
375
376 if (itemIdAsString.startsWith(GROUP_PLACEHOLDER_ITEMID)) {
377 return;
378 }
379 if (event.isDoubleClick()) {
380 listener.onItemClicked(itemIdAsString);
381 } else {
382 if (itemTable.isSelected(itemIdAsString)) {
383 itemTable.unselect(itemIdAsString);
384 }
385 }
386 }
387
388 private void onItemCategoryChanged(final PulseItemCategory category) {
389 currentlySelectedCategory = category;
390 listener.filterByItemCategory(category);
391 categoryFilterAlreadyApplied = true;
392
393 for (String id : (Set<String>) itemTable.getValue()) {
394 itemTable.unselect(id);
395 }
396
397 switch (category) {
398 case ALL_TASKS:
399 case ALL_MESSAGES:
400 navigator.enableGroupBy(true);
401 break;
402 default:
403 navigator.enableGroupBy(false);
404 }
405
406 refresh();
407 }
408
409
410
411
412 protected class PulseNewItemColumnGenerator implements Table.ColumnGenerator {
413
414
415 public PulseNewItemColumnGenerator() {
416 }
417
418 @Override
419 public Object generateCell(Table source, Object itemId, Object columnId) {
420 Property<Boolean> newProperty = source.getContainerProperty(itemId, columnId);
421 boolean isNew = newProperty != null && newProperty.getValue();
422 if (isNew) {
423 return "<span class=\"icon icon-tick new-message\"></span>";
424 }
425 return null;
426 }
427 }
428 }