1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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.connector.FormConnector;
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.user.client.DOM;
45 import com.google.gwt.user.client.ui.Widget;
46 import com.googlecode.mgwt.dom.client.event.touch.TouchEndEvent;
47 import com.googlecode.mgwt.dom.client.event.touch.TouchEndHandler;
48 import com.googlecode.mgwt.ui.client.widget.touch.TouchDelegate;
49 import com.vaadin.client.ComponentConnector;
50 import com.vaadin.client.ComputedStyle;
51 import com.vaadin.client.LayoutManager;
52 import com.vaadin.client.ServerConnector;
53 import com.vaadin.client.Util;
54 import com.vaadin.client.communication.StateChangeEvent;
55 import com.vaadin.client.extensions.AbstractExtensionConnector;
56 import com.vaadin.client.ui.layout.ElementResizeEvent;
57 import com.vaadin.client.ui.layout.ElementResizeListener;
58 import com.vaadin.client.ui.ui.UIConnector;
59 import com.vaadin.shared.ui.Connect;
60
61
62
63
64 @Connect(TextAreaStretcher.class)
65 public class TextAreaStretcherConnector extends AbstractExtensionConnector {
66
67 public static final String STRETCHER_BASE = "textarea-stretcher";
68 public static final String STRETCHED = "stretched";
69 public static final String COLLAPSED = "collapsed";
70 public static final String CKEDITOR_TOOLBOX = ".cke_top";
71 public static final String TEXTAREA_STRETCHED = "textarea-stretched";
72 public static final String RICH_TEXT_STYLE_NAME = "rich-text";
73 public static final String SIMPLE_STYLE_NAME = "simple";
74 public static final int DELAY_MS = 500;
75
76 private static final int UNCALCULATED_SIZE = -1;
77 private static final int TOP = 0;
78 private static final int RIGHT = 1;
79 private static final int BOTTOM = 2;
80 private static final int LEFT = 3;
81
82 private Widget form;
83 private Widget dialog;
84 private Widget textWidget;
85 private Element stretchControl = DOM.createDiv();
86 private ComputedStyle dialogComputedStyle;
87
88
89 private int[] dialogWrapperBorder;
90 private int[] dialogHeaderPadding;
91 private int[] dialogContentMargin;
92 private int[] dialogMargin;
93 private int[] dialogPadding;
94 private String formHeight;
95 private int dialogHeaderOuterHeight = UNCALCULATED_SIZE;
96 private int dialogFooterOuterHeight = UNCALCULATED_SIZE;
97 private int dialogDesErrorOuterHeight = UNCALCULATED_SIZE;
98 private boolean isOverlay = false;
99 private boolean isRichTextEditor = false;
100
101 private StateChangeEvent.StateChangeHandler textAreaSizeHandler = stateChangeEvent -> {
102 if (isFormVisible()) {
103 adjustTextAreaAndFormSizeToScreen();
104 }
105 };
106
107 private WindowResizeListener windowResizeListener = new WindowResizeListener();
108
109 private ElementResizeListener formResizeListener = e -> doElementResize();
110
111 private StateChangeEvent.StateChangeHandler formStateChangeHandler = new StateChangeEvent.StateChangeHandler() {
112 @Override
113 public void onStateChanged(StateChangeEvent stateChangeEvent) {
114 if (stateChangeEvent.hasPropertyChanged("descriptionsVisible") || stateChangeEvent.hasPropertyChanged("errorAmount")) {
115 if (!getState().isCollapsed) {
116 unregisterSizeChangeListeners();
117 getRpcProxy(TextAreaStretcherServerRpc.class).toggle(textWidget.getOffsetWidth(), textWidget.getOffsetHeight());
118 }
119 }
120 }
121 };
122
123 @Override
124 public void onStateChanged(StateChangeEvent stateChangeEvent) {
125 super.onStateChanged(stateChangeEvent);
126 if (stateChangeEvent.hasPropertyChanged("isCollapsed")) {
127 updateSize();
128 if (!getState().isCollapsed) {
129 registerSizeChangeListeners();
130 }
131 }
132 }
133
134 @Override
135 public ComponentConnector getParent() {
136 return (ComponentConnector) super.getParent();
137 }
138
139 @Override
140 protected void extend(ServerConnector target) {
141 this.textWidget = ((ComponentConnector) target).getWidget();
142 this.isRichTextEditor = target instanceof RichTextConnector;
143 this.stretchControl.setClassName(STRETCHER_BASE);
144
145 ServerConnector potentialFormParent = getParent();
146 while (potentialFormParent != null && !(potentialFormParent instanceof FormConnector)) {
147 potentialFormParent = potentialFormParent.getParent();
148 }
149
150 if (potentialFormParent == null) {
151 return;
152 }
153
154 final ComponentConnector formConnector = (ComponentConnector) potentialFormParent;
155 textWidget.addAttachHandler(attachEvent -> {
156 if (attachEvent.isAttached()) {
157 initFormView(formConnector.getWidget());
158 initDialog();
159 checkOverlay();
160 if (!isRichTextEditor) {
161 appendStretcher(textWidget.getElement());
162 stretchControl.addClassName(SIMPLE_STYLE_NAME);
163 } else {
164 Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand() {
165 private int repeats = 0;
166
167 @Override
168 public boolean execute() {
169 repeats++;
170 isRichTextEditor = true;
171 final Element toolbox = JQueryWrapper.select(textWidget).find(CKEDITOR_TOOLBOX).get(0);
172 if (toolbox != null) {
173 appendStretcher(toolbox);
174 stretchControl.addClassName(RICH_TEXT_STYLE_NAME);
175 }
176 return toolbox == null && repeats < 5;
177 }
178 }, DELAY_MS);
179 }
180 } else {
181 clearTraces();
182 }
183 });
184 }
185
186 @Override
187 public TextAreaStretcherState getState() {
188 return (TextAreaStretcherState) super.getState();
189 }
190
191 private void appendStretcher(Element rootElement) {
192 rootElement.getStyle().setPosition(Style.Position.RELATIVE);
193 rootElement.getParentElement().insertAfter(stretchControl, rootElement);
194 Widget parent = textWidget.getParent();
195 TouchDelegate touchDelegate = new TouchDelegate(parent);
196 touchDelegate.addTouchEndHandler(new TouchEndHandler() {
197 @Override
198 public void onTouchEnd(TouchEndEvent event) {
199 Element target = event.getNativeEvent().getEventTarget().cast();
200 if (stretchControl.isOrHasChild(target)) {
201 if (!getState().isCollapsed) {
202 unregisterSizeChangeListeners();
203 }
204 getRpcProxy(TextAreaStretcherServerRpc.class).toggle(textWidget.getOffsetWidth(), textWidget.getOffsetHeight());
205
206 }
207 }
208 });
209 }
210
211 private void registerSizeChangeListeners() {
212 final LayoutManager lm = getParent().getLayoutManager();
213 final UIConnector ui = getConnection().getUIConnector();
214 getParent().addStateChangeHandler(textAreaSizeHandler);
215 lm.addElementResizeListener(ui.getWidget().getElement(), windowResizeListener);
216
217 final ComponentConnector formConnector = Util.findConnectorFor(this.form);
218 if (formConnector != null) {
219 formConnector.getLayoutManager().addElementResizeListener(this.form.getElement(), formResizeListener);
220 formConnector.addStateChangeHandler(formStateChangeHandler);
221 }
222 }
223
224 private void updateSize() {
225 if (!isFormVisible()) {
226 return;
227 }
228
229 if (!getState().isCollapsed) {
230 stretchControl.replaceClassName("icon-open-fullscreen-2", "icon-close-fullscreen-2");
231 stretchControl.replaceClassName(COLLAPSED, STRETCHED);
232 form.asWidget().addStyleName("textarea-stretched");
233
234 Style style = textWidget.getElement().getStyle();
235 style.setPosition(Style.Position.ABSOLUTE);
236 style.setZIndex(5);
237
238 int top = calculateTextWidgetTop();
239 int left = calculateTextWidgetLeft();
240
241 style.setLeft(left, Style.Unit.PX);
242 style.setTop(top, Style.Unit.PX);
243
244 adjustTextAreaAndFormSizeToScreen();
245
246 if (!isRichTextEditor) {
247 setStretchControlPosition(top, left);
248 }
249
250 hideOtherStretchers();
251 } else {
252 stretchControl.replaceClassName(STRETCHED, COLLAPSED);
253 stretchControl.replaceClassName("icon-close-fullscreen-2", "icon-open-fullscreen-2");
254 form.asWidget().removeStyleName(TEXTAREA_STRETCHED);
255
256 form.setHeight(formHeight);
257 clearTraces();
258 }
259 }
260
261 @Override
262 public void onUnregister() {
263 super.onUnregister();
264 clearTraces();
265 }
266
267 private void setStretchControlPosition(int top, int left) {
268 stretchControl.getStyle().setTop(top + 5, Style.Unit.PX);
269 stretchControl.getStyle().setLeft(left + textWidget.getOffsetWidth() - stretchControl.getOffsetWidth() - 5, Style.Unit.PX);
270 }
271
272 private int calculateTextWidgetTop() {
273 int top = getDialogHeaderOuterHeight() + getDialogDesErrorOuterHeight();
274 return isOverlay ? top + getDialogMargin()[TOP] + getDialogPadding()[TOP] : top + getDialogHeaderPadding()[TOP] + getDialogHeaderPadding()[BOTTOM];
275 }
276
277 private int calculateTextWidgetLeft() {
278 return isOverlay ? getDialogPadding()[LEFT] : form.getAbsoluteLeft();
279 }
280
281 private void hideOtherStretchers() {
282 JQueryWrapper.select("." + STRETCHER_BASE).setCss("display", "none");
283 this.stretchControl.getStyle().setDisplay(Style.Display.BLOCK);
284 }
285
286 private void clearTraces() {
287 Style style = textWidget.getElement().getStyle();
288 style.clearLeft();
289 style.clearTop();
290 style.clearPosition();
291 style.clearZIndex();
292
293 stretchControl.getStyle().clearTop();
294 stretchControl.getStyle().clearLeft();
295 stretchControl.getStyle().clearDisplay();
296
297 JQueryWrapper.select("." + STRETCHER_BASE).setCss("display", "");
298 }
299
300 private void unregisterSizeChangeListeners() {
301 final LayoutManager lm = getParent().getLayoutManager();
302 final UIConnector ui = getConnection().getUIConnector();
303 if (ui != null) {
304 getParent().removeStateChangeHandler(textAreaSizeHandler);
305 lm.removeElementResizeListener(ui.getWidget().getElement(), windowResizeListener);
306 }
307
308 final ComponentConnector formConnector = Util.findConnectorFor(this.form);
309 if (formConnector != null) {
310 formConnector.getLayoutManager().removeElementResizeListener(this.form.getElement(), formResizeListener);
311 formConnector.removeStateChangeHandler(formStateChangeHandler);
312 }
313 }
314
315 private void checkOverlay() {
316 Widget it = this.dialog.asWidget();
317 while (it != null && !isOverlay) {
318 it = it.getParent();
319 this.isOverlay = it instanceof OverlayWidget;
320 }
321 }
322
323 private void initDialog() {
324 this.dialog = form.getParent();
325 }
326
327 private void initFormView(Widget formWidget) {
328 this.form = formWidget;
329
330 this.formHeight = this.form.getElement().getStyle().getHeight();
331 }
332
333 private void adjustTextAreaAndFormSizeToScreen() {
334 int formHeight = calculateFormHeight();
335 form.setHeight(formHeight + "px");
336 textWidget.setWidth((form.getOffsetWidth() + getDialogWrapperBorder()[RIGHT] + getDialogWrapperBorder()[LEFT]) + "px");
337 textWidget.setHeight(formHeight + "px");
338 }
339
340 private int calculateFormHeight() {
341 int formHeight = dialog.getOffsetHeight();
342 formHeight -= getDialogHeaderOuterHeight() + getDialogFooterOuterHeight();
343 formHeight -= getDialogMargin()[TOP];
344 formHeight -= getDialogContentMargin()[TOP] - getDialogContentMargin()[BOTTOM];
345 formHeight -= getDialogDesErrorOuterHeight();
346
347 return formHeight;
348 }
349
350 private void doElementResize() {
351 if (isRichTextEditor) {
352 Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
353 @Override
354 public void execute() {
355 updateSize();
356 }
357 });
358 } else {
359 updateSize();
360 }
361 }
362
363 private int getElementsOuterHeight(String... elementClasses) {
364 int elementsOuterHeight = 0;
365 for (String clazzName : elementClasses) {
366 Element element = JQueryWrapper.select(dialog.asWidget()).find(clazzName).get(0);
367 if (element != null) {
368 int margin[] = new ComputedStyle(element).getMargin();
369 elementsOuterHeight += element.getOffsetHeight() + margin[TOP] + margin[BOTTOM];
370 }
371 }
372 return elementsOuterHeight;
373 }
374
375 private int[] getDialogHeaderPadding() {
376 if (dialogHeaderPadding == null) {
377 Element headerElement = JQueryWrapper.select(dialog.asWidget()).find(".dialog-header").get(0);
378 if (headerElement != null) {
379 dialogHeaderPadding = new ComputedStyle(headerElement).getPadding();
380 } else {
381 dialogHeaderPadding = new int[]{0, 0, 0, 0};
382 }
383 }
384
385 return dialogHeaderPadding;
386 }
387
388 private int[] getDialogContentMargin() {
389 if (dialogContentMargin == null) {
390 Element contentElement = JQueryWrapper.select(dialog.asWidget()).find(".dialog-content").get(0);
391 if (contentElement != null) {
392 dialogContentMargin = new ComputedStyle(contentElement).getMargin();
393 } else {
394 dialogContentMargin = new int[]{0, 0, 0, 0};
395 }
396 }
397
398 return dialogContentMargin;
399 }
400
401 private int[] getDialogPadding() {
402 if (dialogPadding == null) {
403 dialogPadding = getDialogComputedStyle().getPadding();
404 }
405
406 return dialogPadding;
407 }
408
409 private int[] getDialogMargin() {
410 if (dialogMargin == null) {
411 dialogMargin = getDialogComputedStyle().getMargin();
412 }
413
414 return dialogMargin;
415 }
416
417 private int[] getDialogWrapperBorder() {
418 if (dialogWrapperBorder == null) {
419 Element dialogWrapperElement = JQueryWrapper.select(dialog.asWidget()).find(".dialog-wrapper").get(0);
420 if (dialogWrapperElement != null) {
421 dialogWrapperBorder = new ComputedStyle(dialogWrapperElement).getBorder();
422 } else {
423 dialogWrapperBorder = new int[]{0, 0, 0, 0};
424 }
425 }
426
427 return dialogWrapperBorder;
428 }
429
430 private int getDialogFooterOuterHeight() {
431 if (dialogFooterOuterHeight == UNCALCULATED_SIZE) {
432 dialogFooterOuterHeight = getElementsOuterHeight(".dialog-footer");
433 }
434
435 return dialogFooterOuterHeight;
436 }
437
438 private int getDialogHeaderOuterHeight() {
439 if (dialogHeaderOuterHeight == UNCALCULATED_SIZE) {
440 dialogHeaderOuterHeight = getElementsOuterHeight(".dialog-header");
441 }
442
443 return dialogHeaderOuterHeight;
444 }
445
446 private int getDialogDesErrorOuterHeight() {
447 if (dialogDesErrorOuterHeight == UNCALCULATED_SIZE) {
448 dialogDesErrorOuterHeight = getElementsOuterHeight(".dialog-description", ".dialog-error");
449 }
450
451 return dialogDesErrorOuterHeight;
452 }
453
454 private ComputedStyle getDialogComputedStyle() {
455 if (dialogComputedStyle == null) {
456 dialogComputedStyle = new ComputedStyle(dialog.getElement());
457 }
458
459 return dialogComputedStyle;
460 }
461
462 private boolean isFormVisible() {
463 return JQueryWrapper.select(form.asWidget()).is(":visible");
464 }
465
466 private class WindowResizeListener implements ElementResizeListener {
467 @Override
468 public void onElementResize(ElementResizeEvent e) {
469 doElementResize();
470 }
471 }
472
473 }