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.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
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 public static final String RICH_TEXT_STYLE_NAME = "rich-text";
74 public static final String SIMPLE_STYLE_NAME = "simple";
75 public static final int DELAY_MS = 500;
76
77 private static final int UNCALCULATED_SIZE = -1;
78 private static final int TOP = 0;
79 private static final int RIGHT = 1;
80 private static final int BOTTOM = 2;
81 private static final int LEFT = 3;
82
83 private Widget form;
84 private Widget dialog;
85 private Widget textWidget;
86 private Element stretchControl = DOM.createDiv();
87 private ComputedStyle dialogComputedStyle;
88
89
90 private int[] dialogWrapperBorder;
91 private int[] dialogHeaderPadding;
92 private int[] dialogContentMargin;
93 private int[] dialogMargin;
94 private int[] dialogPadding;
95 private String formHeight;
96 private int dialogHeaderOuterHeight = UNCALCULATED_SIZE;
97 private int dialogFooterOuterHeight = UNCALCULATED_SIZE;
98 private int dialogDesErrorOuterHeight = UNCALCULATED_SIZE;
99 private boolean isOverlay = false;
100 private boolean isRichTextEditor = false;
101
102 private StateChangeEvent.StateChangeHandler textAreaSizeHandler = new StateChangeEvent.StateChangeHandler() {
103 @Override
104 public void onStateChanged(StateChangeEvent stateChangeEvent) {
105 if (isFormVisible()) {
106 adjustTextAreaAndFormSizeToScreen();
107 }
108 }
109 };
110
111 private WindowResizeListener windowResizeListener = new WindowResizeListener();
112
113 private ElementResizeListener formResizeListener = new ElementResizeListener() {
114 @Override
115 public void onElementResize(ElementResizeEvent e) {
116 doElementResize();
117 }
118 };
119
120 private StateChangeEvent.StateChangeHandler formStateChangeHandler = new StateChangeEvent.StateChangeHandler() {
121 @Override
122 public void onStateChanged(StateChangeEvent stateChangeEvent) {
123 if (stateChangeEvent.hasPropertyChanged("descriptionsVisible") || stateChangeEvent.hasPropertyChanged("errorAmount")) {
124 if (!getState().isCollapsed) {
125 unregisterSizeChangeListeners();
126 getRpcProxy(TextAreaStretcherServerRpc.class).toggle(textWidget.getOffsetWidth(), textWidget.getOffsetHeight());
127 }
128 }
129 }
130 };
131
132 @Override
133 public void onStateChanged(StateChangeEvent stateChangeEvent) {
134 super.onStateChanged(stateChangeEvent);
135 if (stateChangeEvent.hasPropertyChanged("isCollapsed")) {
136 updateSize();
137 if (!getState().isCollapsed) {
138 registerSizeChangeListeners();
139 }
140 }
141 }
142
143 @Override
144 public ComponentConnector getParent() {
145 return (ComponentConnector) super.getParent();
146 }
147
148 @Override
149 protected void extend(ServerConnector target) {
150 this.textWidget = ((ComponentConnector) target).getWidget();
151 this.isRichTextEditor = target instanceof RichTextConnector;
152 this.stretchControl.setClassName(STRETCHER_BASE);
153 textWidget.addAttachHandler(new AttachEvent.Handler() {
154 @Override
155 public void onAttachOrDetach(AttachEvent attachEvent) {
156 if (attachEvent.isAttached()) {
157 initFormView();
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
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 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 (!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() {
329 Widget it = textWidget;
330 while (it != null && !(it instanceof FormView)) {
331 it = it.getParent();
332 }
333 this.form = (it instanceof FormView) ? it : null;
334
335 this.formHeight = this.form.getElement().getStyle().getHeight();
336 }
337
338 private void adjustTextAreaAndFormSizeToScreen() {
339 int formHeight = calculateFormHeight();
340 form.setHeight(formHeight + "px");
341 textWidget.setWidth((form.getOffsetWidth() + getDialogWrapperBorder()[RIGHT] + getDialogWrapperBorder()[LEFT]) + "px");
342 textWidget.setHeight(formHeight + "px");
343 }
344
345 private int calculateFormHeight() {
346 int formHeight = dialog.getOffsetHeight();
347 formHeight -= getDialogHeaderOuterHeight() + getDialogFooterOuterHeight();
348 formHeight -= getDialogMargin()[TOP];
349 formHeight -= getDialogContentMargin()[TOP] - getDialogContentMargin()[BOTTOM];
350 formHeight -= getDialogDesErrorOuterHeight();
351
352 return formHeight;
353 }
354
355 private void doElementResize() {
356 if (isRichTextEditor) {
357 Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
358 @Override
359 public void execute() {
360 updateSize();
361 }
362 });
363 } else {
364 updateSize();
365 }
366 }
367
368 private int getElementsOuterHeight(String... elementClasses) {
369 int elementsOuterHeight = 0;
370 for (String clazzName : elementClasses) {
371 Element element = JQueryWrapper.select(dialog.asWidget()).find(clazzName).get(0);
372 if (element != null) {
373 int margin[] = new ComputedStyle(element).getMargin();
374 elementsOuterHeight += element.getOffsetHeight() + margin[TOP] + margin[BOTTOM];
375 }
376 }
377 return elementsOuterHeight;
378 }
379
380 private int[] getDialogHeaderPadding() {
381 if (dialogHeaderPadding == null) {
382 Element headerElement = JQueryWrapper.select(dialog.asWidget()).find(".dialog-header").get(0);
383 if (headerElement != null) {
384 dialogHeaderPadding = new ComputedStyle(headerElement).getPadding();
385 } else {
386 dialogHeaderPadding = new int[]{0, 0, 0, 0};
387 }
388 }
389
390 return dialogHeaderPadding;
391 }
392
393 private int[] getDialogContentMargin() {
394 if (dialogContentMargin == null) {
395 Element contentElement = JQueryWrapper.select(dialog.asWidget()).find(".dialog-content").get(0);
396 if (contentElement != null) {
397 dialogContentMargin = new ComputedStyle(contentElement).getMargin();
398 } else {
399 dialogContentMargin = new int[]{0, 0, 0, 0};
400 }
401 }
402
403 return dialogContentMargin;
404 }
405
406 private int[] getDialogPadding() {
407 if (dialogPadding == null) {
408 dialogPadding = getDialogComputedStyle().getPadding();
409 }
410
411 return dialogPadding;
412 }
413
414 private int[] getDialogMargin() {
415 if (dialogMargin == null) {
416 dialogMargin = getDialogComputedStyle().getMargin();
417 }
418
419 return dialogMargin;
420 }
421
422 private int[] getDialogWrapperBorder() {
423 if (dialogWrapperBorder == null) {
424 Element dialogWrapperElement = JQueryWrapper.select(dialog.asWidget()).find(".dialog-wrapper").get(0);
425 if (dialogWrapperElement != null) {
426 dialogWrapperBorder = new ComputedStyle(dialogWrapperElement).getBorder();
427 } else {
428 dialogWrapperBorder = new int[]{0, 0, 0, 0};
429 }
430 }
431
432 return dialogWrapperBorder;
433 }
434
435 private int getDialogFooterOuterHeight() {
436 if (dialogFooterOuterHeight == UNCALCULATED_SIZE) {
437 dialogFooterOuterHeight = getElementsOuterHeight(".dialog-footer");
438 }
439
440 return dialogFooterOuterHeight;
441 }
442
443 private int getDialogHeaderOuterHeight() {
444 if (dialogHeaderOuterHeight == UNCALCULATED_SIZE) {
445 dialogHeaderOuterHeight = getElementsOuterHeight(".dialog-header");
446 }
447
448 return dialogHeaderOuterHeight;
449 }
450
451 private int getDialogDesErrorOuterHeight() {
452 if (dialogDesErrorOuterHeight == UNCALCULATED_SIZE) {
453 dialogDesErrorOuterHeight = getElementsOuterHeight(".dialog-description", ".dialog-error");
454 }
455
456 return dialogDesErrorOuterHeight;
457 }
458
459 private ComputedStyle getDialogComputedStyle() {
460 if (dialogComputedStyle == null) {
461 dialogComputedStyle = new ComputedStyle(dialog.getElement());
462 }
463
464 return dialogComputedStyle;
465 }
466
467 private boolean isFormVisible() {
468 return JQueryWrapper.select(form.asWidget()).is(":visible");
469 }
470
471 private class WindowResizeListener implements ElementResizeListener {
472 @Override
473 public void onElementResize(ElementResizeEvent e) {
474 doElementResize();
475 }
476 }
477
478 }