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