View Javadoc
1   /**
2    * This file Copyright (c) 2013-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.contentapp.detail;
35  
36  import static java.util.stream.Collectors.*;
37  
38  import info.magnolia.ui.CloseHandler;
39  import info.magnolia.ui.api.app.SubAppContext;
40  import info.magnolia.ui.api.i18n.I18NAuthoringSupport;
41  import info.magnolia.ui.api.location.DefaultLocation;
42  import info.magnolia.ui.api.location.Location;
43  import info.magnolia.ui.contentapp.ItemDescriber;
44  import info.magnolia.ui.ValueContext;
45  import info.magnolia.ui.dialog.ActionExecution;
46  import info.magnolia.ui.dialog.EditorActionBar;
47  import info.magnolia.ui.field.LocaleSelector;
48  import info.magnolia.ui.editor.FormDefinition;
49  import info.magnolia.ui.editor.LocaleContext;
50  import info.magnolia.ui.framework.ContextProperty;
51  import info.magnolia.ui.UIComponent;
52  import info.magnolia.ui.framework.UiComponentContext;
53  import info.magnolia.ui.editor.ItemProviderStrategy;
54  import info.magnolia.ui.editor.EditorView;
55  import info.magnolia.ui.editor.FormView;
56  
57  import java.util.List;
58  import java.util.Map;
59  import java.util.Optional;
60  import java.util.stream.Collectors;
61  
62  import javax.inject.Inject;
63  
64  import org.apache.commons.lang3.StringUtils;
65  
66  import com.vaadin.event.ShortcutListener;
67  import com.vaadin.ui.Component;
68  import com.vaadin.ui.CssLayout;
69  import com.vaadin.ui.Panel;
70  
71  import lombok.AccessLevel;
72  import lombok.Getter;
73  
74  /**
75   * Generic form detail subapp.
76   *
77   * @param <T> item type.
78   */
79  public class ContentDetailSubApp<T> extends AbstractDetailSubApp<ContentDetailSubApp.SubAppView> {
80  
81      private final DetailDescriptor<T, ?> subAppDescriptor;
82      private final I18NAuthoringSupport<T> i18NAuthoringSupport;
83      private final LocaleContext localeContext;
84      private final ValueContext<T> valueContext;
85      private final ItemDescriber<T> itemDescriber;
86  
87      @Inject
88      protected ContentDetailSubApp(
89              SubAppContext subAppContext,
90              I18NAuthoringSupport<T> i18NAuthoringSupport,
91              DetailDescriptor<T, ?> descriptor,
92              LocaleContext localeContext,
93              ValueContext<T> valueContext,
94              ItemDescriber<T> itemDescriber) {
95          super(subAppContext, new SubAppView());
96          this.valueContext = valueContext;
97          this.localeContext = localeContext;
98          this.subAppDescriptor = descriptor;
99          this.i18NAuthoringSupport = i18NAuthoringSupport;
100         this.itemDescriber = itemDescriber;
101     }
102 
103     @Override
104     public SubAppView start(Location location) {
105         super.start(location);
106 
107         final SubAppView view = getView();
108 
109         localeContext.defaultLocale().set(getSubAppContext().getAuthoringLocale());
110 
111         bindInstance(UIComponent.class, view);
112         bindInstance(CloseHandler.class, () -> getSubAppContext().close());
113 
114         ItemProviderStrategy<T, DetailLocation> itemProviderStrategy = create(subAppDescriptor.getItemProvider());
115         Optional<T> item = itemProviderStrategy.read(DetailLocation.wrap(location));
116 
117         item.ifPresent(it -> localeContext.populateFromI18NAuthoringSupport(it, i18NAuthoringSupport));
118 
119         FormDefinition<T> formDefinition = subAppDescriptor.getForm();
120         FormView<T> form = (FormView<T>) create(formDefinition);
121 
122         EditorActionBar<T> editorActionBar = view.create(EditorActionBar.class);
123         List<ActionExecution<T>> actionExecutions =
124                 ActionExecution.<T>fromDefinitions(subAppDescriptor.getActions().values(), getComponentProvider())
125                         .collect(toList());
126 
127         editorActionBar
128                 .withActions(actionExecutions)
129                 .withLayoutDefinition(subAppDescriptor.getFooterLayout());
130 
131         if (formDefinition.hasI18NProperties()) {
132             editorActionBar = editorActionBar.withLabeledControl("localeSelector", create(LocaleSelector.class));
133         }
134 
135         Map<Integer, Runnable> shortcuts = actionExecutions.stream()
136                 .filter(execution -> execution.getDefinition().getShortcut() > 0)
137                 .collect(toMap(execution -> execution.getDefinition().getShortcut(), actionExecution -> (Runnable) actionExecution::execute));
138 
139         view.bindInstance(EditorView.class, form);
140         view.setShortcuts(shortcuts);
141 
142         view.getLayout().setTitle(subAppDescriptor.getLabel());
143         view.getLayout().setMargin(false);
144         view.getLayout().setForm(form.asVaadinComponent());
145         view.getLayout().setFooter(editorActionBar.layout());
146         view.getLayout().construct();
147 
148         item.ifPresent(it -> {
149             valueContext.set(it);
150             form.populate(it);
151         });
152 
153         return view;
154     }
155 
156     @Override
157     public String getCaption() {
158         return itemDescriber.apply(valueContext.get().collect(Collectors.toList()));
159     }
160 
161     @Override
162     public void locationChanged(Location location) {
163         // do nothing, detail sub-app isn't really designed to support
164         // locale changes
165     }
166 
167     /**
168      * LocationContext.
169      */
170     public interface LocationContext extends UiComponentContext {
171         ContextProperty<DetailLocation> location();
172     }
173 
174     /**
175      * ItemLocation used by implementations of {@link ContentDetailSubApp}.
176      * Extends the Default Location by adding fields for:
177      * <ul>
178      * <li>the nodePath (some/node/path)</li>
179      * <li>the viewType</li>
180      * <li>the node version (version)</li>
181      * </ul>
182      * <p>
183      * {@code appType:appName:subAppId;some/node/path:viewType:version}
184      */
185     public static class DetailLocation extends DefaultLocation {
186 
187         private String viewType;
188         private String nodePath;
189         private String version;
190         // Position of the parameter based on the ':' used as separator.
191         private final static int NODE_PATH_PARAM_POSITION = 0;
192         private final static int VIEW_TYPE_PARAM_POSITION = 1;
193         private final static int VERSION_PARAM_POSITION = 2;
194 
195         public DetailLocation(String appName, String subAppId, String parameter) {
196             super(LOCATION_TYPE_APP, appName, subAppId, parameter);
197 
198             setNodePath(extractNodePath(parameter));
199             setViewType(extractViewType(parameter));
200             setVersion(extractVersion(parameter));
201         }
202 
203         public DetailLocation(String appName, String subAppId, String viewType, String nodePath, String version) {
204             super(LOCATION_TYPE_APP, appName, subAppId);
205 
206             setNodePath(nodePath);
207             setViewType(viewType);
208             setVersion(version);
209             updateParameter();
210         }
211 
212         public String getNodePath() {
213             return unescapeSpecialCharacters(nodePath);
214         }
215 
216         /**
217          * If the node path is empty, assume root path.
218          */
219         private void setNodePath(String nodePath) {
220             this.nodePath = (nodePath == null || nodePath.isEmpty()) ? "/" : escapeSpecialCharacters(nodePath);
221         }
222 
223         public String getViewType() {
224             return viewType;
225         }
226 
227         public void setViewType(String viewType) {
228             this.viewType = viewType;
229         }
230 
231         public String getVersion() {
232             return unescapeSpecialCharacters(version);
233         }
234 
235         public void setVersion(String version) {
236             this.version = escapeSpecialCharacters(version);
237         }
238 
239         public boolean hasVersion() {
240             return StringUtils.isNotBlank(version);
241         }
242 
243         /**
244          * Extract the Node path from the parameter.
245          *
246          * @param parameter some/node/path:viewType:version
247          * @return some/node/path
248          */
249         private String extractNodePath(String parameter) {
250             return getParameter(parameter, NODE_PATH_PARAM_POSITION);
251         }
252 
253         /**
254          * Extract the viewType from the parameter.
255          *
256          * @param parameter some/node/path:viewType:version
257          * @return viewType
258          */
259         private String extractViewType(String parameter) {
260             String action = getParameter(parameter, VIEW_TYPE_PARAM_POSITION);
261             return action;
262         }
263 
264         /**
265          * Extract the Node Version from the parameter.
266          *
267          * @param parameter some/node/path:viewType:version
268          * @return version
269          */
270         private String extractVersion(String parameter) {
271             return getParameter(parameter, VERSION_PARAM_POSITION);
272         }
273 
274         protected String getParameter(String parameter, int position) {
275             String arguments[] = StringUtils.split(parameter, ':');
276             if (position <= arguments.length - 1) {
277                 return arguments[position];
278             }
279             return "";
280         }
281 
282         protected void updateParameter() {
283             StringBuilder sb = new StringBuilder();
284             sb.append(nodePath);
285             sb.append(":");
286             sb.append(viewType);
287             if (StringUtils.isNotBlank(version)) {
288                 sb.append(":");
289                 sb.append(version);
290             }
291             super.setParameter(sb.toString());
292         }
293 
294         public static DetailLocation wrap(Location location) {
295             return new DetailLocation(location.getAppName(), location.getSubAppId(), location.getParameter());
296         }
297 
298         public void updateNodePath(String newNodePath) {
299             setNodePath(newNodePath);
300             updateParameter();
301         }
302 
303         public void updateViewtype(String newViewType) {
304             setViewType(newViewType);
305             updateParameter();
306         }
307 
308         public void updateVersion(String newVersion) {
309             setVersion(newVersion);
310             updateParameter();
311         }
312     }
313 
314     @Getter(AccessLevel.PACKAGE)
315     public static class SubAppView extends Panel implements UIComponent {
316 
317         private DetailViewLayoutDetailViewLayout.html#DetailViewLayout">DetailViewLayout layout = new DetailViewLayout();
318 
319         public SubAppView() {
320             setSizeFull();
321             addStyleName("detail");
322             CssLayout contentViewWrapper = new CssLayout();
323             contentViewWrapper.addStyleName("detailview");
324             contentViewWrapper.setSizeFull();
325             contentViewWrapper.addComponent(layout);
326             layout.setSizeFull();
327 
328             setContent(contentViewWrapper);
329         }
330 
331         @Override
332         public Component asVaadinComponent() {
333             return this;
334         }
335 
336         public void setShortcuts(Map<Integer, Runnable> shortcuts) {
337             shortcuts.forEach((keyCode, execution) -> {
338                 addShortcutListener(new ShortcutListener("", keyCode, new int[]{}) {
339                     @Override
340                     public void handleAction(Object sender, Object target) {
341                         execution.run();
342                     }
343                 });
344             });
345         }
346     }
347 }