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 = false;
143 this.isRichTextEditor = target instanceof RichTextConnector;
144 this.stretchControl.setClassName(STRETCHER_BASE);
145
146 ServerConnector potentialFormParent = getParent();
147 while (potentialFormParent != null && !(potentialFormParent instanceof FormConnector)) {
148 potentialFormParent = potentialFormParent.getParent();
149 }
150
151 if (potentialFormParent == null) {
152 return;
153 }
154
155 final ComponentConnector formConnector = (ComponentConnector) potentialFormParent;
156 textWidget.addAttachHandler(attachEvent -> {
157 if (attachEvent.isAttached()) {
158 initFormView(formConnector.getWidget());
159 initDialog();
160 checkOverlay();
161 if (!isRichTextEditor) {
162 appendStretcher(textWidget.getElement());
163 stretchControl.addClassName(SIMPLE_STYLE_NAME);
164 } else {
165 Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand() {
166 private int repeats = 0;
167
168 @Override
169 public boolean execute() {
170 repeats++;
171 isRichTextEditor = true;
172 final Element toolbox = JQueryWrapper.select(textWidget).find(CKEDITOR_TOOLBOX).get(0);
173 if (toolbox != null) {
174 appendStretcher(toolbox);
175 stretchControl.addClassName(RICH_TEXT_STYLE_NAME);
176 }
177 return toolbox == null && repeats < 5;
178 }
179 }, DELAY_MS);
180 }
181 } else {
182 clearTraces();
183 }
184 });
185 }
186
187 @Override
188 public TextAreaStretcherState getState() {
189 return (TextAreaStretcherState) super.getState();
190 }
191
192 private void appendStretcher(Element rootElement) {
193 rootElement.getStyle().setPosition(Style.Position.RELATIVE);
194 rootElement.getParentElement().insertAfter(stretchControl, rootElement);
195 Widget parent = textWidget.getParent();
196 TouchDelegateget/touch/TouchDelegate.html#TouchDelegate">TouchDelegate touchDelegate = new TouchDelegate(parent);
197 touchDelegate.addTouchEndHandler(new TouchEndHandler() {
198 @Override
199 public void onTouchEnd(TouchEndEvent event) {
200 Element target = event.getNativeEvent().getEventTarget().cast();
201 if (stretchControl.isOrHasChild(target)) {
202 if (!getState().isCollapsed) {
203 unregisterSizeChangeListeners();
204 }
205 getRpcProxy(TextAreaStretcherServerRpc.class).toggle(textWidget.getOffsetWidth(), textWidget.getOffsetHeight());
206
207 }
208 }
209 });
210 }
211
212 private void registerSizeChangeListeners() {
213 final LayoutManager lm = getParent().getLayoutManager();
214 final UIConnector ui = getConnection().getUIConnector();
215 getParent().addStateChangeHandler(textAreaSizeHandler);
216 lm.addElementResizeListener(ui.getWidget().getElement(), windowResizeListener);
217
218 final ComponentConnector formConnector = Util.findConnectorFor(this.form);
219 if (formConnector != null) {
220 formConnector.getLayoutManager().addElementResizeListener(this.form.getElement(), formResizeListener);
221 formConnector.addStateChangeHandler(formStateChangeHandler);
222 }
223 }
224
225 private void updateSize() {
226 if (form == null || !isFormVisible()) {
227 return;
228 }
229
230 if (!getState().isCollapsed) {
231 stretchControl.replaceClassName("icon-open-fullscreen-2", "icon-close-fullscreen-2");
232 stretchControl.replaceClassName(COLLAPSED, STRETCHED);
233 form.asWidget().addStyleName("textarea-stretched");
234
235 Style style = textWidget.getElement().getStyle();
236 style.setPosition(Style.Position.ABSOLUTE);
237 style.setZIndex(5);
238
239 int top = calculateTextWidgetTop();
240 int left = calculateTextWidgetLeft();
241
242 style.setLeft(left, Style.Unit.PX);
243 style.setTop(top, Style.Unit.PX);
244
245 adjustTextAreaAndFormSizeToScreen();
246
247 if (!isRichTextEditor) {
248 setStretchControlPosition(top, left);
249 }
250
251 hideOtherStretchers();
252 } else {
253 stretchControl.replaceClassName(STRETCHED, COLLAPSED);
254 stretchControl.replaceClassName("icon-close-fullscreen-2", "icon-open-fullscreen-2");
255 form.asWidget().removeStyleName(TEXTAREA_STRETCHED);
256
257 form.setHeight(formHeight);
258 clearTraces();
259 }
260 }
261
262 @Override
263 public void onUnregister() {
264 super.onUnregister();
265 clearTraces();
266 }
267
268 private void setStretchControlPosition(int top, int left) {
269 stretchControl.getStyle().setTop(top + 5, Style.Unit.PX);
270 stretchControl.getStyle().setLeft(left + textWidget.getOffsetWidth() - stretchControl.getOffsetWidth() - 5, Style.Unit.PX);
271 }
272
273 private int calculateTextWidgetTop() {
274 int top = getDialogHeaderOuterHeight() + getDialogDesErrorOuterHeight();
275 return isOverlay ? top + getDialogMargin()[TOP] + getDialogPadding()[TOP] : top + getDialogHeaderPadding()[TOP] + getDialogHeaderPadding()[BOTTOM];
276 }
277
278 private int calculateTextWidgetLeft() {
279 return isOverlay ? getDialogPadding()[LEFT] : form.getAbsoluteLeft();
280 }
281
282 private void hideOtherStretchers() {
283 JQueryWrapper.select("." + STRETCHER_BASE).setCss("display", "none");
284 this.stretchControl.getStyle().setDisplay(Style.Display.BLOCK);
285 }
286
287 private void clearTraces() {
288 Style style = textWidget.getElement().getStyle();
289 style.clearLeft();
290 style.clearTop();
291 style.clearPosition();
292 style.clearZIndex();
293
294 stretchControl.getStyle().clearTop();
295 stretchControl.getStyle().clearLeft();
296 stretchControl.getStyle().clearDisplay();
297
298 JQueryWrapper.select("." + STRETCHER_BASE).setCss("display", "");
299 }
300
301 private void unregisterSizeChangeListeners() {
302 final LayoutManager lm = getParent().getLayoutManager();
303 final UIConnector ui = getConnection().getUIConnector();
304 if (ui != null) {
305 getParent().removeStateChangeHandler(textAreaSizeHandler);
306 lm.removeElementResizeListener(ui.getWidget().getElement(), windowResizeListener);
307 }
308
309 final ComponentConnector formConnector = Util.findConnectorFor(this.form);
310 if (formConnector != null) {
311 formConnector.getLayoutManager().removeElementResizeListener(this.form.getElement(), formResizeListener);
312 formConnector.removeStateChangeHandler(formStateChangeHandler);
313 }
314 }
315
316 private void checkOverlay() {
317 Widget it = this.dialog.asWidget();
318 while (it != null && !isOverlay) {
319 it = it.getParent();
320 this.isOverlay = it instanceof OverlayWidget;
321 }
322 }
323
324 private void initDialog() {
325 this.dialog = form.getParent();
326 }
327
328 private void initFormView(Widget formWidget) {
329 this.form = formWidget;
330
331 this.formHeight = this.form.getElement().getStyle().getHeight();
332 }
333
334 private void adjustTextAreaAndFormSizeToScreen() {
335 int formHeight = calculateFormHeight();
336 form.setHeight(formHeight + "px");
337 textWidget.setWidth((form.getOffsetWidth() + getDialogWrapperBorder()[RIGHT] + getDialogWrapperBorder()[LEFT]) + "px");
338 textWidget.setHeight(formHeight + "px");
339 }
340
341 private int calculateFormHeight() {
342 int formHeight = dialog.getOffsetHeight();
343 formHeight -= getDialogHeaderOuterHeight() + getDialogFooterOuterHeight();
344 formHeight -= getDialogMargin()[TOP];
345 formHeight -= getDialogContentMargin()[TOP] - getDialogContentMargin()[BOTTOM];
346 formHeight -= getDialogDesErrorOuterHeight();
347
348 return formHeight;
349 }
350
351 private void doElementResize() {
352 if (isRichTextEditor) {
353 Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
354 @Override
355 public void execute() {
356 updateSize();
357 }
358 });
359 } else {
360 updateSize();
361 }
362 }
363
364 private int getElementsOuterHeight(String... elementClasses) {
365 int elementsOuterHeight = 0;
366 for (String clazzName : elementClasses) {
367 Element element = JQueryWrapper.select(dialog.asWidget()).find(clazzName).get(0);
368 if (element != null) {
369 int margin[] = new ComputedStyle(element).getMargin();
370 elementsOuterHeight += element.getOffsetHeight() + margin[TOP] + margin[BOTTOM];
371 }
372 }
373 return elementsOuterHeight;
374 }
375
376 private int[] getDialogHeaderPadding() {
377 if (dialogHeaderPadding == null) {
378 Element headerElement = JQueryWrapper.select(dialog.asWidget()).find(".dialog-header").get(0);
379 if (headerElement != null) {
380 dialogHeaderPadding = new ComputedStyle(headerElement).getPadding();
381 } else {
382 dialogHeaderPadding = new int[]{0, 0, 0, 0};
383 }
384 }
385
386 return dialogHeaderPadding;
387 }
388
389 private int[] getDialogContentMargin() {
390 if (dialogContentMargin == null) {
391 Element contentElement = JQueryWrapper.select(dialog.asWidget()).find(".dialog-content").get(0);
392 if (contentElement != null) {
393 dialogContentMargin = new ComputedStyle(contentElement).getMargin();
394 } else {
395 dialogContentMargin = new int[]{0, 0, 0, 0};
396 }
397 }
398
399 return dialogContentMargin;
400 }
401
402 private int[] getDialogPadding() {
403 if (dialogPadding == null) {
404 dialogPadding = getDialogComputedStyle().getPadding();
405 }
406
407 return dialogPadding;
408 }
409
410 private int[] getDialogMargin() {
411 if (dialogMargin == null) {
412 dialogMargin = getDialogComputedStyle().getMargin();
413 }
414
415 return dialogMargin;
416 }
417
418 private int[] getDialogWrapperBorder() {
419 if (dialogWrapperBorder == null) {
420 Element dialogWrapperElement = JQueryWrapper.select(dialog.asWidget()).find(".dialog-wrapper").get(0);
421 if (dialogWrapperElement != null) {
422 dialogWrapperBorder = new ComputedStyle(dialogWrapperElement).getBorder();
423 } else {
424 dialogWrapperBorder = new int[]{0, 0, 0, 0};
425 }
426 }
427
428 return dialogWrapperBorder;
429 }
430
431 private int getDialogFooterOuterHeight() {
432 if (dialogFooterOuterHeight == UNCALCULATED_SIZE) {
433 dialogFooterOuterHeight = getElementsOuterHeight(".dialog-footer");
434 }
435
436 return dialogFooterOuterHeight;
437 }
438
439 private int getDialogHeaderOuterHeight() {
440 if (dialogHeaderOuterHeight == UNCALCULATED_SIZE) {
441 dialogHeaderOuterHeight = getElementsOuterHeight(".dialog-header");
442 }
443
444 return dialogHeaderOuterHeight;
445 }
446
447 private int getDialogDesErrorOuterHeight() {
448 if (dialogDesErrorOuterHeight == UNCALCULATED_SIZE) {
449 dialogDesErrorOuterHeight = getElementsOuterHeight(".dialog-description", ".dialog-error");
450 }
451
452 return dialogDesErrorOuterHeight;
453 }
454
455 private ComputedStyle getDialogComputedStyle() {
456 if (dialogComputedStyle == null) {
457 dialogComputedStyle = new ComputedStyle(dialog.getElement());
458 }
459
460 return dialogComputedStyle;
461 }
462
463 private boolean isFormVisible() {
464 return form == null || JQueryWrapper.select(form.asWidget()).is(":visible");
465 }
466
467 private class WindowResizeListener implements ElementResizeListener {
468 @Override
469 public void onElementResize(ElementResizeEvent e) {
470 doElementResize();
471 }
472 }
473
474 }