View Javadoc
1   /**
2    * This file Copyright (c) 2013-2015 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.vaadin.gwt.client.richtext;
35  
36  import info.magnolia.ui.vaadin.gwt.client.dialog.widget.OverlayWidget;
37  import info.magnolia.ui.vaadin.gwt.client.form.widget.FormView;
38  import info.magnolia.ui.vaadin.gwt.client.jquerywrapper.JQueryWrapper;
39  import info.magnolia.ui.vaadin.richtext.TextAreaStretcher;
40  
41  import com.google.gwt.core.client.Scheduler;
42  import com.google.gwt.dom.client.Element;
43  import com.google.gwt.dom.client.Style;
44  import com.google.gwt.event.logical.shared.AttachEvent;
45  import com.google.gwt.user.client.DOM;
46  import com.google.gwt.user.client.ui.Widget;
47  import com.googlecode.mgwt.dom.client.event.touch.TouchEndEvent;
48  import com.googlecode.mgwt.dom.client.event.touch.TouchEndHandler;
49  import com.googlecode.mgwt.ui.client.widget.touch.TouchDelegate;
50  import com.vaadin.client.ComponentConnector;
51  import com.vaadin.client.ComputedStyle;
52  import com.vaadin.client.LayoutManager;
53  import com.vaadin.client.ServerConnector;
54  import com.vaadin.client.Util;
55  import com.vaadin.client.communication.StateChangeEvent;
56  import com.vaadin.client.extensions.AbstractExtensionConnector;
57  import com.vaadin.client.ui.layout.ElementResizeEvent;
58  import com.vaadin.client.ui.layout.ElementResizeListener;
59  import com.vaadin.client.ui.ui.UIConnector;
60  import com.vaadin.shared.ui.Connect;
61  
62  /**
63   * Client-side connector for {@link info.magnolia.ui.vaadin.richtext.TextAreaStretcher}.
64   */
65  @Connect(TextAreaStretcher.class)
66  public class TextAreaStretcherConnector extends AbstractExtensionConnector {
67  
68      public static final String STRETCHER_BASE = "textarea-stretcher";
69      public static final String STRETCHED = "stretched";
70      public static final String COLLAPSED = "collapsed";
71      public static final String CKEDITOR_TOOLBOX = ".cke_top";
72      public static final String TEXTAREA_STRETCHED = "textarea-stretched";
73  
74      public static final String RICH_TEXT_STYLE_NAME = "rich-text";
75      public static final String SIMPLE_STYLE_NAME = "simple";
76  
77      public static final int DELAY_MS = 500;
78  
79      private Widget form;
80      private Widget dialog;
81      private Widget textWidget;
82  
83      private Element stretchControl = DOM.createDiv();
84      private WindowResizeListener windowResizeListener = new WindowResizeListener();
85  
86      private boolean isOverlay = false;
87      private boolean isRichTextEditor = false;
88  
89      private StateChangeEvent.StateChangeHandler textAreaSizeHandler = new StateChangeEvent.StateChangeHandler() {
90          @Override
91          public void onStateChanged(StateChangeEvent stateChangeEvent) {
92              stretchTextArea(textWidget.getElement().getStyle());
93          }
94      };
95  
96      private ElementResizeListener formResizeListener = new ElementResizeListener() {
97          @Override
98          public void onElementResize(ElementResizeEvent e) {
99              if (isRichTextEditor) {
100                 Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
101                     @Override
102                     public void execute() {
103                         updateSize();
104                     }
105                 });
106             } else {
107                 updateSize();
108             }
109         }
110     };;
111 
112     @Override
113     public void onStateChanged(StateChangeEvent stateChangeEvent) {
114         super.onStateChanged(stateChangeEvent);
115         if (stateChangeEvent.hasPropertyChanged("isCollapsed")) {
116             updateSize();
117             if (!getState().isCollapsed) {
118                 registerSizeChangeListeners();
119             }
120         }
121     }
122 
123     @Override
124     public ComponentConnector getParent() {
125         return (ComponentConnector) super.getParent();
126     }
127 
128     @Override
129     protected void extend(ServerConnector target) {
130         this.textWidget = ((ComponentConnector) target).getWidget();
131         this.isRichTextEditor = target instanceof RichTextConnector;
132         this.stretchControl.setClassName(STRETCHER_BASE);
133         textWidget.addAttachHandler(new AttachEvent.Handler() {
134             @Override
135             public void onAttachOrDetach(AttachEvent attachEvent) {
136                 if (attachEvent.isAttached()) {
137                     initFormView();
138                     initDialog();
139                     checkOverlay();
140                     if (!isRichTextEditor) {
141                         appendStretcher(textWidget.getElement());
142                         stretchControl.addClassName(SIMPLE_STYLE_NAME);
143                     } else {
144                         Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand() {
145                             private int repeats = 0;
146 
147                             @Override
148                             public boolean execute() {
149                                 repeats++;
150                                 isRichTextEditor = true;
151                                 final Element toolbox = JQueryWrapper.select(textWidget).find(CKEDITOR_TOOLBOX).get(0);
152                                 if (toolbox != null) {
153                                     appendStretcher(toolbox);
154                                     stretchControl.addClassName(RICH_TEXT_STYLE_NAME);
155                                 }
156                                 return toolbox == null && repeats < 5;
157                             }
158                         }, DELAY_MS);
159                     }
160                 } else {
161                     clearTraces();
162                 }
163             }
164         });
165     }
166 
167     @Override
168     public TextAreaStretcherState getState() {
169         return (TextAreaStretcherState) super.getState();
170     }
171 
172     private void appendStretcher(Element rootElement) {
173         rootElement.getStyle().setPosition(Style.Position.RELATIVE);
174         rootElement.getParentElement().insertAfter(stretchControl, rootElement);
175         Widget parent = textWidget.getParent();
176         TouchDelegate touchDelegate = new TouchDelegate(parent);
177         touchDelegate.addTouchEndHandler(new TouchEndHandler() {
178             @Override
179             public void onTouchEnd(TouchEndEvent event) {
180                 Element target = event.getNativeEvent().getEventTarget().cast();
181                 if (stretchControl.isOrHasChild(target)) {
182                     if (!getState().isCollapsed) {
183                         unregisterSizeChangeListeners();
184                     }
185                     getRpcProxy(TextAreaStretcherServerRpc.class).toggle(textWidget.getOffsetWidth(), textWidget.getOffsetHeight());
186 
187                 }
188             }
189         });
190     }
191 
192     private void registerSizeChangeListeners() {
193         final LayoutManager lm = getParent().getLayoutManager();
194         final UIConnector ui = getConnection().getUIConnector();
195         getParent().addStateChangeHandler(textAreaSizeHandler);
196         lm.addElementResizeListener(ui.getWidget().getElement(), windowResizeListener);
197 
198         final ComponentConnector formConnector = Util.findConnectorFor(this.form);
199         if (formConnector != null) {
200             formConnector.getLayoutManager().addElementResizeListener(this.form.getElement(), formResizeListener);
201         }
202     }
203 
204     private void updateSize() {
205         if (!getState().isCollapsed) {
206             stretchControl.replaceClassName("icon-open-fullscreen-2", "icon-close-fullscreen-2");
207             stretchControl.replaceClassName(COLLAPSED, STRETCHED);
208             form.asWidget().addStyleName("textarea-stretched");
209 
210             Style style = textWidget.getElement().getStyle();
211             style.setPosition(Style.Position.ABSOLUTE);
212             Element header = getDialogHeaderElement();
213             ComputedStyle headerCS = new ComputedStyle(header);
214 
215             int top = form.getAbsoluteTop() - dialog.getAbsoluteTop();
216             top = isOverlay ? top : top + headerCS.getPadding()[0] + headerCS.getPadding()[2];
217 
218             int left = isOverlay ? 0 : form.getAbsoluteLeft();
219 
220             style.setLeft(left, Style.Unit.PX);
221             style.setTop(top, Style.Unit.PX);
222 
223             stretchTextArea(style);
224             style.setZIndex(5);
225 
226             if (!isOverlay && !isRichTextEditor) {
227                 stretchControl.getStyle().setTop(top + 5, Style.Unit.PX);
228                 stretchControl.getStyle().setLeft(left + textWidget.getOffsetWidth() - stretchControl.getOffsetWidth() - 5, Style.Unit.PX);
229 
230             }
231 
232             hideOtherStretchers();
233         } else {
234             stretchControl.replaceClassName(STRETCHED, COLLAPSED);
235             stretchControl.replaceClassName("icon-close-fullscreen-2", "icon-open-fullscreen-2");
236             form.asWidget().removeStyleName(TEXTAREA_STRETCHED);
237             clearTraces();
238         }
239     }
240 
241     @Override
242     public void onUnregister() {
243         super.onUnregister();
244         clearTraces();
245     }
246 
247     private void hideOtherStretchers() {
248         JQueryWrapper.select("." + STRETCHER_BASE).setCss("display", "none");
249         this.stretchControl.getStyle().setDisplay(Style.Display.BLOCK);
250     }
251 
252     private void clearTraces() {
253         Style style = textWidget.getElement().getStyle();
254         style.clearLeft();
255         style.clearTop();
256         style.clearPosition();
257         style.clearZIndex();
258 
259         stretchControl.getStyle().clearTop();
260         stretchControl.getStyle().clearLeft();
261 
262         JQueryWrapper.select("." + STRETCHER_BASE).setCss("display", "");
263     }
264 
265     private void stretchTextArea(Style style) {
266         style.setWidth(form.getOffsetWidth(), Style.Unit.PX);
267         adjustTextAreaHeightToScreen(getConnection().getUIConnector().getWidget().getOffsetHeight());
268     }
269 
270     private void unregisterSizeChangeListeners() {
271         final LayoutManager lm = getParent().getLayoutManager();
272         final UIConnector ui = getConnection().getUIConnector();
273         if (ui != null) {
274             getParent().removeStateChangeHandler(textAreaSizeHandler);
275             lm.removeElementResizeListener(ui.getWidget().getElement(), windowResizeListener);
276         }
277 
278         final ComponentConnector formConnector = Util.findConnectorFor(this.form);
279         if (formConnector != null) {
280             formConnector.getLayoutManager().removeElementResizeListener(this.form.getElement(), formResizeListener);
281         }
282     }
283 
284     private Element getDialogHeaderElement() {
285         return JQueryWrapper.select(dialog.asWidget()).find(".dialog-header").get(0);
286     }
287 
288     private void checkOverlay() {
289         Widget it = this.dialog.asWidget();
290         while (it != null && !isOverlay) {
291             it = it.getParent();
292             this.isOverlay = it instanceof OverlayWidget;
293         }
294     }
295 
296     private void initDialog() {
297         this.dialog = form.getParent();
298     }
299 
300     private void initFormView() {
301         Widget it = textWidget;
302         while (it != null && !(it instanceof FormView)) {
303             it = it.getParent();
304         }
305         this.form = (it instanceof FormView) ? it : null;
306     }
307 
308     private void adjustTextAreaHeightToScreen(int uiHeight) {
309         int formTop = form.getAbsoluteTop();
310         textWidget.setHeight((uiHeight - formTop) + "px");
311     }
312 
313     private class WindowResizeListener implements ElementResizeListener {
314         @Override
315         public void onElementResize(ElementResizeEvent e) {
316             adjustTextAreaHeightToScreen(e.getLayoutManager().getOuterHeight(e.getElement()));
317         }
318     }
319 
320 }