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 com.google.common.base.Objects;
40  import info.magnolia.ui.datasource.PropertySetFactory;
41  import info.magnolia.ui.datasource.optionlist.Option;
42  import info.magnolia.ui.datasource.optionlist.OptionListDefinition;
43  import info.magnolia.ui.field.AbstractSelectFieldDefinition;
44  import info.magnolia.ui.field.FieldBinder;
45  import info.magnolia.ui.field.WithPropertyNameDecorator;
46  import info.magnolia.ui.framework.ioc.UiComponentProvider;
47  import info.magnolia.ui.framework.layout.PlainFormLayoutDefinition;
48  
49  import java.util.Collections;
50  import java.util.List;
51  import java.util.Locale;
52  import java.util.Map;
53  import java.util.Optional;
54  
55  import javax.inject.Inject;
56  
57  import com.vaadin.data.Binder;
58  import com.vaadin.data.BinderValidationStatus;
59  import com.vaadin.data.HasValue;
60  import com.vaadin.ui.Component;
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         subForm(getOptionField().getValue()).ifPresent(form -> form.write(item));
118     }
119 
120     @Override
121     public void applyDefaults() {
122         definition.getField().getDatasource().getOptions().stream()
123                 .filter(option -> Objects.equal(option.getValue(), definition.getField().getDefaultValue()))
124                 .findFirst()
125                 .ifPresent(defaultOption -> {
126                     getOptionField().setValue(defaultOption);
127                     subForm(defaultOption).ifPresent(EditorView::applyDefaults);
128                 });
129     }
130 
131     private void layout() {
132         layout.removeAllComponents();
133         layout.setMargin(false);
134         HasValue<Option> optionField = getOptionField();
135         layout.addComponent((Component) optionField);
136 
137         ((Component) optionField).setCaption(null);
138         optionField.getOptionalValue()
139                 .flatMap(this::subForm)
140                 .ifPresent(view -> layout.addComponent(view.asVaadinComponent()));
141     }
142 
143     private Binder<T> bindOptionField() {
144         AbstractSelectFieldDefinition<Option, OptionListDefinition> fieldDefinition = definition.getField();
145         UiComponentProvider componentProvider = getComponentProvider().inChildContext(fieldDefinition);
146         Binder<T> binder = Binder.withPropertySet(propertySetFactory.fromFieldDefinitions(singletonList(fieldDefinition), this.locale));
147 
148         FieldBinder<Option> fieldBinder = componentProvider.newInstance(fieldDefinition.getFieldBinderClass());
149         HasValue<Option> optionsField = componentProvider.newInstance(fieldDefinition.getFactoryClass()).createField();
150         fieldBinder
151                 .configureBinding(fieldDefinition, binder.forField(optionsField))
152                 .bind(fieldDefinition.getName());
153 
154         optionsField.addValueChangeListener(e -> {
155             subForm(e.getOldValue()).ifPresent(oldView -> layout.removeComponent(oldView.asVaadinComponent()));
156             subForm(e.getValue()).ifPresent(newView -> layout.addComponent(newView.asVaadinComponent()));
157         });
158 
159         return binder;
160     }
161 
162     private HasValue<Option> getOptionField() {
163         return binder.getBinding(definition.getField().getName())
164                 .map(binding -> (HasValue<Option>) binding.getField())
165                 .orElse(null);
166     }
167 
168     private Optional<EditorView<T>> subForm(Option selectedOption) {
169         return Optional.ofNullable(selectedOption).map(option -> representations.get(option.getValue()));
170     }
171 
172     private class CompositeFormDefinition<T> extends ConfiguredFormDefinition<T> implements WithPropertyNameDecorator {
173 
174         private final Class<PropertyNameDecorator> propertyNameDecorator;
175 
176         public CompositeFormDefinition(FormDefinition<T> definition, Class<PropertyNameDecorator> propertyNameDecorator) {
177             this.setLayout(new PlainFormLayoutDefinition());
178             this.setName(definition.getName());
179             this.setProperties(definition.getProperties());
180             this.propertyNameDecorator = propertyNameDecorator;
181         }
182 
183         @Override
184         public Class getPropertyNameDecorator() {
185             return propertyNameDecorator;
186         }
187     }
188 
189     @Override
190     public Component asVaadinComponent() {
191         return layout;
192     }
193 }