1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.vaadin.alump.ckeditor.client;
21
22 import java.util.HashMap;
23 import java.util.LinkedList;
24 import java.util.Set;
25
26 import com.google.gwt.core.client.Scheduler;
27 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
28 import com.google.gwt.dom.client.DivElement;
29 import com.google.gwt.dom.client.Document;
30 import com.google.gwt.dom.client.Style.Overflow;
31 import com.google.gwt.dom.client.Style.Visibility;
32 import com.google.gwt.user.client.ui.Focusable;
33 import com.google.gwt.user.client.ui.Widget;
34 import com.vaadin.client.ApplicationConnection;
35 import com.vaadin.client.LayoutManager;
36 import com.vaadin.client.Paintable;
37 import com.vaadin.client.UIDL;
38 import com.vaadin.client.ui.layout.ElementResizeEvent;
39 import com.vaadin.client.ui.layout.ElementResizeListener;
40 import com.vaadin.shared.EventId;
41
42
43
44
45
46 public class VCKEditorTextField extends Widget implements Paintable, CKEditorService.CKEditorListener, Focusable {
47
48
49 public static final String CLASSNAME = "v-ckeditortextfield";
50
51 public static final String ATTR_FOCUS = "focus";
52 public static final String ATTR_IMMEDIATE = "immediate";
53 public static final String ATTR_READONLY = "readonly";
54 public static final String ATTR_VIEW_WITHOUT_EDITOR = "viewWithoutEditor";
55 public static final String ATTR_INPAGECONFIG = "inPageConfig";
56 public static final String ATTR_PROTECTED_SOURCE = "protectedSource";
57 public static final String ATTR_WRITERRULES_TAGNAME = "writerRules.tagName";
58 public static final String ATTR_WRITERRULES_JSRULE = "writerRules.jsRule";
59 public static final String ATTR_WRITER_INDENTATIONCHARS = "writerIndentationChars";
60 public static final String ATTR_KEYSTROKES_KEYSTROKE = "keystrokes.keystroke";
61 public static final String ATTR_KEYSTROKES_COMMAND = "keystrokes.command";
62 public static final String ATTR_INSERT_HTML = "insert_html";
63 public static final String ATTR_INSERT_TEXT = "insert_text";
64 public static final String ATTR_PROTECTED_BODY = "protected_body";
65 public static final String VAR_TEXT = "text";
66 public static final String VAR_VAADIN_SAVE_BUTTON_PRESSED = "vaadinsave";
67 public static final String VAR_VERSION = "version";
68
69 public static final String EVENT_SELECTION_CHANGE = "selectionChange";
70
71 private static String ckeditorVersion;
72
73
74 protected String paintableId;
75
76
77 protected ApplicationConnection clientToServer;
78
79 private String dataBeforeEdit = null;
80
81 private boolean immediate;
82 private boolean readOnly;
83 private boolean viewWithoutEditor;
84 private boolean protectedBody;
85
86 private CKEditor ckEditor = null;
87 private boolean ckEditorIsReady = false;
88 private boolean resizeListenerInPlace = false;
89 private boolean notifyBlankSelection = false;
90
91 private LinkedList<String> protectedSourceList = null;
92 private HashMap<String,String> writerRules = null;
93 private String writerIndentationChars = null;
94 private HashMap<Integer,String> keystrokeMappings = null;
95
96 private int tabIndex;
97 private boolean setFocusAfterReady;
98 private boolean setTabIndexAfterReady;
99
100
101
102
103
104 public VCKEditorTextField() {
105
106
107 DivElement rootDiv = Document.get().createDivElement();
108 rootDiv.getStyle().setOverflow(Overflow.HIDDEN);
109 rootDiv.getStyle().setVisibility(Visibility.VISIBLE);
110 setElement(rootDiv);
111
112
113
114 setStyleName(CLASSNAME);
115 }
116
117
118
119
120
121
122
123 protected CKEditor loadEditor(String inPageConfig) {
124 return (CKEditor) CKEditorService.loadEditor(paintableId,
125 VCKEditorTextField.this,
126 inPageConfig,
127 VCKEditorTextField.super.getOffsetWidth(),
128 VCKEditorTextField.super.getOffsetHeight());
129 }
130
131
132
133
134 @Override
135 public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
136 clientToServer = client;
137 paintableId = uidl.getId();
138 boolean needsDataUpdate = false;
139 boolean needsProtectedBodyUpdate = false;
140 boolean readOnlyModeChanged = false;
141
142
143
144
145
146 if ( clientToServer.updateComponent(this, uidl, true) ) {
147 return;
148 }
149
150 if ( ! resizeListenerInPlace ) {
151 LayoutManager.get(client).addElementResizeListener(getElement(), new ElementResizeListener() {
152
153 @Override
154 public void onElementResize(ElementResizeEvent e) {
155 doResize();
156 }
157
158 });
159 resizeListenerInPlace = true;
160 }
161
162 if ( uidl.hasAttribute(ATTR_IMMEDIATE) ) {
163 immediate = uidl.getBooleanAttribute(ATTR_IMMEDIATE);
164 }
165 if ( uidl.hasAttribute(ATTR_READONLY) ) {
166 boolean newReadOnly = uidl.getBooleanAttribute(ATTR_READONLY);
167 readOnlyModeChanged = newReadOnly != readOnly;
168 readOnly = newReadOnly;
169 }
170 if ( uidl.hasAttribute(ATTR_VIEW_WITHOUT_EDITOR) ) {
171 viewWithoutEditor = uidl.getBooleanAttribute(ATTR_VIEW_WITHOUT_EDITOR);
172 }
173 if ( uidl.hasAttribute(ATTR_PROTECTED_BODY) ) {
174 boolean state = uidl.getBooleanAttribute(ATTR_PROTECTED_BODY);
175 if (protectedBody != state) {
176 protectedBody = state ;
177 needsProtectedBodyUpdate = true;
178 }
179 }
180 if ( uidl.hasVariable(VAR_TEXT) ) {
181 String data = uidl.getStringVariable(VAR_TEXT);
182 if ( ckEditor != null )
183 dataBeforeEdit = ckEditor.getData();
184 needsDataUpdate = ! data.equals(dataBeforeEdit);
185 dataBeforeEdit = data;
186 }
187
188
189 if ( ! paintableId.equals(getElement().getId()) ) {
190 getElement().setId(paintableId);
191 }
192
193 if ( viewWithoutEditor ) {
194 if ( ckEditor != null ) {
195
196 if ( ! needsDataUpdate ) {
197 dataBeforeEdit = ckEditor.getData();
198 }
199 ckEditor.destroy(true);
200 ckEditorIsReady = false;
201 ckEditor = null;
202 }
203 getElement().setInnerHTML(dataBeforeEdit);
204 }
205 else if ( ckEditor == null ) {
206 getElement().setInnerHTML("");
207
208 final String inPageConfig = uidl.hasAttribute(ATTR_INPAGECONFIG) ? uidl.getStringAttribute(ATTR_INPAGECONFIG) : null;
209
210 writerIndentationChars = uidl.hasAttribute(ATTR_WRITER_INDENTATIONCHARS) ? uidl.getStringAttribute(ATTR_WRITER_INDENTATIONCHARS) : null;
211
212 if ( uidl.hasAttribute(ATTR_FOCUS) ) {
213 setFocus(uidl.getBooleanAttribute(ATTR_FOCUS));
214 }
215
216
217 int i = 0;
218 while( true ) {
219 if ( ! uidl.hasAttribute(ATTR_WRITERRULES_TAGNAME+i) ) {
220 break;
221 }
222
223 String tagName = uidl.getStringAttribute(ATTR_WRITERRULES_TAGNAME+i);
224 String jsRule = uidl.getStringAttribute(ATTR_WRITERRULES_JSRULE+i);
225 if ( writerRules == null ) {
226 writerRules = new HashMap<String,String>();
227 }
228 writerRules.put(tagName, jsRule);
229 ++i;
230 }
231
232
233 i = 0;
234 while( true ) {
235 if ( ! uidl.hasAttribute(ATTR_KEYSTROKES_KEYSTROKE+i) ) {
236 break;
237 }
238
239 int keystroke = uidl.getIntAttribute(ATTR_KEYSTROKES_KEYSTROKE+i);
240 String command = uidl.getStringAttribute(ATTR_KEYSTROKES_COMMAND+i);
241 if ( keystrokeMappings == null ) {
242 keystrokeMappings = new HashMap<Integer,String>();
243 }
244 keystrokeMappings.put(keystroke, command);
245 ++i;
246 }
247
248
249 i = 0;
250 while( true ) {
251 if ( ! uidl.hasAttribute(ATTR_PROTECTED_SOURCE+i) ) {
252 break;
253 }
254
255 String regex = uidl.getStringAttribute(ATTR_PROTECTED_SOURCE+i);
256 if ( protectedSourceList == null ) {
257 protectedSourceList = new LinkedList<String>();
258 }
259 protectedSourceList.add(regex);
260 ++i;
261 }
262
263 ScheduledCommand scE = new ScheduledCommand() {
264 @Override
265 public void execute() {
266 ckEditor = loadEditor(inPageConfig);
267 }
268 };
269
270 CKEditorService.loadLibrary(scE);
271
272
273 } else if ( ckEditorIsReady ) {
274 if ( needsDataUpdate ) {
275 ckEditor.setData(dataBeforeEdit);
276 }
277
278 if ( needsProtectedBodyUpdate ) {
279 ckEditor.protectBody(protectedBody);
280 }
281
282 if (uidl.hasAttribute(ATTR_INSERT_HTML)) {
283 ckEditor.insertHtml(uidl.getStringAttribute(ATTR_INSERT_HTML));
284 }
285
286 if (uidl.hasAttribute(ATTR_INSERT_TEXT)) {
287 ckEditor.insertText(uidl.getStringAttribute(ATTR_INSERT_TEXT));
288 }
289
290 if ( uidl.hasAttribute(ATTR_FOCUS) ) {
291 setFocus(uidl.getBooleanAttribute(ATTR_FOCUS));
292 }
293
294 if ( readOnlyModeChanged ) {
295 ckEditor.setReadOnly(readOnly);
296 }
297 }
298
299 }
300
301
302 @Override
303 public void onSave() {
304 if ( ckEditorIsReady && ! readOnly ) {
305
306 String data = ckEditor.getData();
307 if ( ! data.equals(dataBeforeEdit) ) {
308 clientToServer.updateVariable(paintableId, VAR_TEXT, data, false);
309 dataBeforeEdit = data;
310 }
311 clientToServer.updateVariable(paintableId, VAR_VAADIN_SAVE_BUTTON_PRESSED,"",false);
312 clientToServer.sendPendingVariableChanges();
313 }
314 }
315
316
317 @Override
318 public void onBlur() {
319 if ( ckEditorIsReady ) {
320 boolean sendToServer = false;
321
322 if ( clientToServer.hasEventListeners(this, EventId.BLUR) ) {
323 sendToServer = true;
324 clientToServer.updateVariable(paintableId, EventId.BLUR, "", false);
325 }
326
327
328
329 if ( ! readOnly ) {
330 String data = ckEditor.getData();
331 if ( ! data.equals(dataBeforeEdit) ) {
332 clientToServer.updateVariable(paintableId, VAR_TEXT, data, false);
333 sendToServer = true;
334 dataBeforeEdit = data;
335 }
336 }
337
338 if (sendToServer) {
339 clientToServer.sendPendingVariableChanges();
340 }
341 }
342 }
343
344
345 @Override
346 public void onFocus() {
347 if ( ckEditorIsReady ) {
348 if ( clientToServer.hasEventListeners(this, EventId.FOCUS) ) {
349 clientToServer.updateVariable(paintableId, EventId.FOCUS, "", true);
350 }
351 }
352 }
353
354
355 @Override
356 public void onInstanceReady() {
357 ckEditor.instanceReady(this);
358
359 if ( writerRules != null ) {
360 Set<String> tagNameSet = writerRules.keySet();
361 for( String tagName : tagNameSet ) {
362 ckEditor.setWriterRules(tagName, writerRules.get(tagName));
363 }
364 writerRules = null;
365 }
366
367 if ( writerIndentationChars != null ) {
368 ckEditor.setWriterIndentationChars(writerIndentationChars);
369 writerIndentationChars = null;
370 }
371
372 if ( keystrokeMappings != null ) {
373 Set<Integer> keystrokeSet = keystrokeMappings.keySet();
374 for( Integer keystroke : keystrokeSet ) {
375 ckEditor.setKeystroke(keystroke, keystrokeMappings.get(keystroke));
376 }
377 keystrokeMappings = null;
378 }
379
380 if ( protectedSourceList != null ) {
381 for( String regex : protectedSourceList ) {
382 ckEditor.pushProtectedSource(regex);
383 }
384 protectedSourceList = null;
385 }
386
387 if ( dataBeforeEdit != null ) {
388 ckEditor.setData(dataBeforeEdit);
389 }
390
391 ckEditorIsReady = true;
392
393 if (setFocusAfterReady) {
394 setFocus(true);
395 }
396
397 if ( setTabIndexAfterReady ) {
398 setTabIndex(tabIndex);
399 }
400
401 doResize();
402
403 if (protectedBody) {
404 ckEditor.protectBody(protectedBody);
405 }
406
407 ckEditor.setReadOnly(readOnly);
408
409 ckeditorVersion = CKEditorService.version();
410 clientToServer.updateVariable(paintableId, VAR_VERSION, ckeditorVersion, true);
411 }
412
413
414 @Override
415 public void onChange() {
416 if ( ckEditor != null && ! readOnly ) {
417 String data = ckEditor.getData();
418 if ( ! data.equals(dataBeforeEdit) ) {
419 clientToServer.updateVariable(paintableId, VAR_TEXT, data, immediate);
420 dataBeforeEdit = data;
421 }
422 }
423 }
424
425
426 @Override
427 public void onModeChange(String mode) {
428 if ( ckEditor != null ) {
429 if ( ! readOnly ) {
430 String data = ckEditor.getData();
431 if ( ! data.equals(dataBeforeEdit) ) {
432 clientToServer.updateVariable(paintableId, VAR_TEXT, data, true);
433 dataBeforeEdit = data;
434 }
435 }
436
437 if ("wysiwyg".equals(mode)) {
438 ckEditor.protectBody(protectedBody);
439 }
440 }
441 }
442
443
444 @Override
445 public void onSelectionChange() {
446 if ( ckEditorIsReady ) {
447 if ( clientToServer.hasEventListeners(this, EVENT_SELECTION_CHANGE) ) {
448 String html = ckEditor.getSelectedHtml();
449 if ( html == null )
450 html = "";
451
452 boolean isBlankSelection = "".equals(html);
453 if ( ! isBlankSelection || notifyBlankSelection ) {
454 clientToServer.updateVariable(paintableId, EVENT_SELECTION_CHANGE, html, true);
455 notifyBlankSelection = ! isBlankSelection;
456 }
457 }
458 }
459 }
460
461
462 @Override
463 public void onDataReady() {
464 if ( ckEditor != null ) {
465 ckEditor.protectBody(protectedBody);
466 }
467 }
468
469 @Override
470 public void setWidth(String width) {
471 super.setWidth(width);
472 doResize();
473 }
474
475 @Override
476 public void setHeight(String height) {
477 super.setHeight(height);
478 doResize();
479 }
480
481 protected void doResize() {
482 if (ckEditorIsReady) {
483 Scheduler.get().scheduleDeferred(new ScheduledCommand() {
484 @Override
485 public void execute() {
486 ckEditor.resize(VCKEditorTextField.super.getOffsetWidth(), VCKEditorTextField.super.getOffsetHeight());
487 }
488 });
489 }
490 }
491
492 @Override
493 protected void onUnload() {
494 if ( ckEditor != null ) {
495 ckEditor.destroy();
496 ckEditor = null;
497 }
498 ckEditorIsReady = false;
499 }
500
501 @Override
502 public int getTabIndex() {
503 if (ckEditorIsReady) {
504 return ckEditor.getTabIndex();
505 } else {
506 return tabIndex;
507 }
508 }
509
510 @Override
511 public void setTabIndex(int tabIndex) {
512 if (ckEditorIsReady) {
513 ckEditor.setTabIndex(tabIndex);
514 } else {
515 setTabIndexAfterReady = true;
516 }
517 this.tabIndex = tabIndex;
518 }
519
520 @Override
521 public void setAccessKey(char arg0) {
522 return;
523 }
524
525 @Override
526 public void setFocus(boolean arg0) {
527 if (arg0) {
528 if (ckEditorIsReady)
529 ckEditor.focus();
530 else
531 setFocusAfterReady = true;
532 } else {
533 setFocusAfterReady = false;
534 }
535 }
536
537 }