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.poc;
35  
36  import static com.vaadin.server.Sizeable.Unit.PIXELS;
37  
38  import info.magnolia.objectfactory.Components;
39  import info.magnolia.ui.field.RichTextFieldDefinition;
40  import info.magnolia.ui.field.factory.RichTextFieldFactory;
41  import info.magnolia.ui.vaadin.ckeditor.MagnoliaCKEditorTextField;
42  
43  import java.beans.PropertyDescriptor;
44  import java.util.Arrays;
45  import java.util.Comparator;
46  import java.util.HashMap;
47  import java.util.List;
48  import java.util.Map;
49  import java.util.Optional;
50  import java.util.stream.Collectors;
51  
52  import javax.servlet.annotation.WebServlet;
53  
54  import org.apache.commons.beanutils.PropertyUtils;
55  import org.apache.commons.lang3.ClassUtils;
56  
57  import com.vaadin.annotations.Theme;
58  import com.vaadin.annotations.Title;
59  import com.vaadin.annotations.VaadinServletConfiguration;
60  import com.vaadin.annotations.Widgetset;
61  import com.vaadin.data.Binder;
62  import com.vaadin.data.converter.StringToLongConverter;
63  import com.vaadin.server.VaadinRequest;
64  import com.vaadin.server.VaadinServlet;
65  import com.vaadin.shared.ui.ContentMode;
66  import com.vaadin.ui.AbstractField;
67  import com.vaadin.ui.Button;
68  import com.vaadin.ui.CheckBox;
69  import com.vaadin.ui.Component;
70  import com.vaadin.ui.CssLayout;
71  import com.vaadin.ui.FormLayout;
72  import com.vaadin.ui.Label;
73  import com.vaadin.ui.TextField;
74  import com.vaadin.ui.UI;
75  
76  /**
77   * CKEditorUI.
78   */
79  @Theme("poctheme")
80  @Title("Magnolia 6 Resurface - CKEditor")
81  @Widgetset("info.magnolia.poc.Widgetset")
82  public class CKEditorUI extends UI {
83      private static final Map<String, String> PROPERTY_INPUTPROMPTS = new HashMap<String, String>() {
84          {
85              put("colors", "e.g. blue,green,da444f");
86              put("fonts", "e.g. Helvetica;Times;Menlo");
87              put("fontSizes", "e.g. 10px;0.8em;1em;1.4em");
88              put("configJsFile", "e.g. /.resources/ckeditor/config-magnolia.js");
89          }
90      };
91  
92      private Binder<RichTextFieldDefinition> binder;
93      private MagnoliaCKEditorTextField richTextField;
94      private FormLayout form;
95  
96      @Override
97      protected void init(VaadinRequest request) {
98          form = new FormLayout();
99          form.setMargin(true);
100         createAndBindFields(form);
101 
102         form.addComponent(new Label("<b>Change settings above and click on 'Open CKEditor' in order to see them applied. Opening a link to Pages or DAM won't work here ¯\\\\_(ツ)_/¯</b>", ContentMode.HTML));
103 
104         final Button submit = new Button("Open CKEditor", event -> {
105             Optional.ofNullable(richTextField).ifPresent(form::removeComponent);
106 
107             richTextField = (MagnoliaCKEditorTextField) new RichTextFieldFactory(binder.getBean(), null, null, null, null).createField();
108             richTextField.setCaption("Rich text field");
109             richTextField.setWidth(682, PIXELS);
110             richTextField.setValue("Die sinnliche Gewißheit oder das Diese und das Meinen" +
111                     "<p>Das Wissen, welches zuerst oder unmittelbar unser Gegenstand ist, kann kein anderes sein als dasjenige, welches selbst unmittelbares Wissen, Wissen des Unmittelbaren oder Seienden ist. " +
112                     "Wir haben uns ebenso unmittelbar oder aufnehmend zu verhalten, also nichts an ihm, wie es sich darbietet, zu verändern und von dem Auffassen das Begreifen abzuhalten. " +
113                     "<p>(Giovanni Trapattoni)");
114             form.addComponent(richTextField);
115         });
116         form.addComponent(submit);
117         setContent(form);
118     }
119 
120 
121     private void createAndBindFields(FormLayout form) {
122         // fetch definition properties through reflection
123         List<PropertyDescriptor> properties = Arrays.stream(PropertyUtils.getPropertyDescriptors(RichTextFieldDefinition.class))
124                 .filter(p -> !(p.getPropertyType().isAssignableFrom(Class.class)))
125                 .filter(p -> p.getWriteMethod().getDeclaringClass() == RichTextFieldDefinition.class)
126                 .sorted(new RichTextFieldDefinitionPropertiesComparator())
127                 .collect(Collectors.toList());
128 
129         // create field group
130         binder = new Binder(RichTextFieldDefinition.class);
131         binder.setBean(new RichTextFieldDefinition());
132 
133         // create fields
134         properties.forEach(property -> {
135             AbstractField<?> field = createField(property.getPropertyType());
136 
137             Binder.BindingBuilder builder = binder.forField(field);
138             if (property.getName().equalsIgnoreCase("height")) {
139                 ((TextField) field).setPlaceholder("300");
140                 builder.withConverter(new StringToLongConverter((String) field.getValue()));
141                 field.setWidth(100, PIXELS);
142             }
143             if (PROPERTY_INPUTPROMPTS.containsKey(property.getName())) {
144                 ((TextField) field).setPlaceholder(PROPERTY_INPUTPROMPTS.get(property.getName()));
145             }
146             builder.bind(property.getName());
147             if (field instanceof CheckBox) {
148                 // for reasons I don't want to inquire (cause life is too short to waste it on such things)
149                 // a checkbox in a FormLayout would display the caption on its right, hence the trick below
150                 Component wrappedCheckbox = new CssLayout(field);
151                 wrappedCheckbox.setCaption(property.getName());
152                 form.addComponent(wrappedCheckbox);
153             } else {
154                 field.setCaption(property.getName());
155                 form.addComponent(field);
156             }
157         });
158     }
159 
160     /**
161      * Sort boolean properties first, custom JS config last.
162      */
163     private static class RichTextFieldDefinitionPropertiesComparator implements Comparator<PropertyDescriptor> {
164         @Override
165         public int compare(PropertyDescriptor o1, PropertyDescriptor o2) {
166 
167             if (o1.getName().equalsIgnoreCase("configJsFile")) {
168                 return 1;
169             } else if (o2.getName().equalsIgnoreCase("configJsFile")) {
170                 return -1;
171             }
172 
173             Class<?> pt1 = ClassUtils.primitiveToWrapper(o1.getPropertyType());
174             Class<?> pt2 = ClassUtils.primitiveToWrapper(o2.getPropertyType());
175             if (pt1.equals(pt2)) {
176                 return o1.getName().compareToIgnoreCase(o2.getName());
177             }
178 
179             if (ClassUtils.isAssignable(o1.getPropertyType(), Boolean.class)) {
180                 return -1;
181             } else if (ClassUtils.isAssignable(pt2, Boolean.class)) {
182                 return 1;
183             }
184 
185             if (ClassUtils.isAssignable(o1.getPropertyType(), Number.class)) {
186                 return -1;
187             } else if (ClassUtils.isAssignable(pt2, Number.class)) {
188                 return 1;
189             }
190             return 0;
191         }
192     }
193 
194     private <T extends AbstractField> T createField(Class<?> fieldType) {
195         if (fieldType.isAssignableFrom(Boolean.class) || fieldType.getName().equals("boolean")) {
196             return (T) new CheckBox();
197         }
198         T field = (T) new TextField();
199         field.setWidth(682, PIXELS);
200         return field;
201     }
202 
203     @WebServlet(value = "/ckeditor/*", asyncSupported = true)
204     @VaadinServletConfiguration(productionMode = false, ui = CKEditorUI.class)
205     public static class Servlet extends VaadinServlet {
206     }
207 }