18
18
import com .vaadin .flow .component .AbstractCompositeField ;
19
19
import com .vaadin .flow .component .AttachEvent ;
20
20
import com .vaadin .flow .component .ClientCallable ;
21
+ import com .vaadin .flow .component .ComponentEventListener ;
21
22
import com .vaadin .flow .component .DetachEvent ;
23
+ import com .vaadin .flow .component .Focusable ;
22
24
import com .vaadin .flow .component .HasSize ;
23
25
import com .vaadin .flow .component .Tag ;
24
26
import com .vaadin .flow .component .UI ;
25
27
import com .vaadin .flow .component .dependency .CssImport ;
26
28
import com .vaadin .flow .component .dependency .JavaScript ;
27
29
import com .vaadin .flow .component .dependency .StyleSheet ;
28
30
import com .vaadin .flow .component .html .Div ;
31
+ import com .vaadin .flow .dom .DomEventListener ;
32
+ import com .vaadin .flow .dom .DomListenerRegistration ;
29
33
import com .vaadin .flow .dom .Element ;
30
34
import com .vaadin .flow .dom .ShadowRoot ;
31
35
import com .vaadin .flow .function .SerializableConsumer ;
36
+ import com .vaadin .flow .shared .Registration ;
37
+
32
38
import elemental .json .Json ;
33
39
import elemental .json .JsonArray ;
34
40
import elemental .json .JsonObject ;
50
56
@ JavaScript ("context://frontend/tinymce_addon/tinymce/tinymce.js" )
51
57
@ CssImport ("./tinymceLumo.css" )
52
58
public class TinyMce extends AbstractCompositeField <Div , TinyMce , String >
53
- implements HasSize {
59
+ implements HasSize , Focusable < TinyMce > {
54
60
61
+ private final DomListenerRegistration domListenerRegistration ;
55
62
private String id ;
56
63
private boolean initialContentSent ;
57
64
private String currentValue = "" ;
58
65
private String rawConfig ;
59
66
JsonObject config = Json .createObject ();
60
67
private Element ta = new Element ("div" );
61
68
private boolean basicTinyMCECreated ;
69
+ private boolean enabled = true ;
70
+ private boolean readOnly = false ;
71
+ private int debounceTimeout = 0 ;
62
72
63
73
/**
64
74
* Creates a new TinyMce editor with shadowroot set or disabled. The shadow
@@ -67,6 +77,8 @@ public class TinyMce extends AbstractCompositeField<Div, TinyMce, String>
67
77
* hand, the shadow root must not be on when for example used in inline
68
78
* mode.
69
79
*
80
+ * @deprecated No longer needed since version x.x
81
+ *
70
82
* @param shadowRoot
71
83
* true of shadow root hack should be used
72
84
*/
@@ -81,6 +93,40 @@ public TinyMce(boolean shadowRoot) {
81
93
getElement ().appendChild (ta );
82
94
}
83
95
getElement ().getClassList ().add ("tinymce-flow" );
96
+ domListenerRegistration = getElement ().addEventListener ("tchange" ,
97
+ (DomEventListener ) event -> {
98
+ boolean value = event .getEventData ()
99
+ .hasKey ("event.htmlString" );
100
+ String htmlString = event .getEventData ()
101
+ .getString ("event.htmlString" );
102
+ currentValue = htmlString ;
103
+ setModelValue (htmlString , true );
104
+ });
105
+ domListenerRegistration .addEventData ("event.htmlString" );
106
+ domListenerRegistration .debounce (debounceTimeout );
107
+ }
108
+
109
+ /**
110
+ * Sets the debounce timeout for the value change event. The default is 0,
111
+ * when value change is triggered on blur and enter key presses. When value
112
+ * is more than 0 the value change is emitted with delay of given timeout
113
+ * milliseconds after last keystroke.
114
+ *
115
+ * @param debounceTimeout
116
+ * the debounce timeout in milliseconds
117
+ */
118
+ public void setDebounceTimeout (int debounceTimeout ) {
119
+ if (debounceTimeout > 0 ) {
120
+ runBeforeClientResponse (ui -> {
121
+ getElement ().callJsFunction ("$connector.setEager" , true );
122
+ });
123
+ } else {
124
+ runBeforeClientResponse (ui -> {
125
+ getElement ().callJsFunction ("$connector.setEager" , false );
126
+ });
127
+ }
128
+ this .debounceTimeout = debounceTimeout ;
129
+ domListenerRegistration .debounce (debounceTimeout );
84
130
}
85
131
86
132
public TinyMce () {
@@ -101,8 +147,16 @@ public void setEditorContent(String html) {
101
147
102
148
@ Override
103
149
protected void onAttach (AttachEvent attachEvent ) {
104
- id = UUID .randomUUID ().toString ();
105
- ta .setAttribute ("id" , id );
150
+ if (id == null ) {
151
+ id = UUID .randomUUID ().toString ();
152
+ ta .setAttribute ("id" , id );
153
+ }
154
+ if (!getEventBus ().hasListener (BlurEvent .class )) {
155
+ // adding fake blur listener so throttled value
156
+ // change events happen by latest at blur
157
+ addBlurListener (e -> {
158
+ });
159
+ }
106
160
if (!attachEvent .isInitialAttach ()) {
107
161
// Value after initial attach should be set via TinyMCE JavaScript
108
162
// API, otherwise value is not updated upon reattach
@@ -115,6 +169,8 @@ protected void onAttach(AttachEvent attachEvent) {
115
169
116
170
@ Override
117
171
protected void onDetach (DetachEvent detachEvent ) {
172
+ detachEvent .getUI ().getPage ().executeJs ("tinymce.get($0).remove();" ,
173
+ id );
118
174
super .onDetach (detachEvent );
119
175
initialContentSent = false ;
120
176
// save the current value to the dom element in case the component gets
@@ -126,8 +182,9 @@ private void initConnector() {
126
182
127
183
runBeforeClientResponse (ui -> {
128
184
ui .getPage ().executeJs (
129
- "window.Vaadin.Flow.tinymceConnector.initLazy($0, $1, $2, $3)" ,
130
- rawConfig , getElement (), ta , config ).then (res -> {
185
+ "window.Vaadin.Flow.tinymceConnector.initLazy($0, $1, $2, $3, $4, $5)" ,
186
+ rawConfig , getElement (), ta , config , currentValue ,
187
+ (enabled && !readOnly )).then (res -> {
131
188
// Delay setting flag on first attach, otherwise setting
132
189
// initial value on attach does not work
133
190
initialContentSent = true ;
@@ -140,12 +197,6 @@ void runBeforeClientResponse(SerializableConsumer<UI> command) {
140
197
.beforeClientResponse (this , context -> command .accept (ui )));
141
198
}
142
199
143
- @ ClientCallable
144
- private void updateValue (String htmlString ) {
145
- this .currentValue = htmlString ;
146
- setModelValue (htmlString , true );
147
- }
148
-
149
200
public String getCurrentValue () {
150
201
return currentValue ;
151
202
}
@@ -189,33 +240,76 @@ public void replaceSelectionContent(String htmlString) {
189
240
"$connector.replaceSelectionContent" , htmlString ));
190
241
}
191
242
243
+ @ Override
192
244
public void focus () {
193
- runBeforeClientResponse (
194
- ui -> getElement ().callJsFunction ("$connector.focus" ));
245
+ runBeforeClientResponse (ui -> {
246
+ // Dialog has timing issues...
247
+ getElement ().executeJs (
248
+ //@formatter:off
249
+ "const el = this;" +
250
+ "if($connector.isInDialog()) {" +
251
+ " setTimeout(() => {" +
252
+ " $connector.focus()" +
253
+ " }, 150);" +
254
+ "} else {" +
255
+ " $connetor.focus();" +
256
+ "}"
257
+ //@formatter:on
258
+ );
259
+ });
260
+ }
261
+
262
+ @ Override
263
+ public Registration addFocusListener (
264
+ ComponentEventListener <FocusEvent <TinyMce >> listener ) {
265
+ DomListenerRegistration domListenerRegistration = getElement ()
266
+ .addEventListener ("tfocus" , event -> listener
267
+ .onComponentEvent (new FocusEvent <>(this , false )));
268
+ return domListenerRegistration ;
269
+ }
270
+
271
+ @ Override
272
+ public Registration addBlurListener (
273
+ ComponentEventListener <BlurEvent <TinyMce >> listener ) {
274
+ DomListenerRegistration domListenerRegistration = getElement ()
275
+ .addEventListener ("tblur" , event -> listener
276
+ .onComponentEvent (new BlurEvent <>(this , false )));
277
+ return domListenerRegistration ;
278
+ }
279
+
280
+ @ Override
281
+ public void blur () {
282
+ throw new RuntimeException (
283
+ "Not implemented, TinyMce does not support programmatic blur." );
195
284
}
196
285
197
286
@ Override
198
287
public void setEnabled (boolean enabled ) {
199
- super .setEnabled (enabled );
200
- runBeforeClientResponse (ui -> {
201
- getElement ().callJsFunction ("$connector.setEnabled" , enabled );
202
- });
288
+ this .enabled = enabled ;
289
+ adjustEnabledState ();
290
+ }
291
+
292
+ private void adjustEnabledState () {
293
+ boolean reallyEnabled = this .enabled && !this .readOnly ;
294
+ super .setEnabled (reallyEnabled );
295
+ runBeforeClientResponse (ui -> getElement ()
296
+ .callJsFunction ("$connector.setEnabled" , reallyEnabled ));
203
297
}
204
298
205
299
@ Override
206
300
public void setReadOnly (boolean readOnly ) {
301
+ this .readOnly = readOnly ;
207
302
super .setReadOnly (readOnly );
208
- setEnabled (!readOnly );
209
- if (readOnly ) {
210
- getElement ().setAttribute ("readonly" , "" );
211
- } else {
212
- getElement ().removeAttribute ("readonly" );
213
- }
303
+ adjustEnabledState ();
214
304
}
215
305
216
306
@ Override
217
- protected void setPresentationValue (String t ) {
218
- setEditorContent (t );
307
+ protected void setPresentationValue (String html ) {
308
+ this .currentValue = html ;
309
+ if (initialContentSent ) {
310
+ runBeforeClientResponse (ui -> getElement ()
311
+ .callJsFunction ("$connector.setEditorContent" , html ));
312
+ }
219
313
}
220
314
221
315
private TinyMce createBasicTinyMce () {
0 commit comments