View Javadoc
1   /**
2    * This file Copyright (c) 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.editor;
35  
36  import static java.util.Collections.singletonList;
37  import static java.util.stream.Collectors.toMap;
38  
39  import info.magnolia.ui.datasource.PropertySetFactory;
40  import info.magnolia.ui.datasource.optionlist.Option;
41  import info.magnolia.ui.datasource.optionlist.OptionListDefinition;
42  import info.magnolia.ui.field.AbstractSelectFieldDefinition;
43  import info.magnolia.ui.field.FieldBinder;
44  import info.magnolia.ui.field.WithPropertyNameDecorator;
45  import info.magnolia.ui.framework.ioc.UiComponentProvider;
46  import info.magnolia.ui.framework.layout.PlainFormLayoutDefinition;
47  
48  import java.util.Collections;
49  import java.util.List;
50  import java.util.Locale;
51  import java.util.Map;
52  import java.util.Optional;
53  
54  import javax.inject.Inject;
55  
56  import com.vaadin.data.Binder;
57  import com.vaadin.data.BinderValidationStatus;
58  import com.vaadin.data.HasValue;
59  import com.vaadin.ui.Component;
60  import com.vaadin.ui.Layout;
61  import com.vaadin.ui.VerticalLayout;
62  
63  /**
64   * Special kind of form that allows to switch between multiple views using selection field.
65   * Only selected sub-form is written upon saving the changes.
66   *
67   * @param <T>
68   *     item type.
69   */
70  public class SwitchableFormView<T> implements EditorView<T> {
71  
72      private final SwitchableFormDefinition<T> definition;
73  
74      private final PropertySetFactory<T> propertySetFactory;
75  
76      private final Binder<T> binder;
77  
78      private final Map<String, EditorView<T>> representations;
79  
80      private final Locale locale;
81  
82      private VerticalLayout layout = new VerticalLayout();
83  
84      @Inject
85      public SwitchableFormView(SwitchableFormDefinition<T> definition, PropertySetFactory<T> propertySetFactory, LocaleContext localeContext) {
86          this.definition = definition;
87          this.propertySetFactory = propertySetFactory;
88          this.representations = definition.getForms().stream()
89                  .collect(toMap(
90                          EditorDefinition::getName,
91                          childEditor -> this.create(new CompositeFormDefinition<>(childEditor, definition.getPropertyNameDecorator()))));
92          this.locale = localeContext.getDefault();
93          this.binder = bindOptionField();
94  
95          layout();
96      }
97  
98      @Override
99      public void populate(T item) {
100         this.binder.readBean(item);
101         this.representations.values().forEach(representation -> representation.populate(item));
102 
103         layout();
104     }
105 
106     @Override
107     public List<BinderValidationStatus<?>> validate() {
108         Option currentRepresentation = getOptionField().getValue();
109         return this.subForm(currentRepresentation)
110                 .map(EditorView::validate)
111                 .orElseGet(Collections::emptyList);
112     }
113 
114     @Override
115     public void write(T item) {
116         this.binder.writeBeanIfValid(item);
117         this.representations.values().forEach(representation -> representation.write(item));
118     }
119 
120     private void layout() {
121         layout.removeAllComponents();
122         layout.setMargin(false);
123         HasValue<Option> optionField = getOptionField();
124         layout.addComponent((Component) optionField);
125 
126         optionField.getOptionalValue()
127                 .flatMap(this::subForm)
128                 .ifPresent(view -> layout.addComponent(view.asVaadinComponent()));
129     }
130 
131     private Binder<T> bindOptionField() {
132         AbstractSelectFieldDefinition<Option, OptionListDefinition> fieldDefinition = definition.getField();
133         UiComponentProvider componentProvider = getComponentProvider().inChildContext(fieldDefinition);
134         Binder<T> binder = Binder.withPropertySet(propertySetFactory.fromFieldDefinitions(singletonList(fieldDefinition), this.locale));
135 
136         FieldBinder<Option> fieldBinder = componentProvider.newInstance(fieldDefinition.getFieldBinderClass());
137         HasValue<Option> optionsField = componentProvider.newInstance(fieldDefinition.getFactoryClass()).createField();
138         fieldBinder
139                 .configureBinding(fieldDefinition, binder.forField(optionsField))
140                 .bind(fieldDefinition.getName());
141 
142         optionsField.addValueChangeListener(e -> {
143             subForm(e.getOldValue()).ifPresent(oldView -> layout.removeComponent(oldView.asVaadinComponent()));
144             subForm(e.getValue()).ifPresent(newView -> layout.addComponent(newView.asVaadinComponent()));
145         });
146 
147         return binder;
148     }
149 
150     private HasValue<Option> getOptionField() {
151         return binder.getBinding(definition.getField().getName())
152                 .map(binding -> (HasValue<Option>) binding.getField())
153                 .orElse(null);
154     }
155 
156     private Optional<EditorView<T>> subForm(Option selectedOption) {
157         return Optional.ofNullable(selectedOption).map(option -> representations.get(option.getValue()));
158     }
159 
160     private class CompositeFormDefinition<T> extends ConfiguredFormDefinition<T> implements WithPropertyNameDecorator {
161 
162         private final Class<PropertyNameDecorator> propertyNameDecorator;
163 
164         public CompositeFormDefinition(FormDefinition<T> definition, Class<PropertyNameDecorator> propertyNameDecorator) {
165             this.setLayout(new PlainFormLayoutDefinition());
166             this.setName(definition.getName());
167             this.setProperties(definition.getProperties());
168             this.propertyNameDecorator = propertyNameDecorator;
169         }
170 
171         @Override
172         public Class getPropertyNameDecorator() {
173             return propertyNameDecorator;
174         }
175     }
176 
177     @Override
178     public Component asVaadinComponent() {
179         return layout;
180     }
181 }