View Javadoc
1   /**
2    * This file Copyright (c) 2019 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.contentapp.browser;
35  
36  import static java.util.stream.Collectors.toSet;
37  
38  import info.magnolia.ui.ValueContext;
39  import info.magnolia.ui.contentapp.browser.actions.ContextAwareActionPopup;
40  import info.magnolia.ui.contentapp.browser.actions.ShortcutActionsExecutor;
41  import info.magnolia.ui.contentapp.browser.drop.RowDragger;
42  import info.magnolia.ui.contentapp.configuration.GridViewDefinition;
43  
44  import java.util.HashSet;
45  import java.util.Iterator;
46  import java.util.Objects;
47  import java.util.Optional;
48  import java.util.stream.Collectors;
49  import java.util.stream.Stream;
50  
51  import com.vaadin.data.provider.DataProvider;
52  import com.vaadin.shared.ui.grid.DropMode;
53  import com.vaadin.shared.ui.grid.ScrollDestination;
54  import com.vaadin.ui.Component;
55  import com.vaadin.ui.Composite;
56  import com.vaadin.ui.Grid;
57  import com.vaadin.ui.components.grid.Editor;
58  
59  /**
60   * Base abstract implementation of Grid-based browsing views.
61   *
62   * @see info.magnolia.ui.contentapp.browser.ListView
63   * @see info.magnolia.ui.contentapp.browser.TreeView
64   */
65  public abstract class GridView<T, P extends GridViewPresenter<T>> extends Composite implements ContentView, Component.Focusable {
66  
67      private boolean scrolled;
68  
69      protected final ValueContext<T> valueContext;
70      protected final GridViewDefinition<T> definition;
71      protected final Grid<T> grid;
72  
73      protected final P presenter;
74      protected final Editor<T> editor;
75  
76      protected GridView(ValueContext<T> valueContext, GridViewDefinition<T> definition) {
77          this.valueContext = valueContext;
78          this.definition = definition;
79          this.presenter = createPresenter();
80          this.grid = presenter.grid();
81          this.editor = grid.getEditor();
82  
83          ContextAwareActionPopup<T> actionPopup = null;
84          if (!definition.isReadOnly()) {
85              actionPopup = create(ContextAwareActionPopup.class, grid);
86          }
87          final ShortcutActionsExecutor actionsExecutor =
88                  actionPopup != null ? actionPopup : ShortcutActionsExecutor.NOOP;
89  
90          grid.setSizeFull();
91          grid.setSelectionMode(definition.isMultiSelect() ? Grid.SelectionMode.MULTI : Grid.SelectionMode.SINGLE);
92          grid.addSelectionListener(event -> {
93              scrolled = false;
94              if (!valueContext.get().collect(Collectors.toSet()).equals(event.getAllSelectedItems())) {
95                  valueContext.set(event.getAllSelectedItems());
96              } else if (!event.isUserOriginated()) {
97                  scrollToSelectedItem();
98              }
99          });
100 
101         editor.setEnabled(false);
102         grid.getColumns().stream()
103                 .map(Grid.Column::getEditorBinding)
104                 .filter(Objects::nonNull)
105                 .findFirst()
106                 .ifPresent(any -> {
107                     editor.setBuffered(false);
108                     editor.setEnabled(true);
109                 });
110 
111         setLastColumnAsNotResizable(grid);
112 
113         Optional.ofNullable(definition.getDropConstraint())
114                 .ifPresent(dropConstraint -> create(RowDragger.class, grid, DropMode.BETWEEN, create(dropConstraint.getImplementationClass(), dropConstraint)));
115 
116 
117         valueContext.observe(items -> {
118             if (!grid.getSelectedItems().equals(items)) {
119                 grid.deselectAll();
120                 items.forEach(grid::select);
121             }
122         });
123         setCompositionRoot(create(GridWithShortcuts.class, grid, actionsExecutor));
124         grid.focus();
125         setSizeFull();
126     }
127 
128     @Override
129     public void setVisible(boolean visible) {
130         super.setVisible(visible);
131         if (visible) {
132             // covers the case when selecting item in tree, then switching tree -> list -> tree without any further selection in between
133             // would be covered by GridScrollExtension.setRestorePosition but this has to be disabled for Grid#scrollTo to work for other cases
134             scrollToSelectedItem();
135         } else {
136             scrolled = false;
137         }
138     }
139 
140     @Override
141     public void focus() {
142         scrolled = false;
143         scrollToSelectedItem();
144     }
145 
146     protected void scrollToSelectedItem() {
147         if (!scrolled && definition.isScrollToSelectedItem() && isVisible()) {
148             grid.getSelectedItems().stream().findFirst()
149                     .ifPresent(selectedItem -> {
150                         int index = 0;
151                         final DataProvider<T, ?> dataProvider = grid.getDataProvider();
152                         final Object selectedItemId = dataProvider.getId(selectedItem);
153                         for (Iterator<T> it = fetchItems().iterator(); it.hasNext(); ) {
154                             T t = it.next();
155                             if (dataProvider.getId(t).equals(selectedItemId)) {
156                                 scrolled = true;
157                                 break;
158                             } else {
159                                 index++;
160                             }
161                         }
162                         grid.scrollTo(index, ScrollDestination.MIDDLE); //org.vaadin.extension.gridscroll.GridScrollExtension.setRestorePosition has to be disabled
163                     });
164         }
165     }
166 
167     @Override
168     public int getTabIndex() {
169         return 0;
170     }
171 
172     @Override
173     public void setTabIndex(int tabIndex) {
174 
175     }
176 
177     Stream<T> fetchItems() {
178         return grid.getDataCommunicator().fetchItemsWithRange(0, Integer.MAX_VALUE).stream();
179     }
180 
181     protected abstract P createPresenter();
182 
183     protected void select(Stream<T> values) {
184         if (this.definition.isMultiSelect()) {
185             this.grid.asMultiSelect().updateSelection(values.collect(toSet()), new HashSet<>());
186         } else {
187             values.findFirst().ifPresent(this.grid.asSingleSelect()::setValue);
188         }
189     }
190 
191     /**
192      * A workaround to a bug in GridScrollExtension. See https://github.com/TatuLund/grid-scroll-extension/issues/10
193      */
194     private void setLastColumnAsNotResizable(Grid<T> grid) {
195         if (grid.getColumns().size() > 0) {
196             grid.getColumns().get(grid.getColumns().size() - 1).setResizable(false);
197         }
198     }
199 }