View Javadoc
1   /**
2    * This file Copyright (c) 2014-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.ui.admincentral.shellapp.pulse.item.list;
35  
36  import info.magnolia.i18nsystem.SimpleTranslator;
37  import info.magnolia.ui.admincentral.shellapp.pulse.data.PulseConstants;
38  import info.magnolia.ui.admincentral.shellapp.pulse.item.detail.PulseItemCategory;
39  import info.magnolia.ui.admincentral.shellapp.pulse.item.detail.PulseItemCategoryNavigator;
40  import info.magnolia.ui.admincentral.shellapp.pulse.item.list.footer.PulseListFooterView;
41  import info.magnolia.ui.vaadin.grid.MagnoliaTable;
42  
43  import java.util.List;
44  import java.util.Set;
45  
46  import javax.inject.Inject;
47  
48  import com.google.common.collect.Lists;
49  import com.vaadin.data.Container;
50  import com.vaadin.data.Container.ItemSetChangeEvent;
51  import com.vaadin.data.Item;
52  import com.vaadin.data.Property;
53  import com.vaadin.data.Property.ValueChangeEvent;
54  import com.vaadin.data.Property.ValueChangeListener;
55  import com.vaadin.event.ItemClickEvent;
56  import com.vaadin.event.MouseEvents.ClickEvent;
57  import com.vaadin.shared.ui.label.ContentMode;
58  import com.vaadin.ui.Component;
59  import com.vaadin.ui.Label;
60  import com.vaadin.ui.Table;
61  import com.vaadin.ui.Table.GeneratedRow;
62  import com.vaadin.ui.VerticalLayout;
63  
64  /**
65   * Abstract view implementation for displaying a list of items in pulse.
66   */
67  public abstract class AbstractPulseListView implements PulseListView {
68  
69      private final String[] order;
70  
71      private final String[] headers;
72  
73      private final Table itemTable = new MagnoliaTable();
74  
75      private final VerticalLayout root = new VerticalLayout();
76  
77      private final PulseItemCategoryNavigator navigator;
78  
79      private final SimpleTranslator i18n;
80  
81      private PulseListView.Listener listener;
82  
83      private Label emptyPlaceHolder;
84  
85      private Component footer;
86  
87      private Property.ValueChangeListener selectionListener = new Property.ValueChangeListener() {
88          @Override
89          public void valueChange(ValueChangeEvent event) {
90              listener.onSelectionChanged((Set<Object>) event.getProperty().getValue());
91          }
92      };
93  
94      private ValueChangeListener groupingListener = new ValueChangeListener() {
95          @Override
96          public void valueChange(ValueChangeEvent event) {
97              doGrouping((Boolean) event.getProperty().getValue());
98          }
99      };
100 
101     /*
102      * Row generator draws grouping headers if such are present in container
103      */
104     private Table.RowGenerator groupingRowGenerator = new Table.RowGenerator() {
105 
106         @Override
107         public GeneratedRow generateRow(Table table, Object itemId) {
108 
109             /*
110              * When sorting by type special items are inserted into Container to
111              * acts as a placeholder for grouping sub section. This row
112              * generator must render those special items.
113              */
114             String id = (String) table.getContainerProperty(itemId, "id").getValue();
115 
116             if (id != null && id.startsWith(PulseConstants.GROUP_PLACEHOLDER_ITEMID)) {
117                 Item item = table.getItem(itemId);
118                 return generateGroupingRow(item);
119             }
120 
121             return null;
122         }
123     };
124 
125     private boolean isGrouping = false;
126 
127     @Inject
128     public AbstractPulseListView(SimpleTranslator i18n, String[] order, String[] headers, String emptyMessage, PulseItemCategory... categories) {
129         this.i18n = i18n;
130         this.order = order;
131         this.headers = headers;
132         navigator = new PulseItemCategoryNavigator(i18n, true, false, categories);
133         navigator.addGroupingListener(new ValueChangeListener() {
134             @Override
135             public void valueChange(ValueChangeEvent event) {
136                 isGrouping = (Boolean) event.getProperty().getValue();
137             }
138         });
139         root.setSizeFull();
140         construct(emptyMessage);
141     }
142 
143     @Override
144     @Deprecated
145     public void refresh() {
146     }
147 
148     @Override
149     public void setDataSource(Container dataSource) {
150         itemTable.setContainerDataSource(dataSource);
151         itemTable.setVisibleColumns(order);
152         itemTable.setColumnHeaders(headers);
153 
154         int size = dataSource.size();
155         setComponentVisibility(size != 0);
156     }
157 
158     @Override
159     public void setListener(PulseListView.Listener listener) {
160         this.listener = listener;
161     }
162 
163     @Override
164     public void updateCategoryBadgeCount(PulseItemCategory category, int count) {
165         navigator.updateCategoryBadgeCount(category, count);
166     }
167 
168     @Override
169     public Component asVaadinComponent() {
170         return root;
171     }
172 
173     @Override
174     public void setFooter(PulseListFooterView footer) {
175         if (this.footer != null) {
176             root.removeComponent(this.footer);
177         }
178         this.footer = footer.asVaadinComponent();
179         root.addComponent(this.footer);
180         this.footer.setHeight("60px");
181     }
182 
183     protected final Listener getListener() {
184         return listener;
185     }
186 
187     private void construct(String emptyMessage) {
188         root.addComponent(navigator);
189         navigator.addCategoryChangeListener(new PulseItemCategoryNavigator.ItemCategoryChangedListener() {
190             @Override
191             public void itemCategoryChanged(PulseItemCategoryNavigator.CategoryChangedEvent event) {
192                 final PulseItemCategory category = event.getCategory();
193                 onItemCategoryChanged(category);
194             }
195         });
196 
197         emptyPlaceHolder = new Label();
198         emptyPlaceHolder.setContentMode(ContentMode.HTML);
199         emptyPlaceHolder.setValue(String.format("<span class=\"icon-pulse\"></span><div class=\"message\">%s</div>", emptyMessage));
200         emptyPlaceHolder.addStyleName("emptyplaceholder");
201 
202         root.addComponent(emptyPlaceHolder);
203 
204         constructTable();
205     }
206 
207     private void constructTable() {
208         root.addComponent(itemTable);
209         root.setExpandRatio(itemTable, 1f);
210         itemTable.setSizeFull();
211         itemTable.addStyleName("message-table");
212         itemTable.setSelectable(true);
213         itemTable.setMultiSelect(true);
214         itemTable.setRowGenerator(groupingRowGenerator);
215 
216         navigator.addGroupingListener(groupingListener);
217 
218         itemTable.addItemClickListener(new ItemClickEvent.ItemClickListener() {
219             @Override
220             public void itemClick(ItemClickEvent event) {
221                 onItemClicked(event, event.getItemId());
222             }
223         });
224 
225         itemTable.addValueChangeListener(selectionListener);
226         itemTable.addItemSetChangeListener(new Container.ItemSetChangeListener() {
227             @Override
228             public void containerItemSetChange(ItemSetChangeEvent event) {
229                 itemTable.setValue(null);
230                 long totalEntriesAmount = listener.getTotalEntriesAmount();
231                 setComponentVisibility(totalEntriesAmount > 0);
232                 listener.onItemSetChanged(totalEntriesAmount);
233             }
234         });
235     }
236 
237     private void doGrouping(boolean checked) {
238         listener.setGrouping(checked);
239     }
240 
241     private void setComponentVisibility(boolean entriesAvailable) {
242         if (!entriesAvailable) {
243             root.setExpandRatio(emptyPlaceHolder, 1f);
244         } else {
245             root.setExpandRatio(emptyPlaceHolder, 0f);
246         }
247 
248         itemTable.setVisible(entriesAvailable);
249         if (footer != null) {
250             footer.setVisible(entriesAvailable);
251         }
252         emptyPlaceHolder.setVisible(!entriesAvailable);
253     }
254 
255     /**
256      * A row generator draws grouping headers if such are present in the container. Default implementation returns null.
257      */
258     abstract protected GeneratedRow generateGroupingRow(Item item);
259 
260     protected SimpleTranslator getI18n() {
261         return i18n;
262     }
263 
264     protected Table getItemTable() {
265         return itemTable;
266     }
267 
268     @Override
269     public void setTabActive(PulseItemCategory category) {
270         navigator.setActive(category);
271         onItemCategoryChanged(category);
272     }
273 
274     @Override
275     public List<Object> getSelectedItemIds() {
276         Set<Object> itemIds = (Set<Object>) getItemTable().getValue();
277         return Lists.newArrayList(itemIds);
278     }
279 
280     protected void onItemClicked(ClickEvent event, final Object itemId) {
281         String itemIdAsString = String.valueOf(itemId);
282         // clicking on the group type header does nothing.
283         if (itemIdAsString.startsWith(PulseConstants.GROUP_PLACEHOLDER_ITEMID)) {
284             return;
285         }
286         if (event.isDoubleClick()) {
287             listener.onItemClicked(itemIdAsString);
288         } else {
289             if (itemTable.isSelected(itemIdAsString)) {
290                 itemTable.unselect(itemIdAsString);
291             }
292         }
293     }
294 
295     private void onItemCategoryChanged(final PulseItemCategory category) {
296         listener.filterByItemCategory(category);
297         // TODO fgrilli Unselect all when switching categories or nasty side effects will happen. See MGNLUI-1447
298         itemTable.setValue(null);
299 
300         boolean isGroupingEnabled = category == PulseItemCategory.ALL_TASKS || category == PulseItemCategory.ALL_MESSAGES;
301         navigator.enableGroupBy(isGroupingEnabled);
302 
303         doGrouping(isGroupingEnabled && isGrouping);
304     }
305 
306     /**
307      * The Vaadin {@link Table.ColumnGenerator ColumnGenerator} for denoting new messages or tasks in the Pulse list views.
308      */
309     protected class PulseNewItemColumnGenerator implements Table.ColumnGenerator {
310 
311         // void public constructor to instantiate from subclasses in different packages
312         public PulseNewItemColumnGenerator() {
313         }
314 
315         @Override
316         public Object generateCell(Table source, Object itemId, Object columnId) {
317             Property<Boolean> newProperty = source.getContainerProperty(itemId, columnId);
318             boolean isNew = newProperty != null && newProperty.getValue();
319             if (isNew) {
320                 return "<span class=\"icon icon-tick new-message\"></span>";
321             }
322             return null;
323         }
324     }
325 }