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 @Override
121 public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
122 clientToServer = client;
123 paintableId = uidl.getId();
124 boolean needsDataUpdate = false;
125 boolean needsProtectedBodyUpdate = false;
126 boolean readOnlyModeChanged = false;
127
128
129
130
131
132 if ( clientToServer.updateComponent(this, uidl, true) ) {
133 return;
134 }
135
136 if ( ! resizeListenerInPlace ) {
137 LayoutManager.get(client).addElementResizeListener(getElement(), new ElementResizeListener() {
138
139 @Override
140 public void onElementResize(ElementResizeEvent e) {
141 doResize();
142 }
143
144 });
145 resizeListenerInPlace = true;
146 }
147
148 if ( uidl.hasAttribute(ATTR_IMMEDIATE) ) {
149 immediate = uidl.getBooleanAttribute(ATTR_IMMEDIATE);
150 }
151 if ( uidl.hasAttribute(ATTR_READONLY) ) {
152 boolean newReadOnly = uidl.getBooleanAttribute(ATTR_READONLY);
153 readOnlyModeChanged = newReadOnly != readOnly;
154 readOnly = newReadOnly;
155 }
156 if ( uidl.hasAttribute(ATTR_VIEW_WITHOUT_EDITOR) ) {
157 viewWithoutEditor = uidl.getBooleanAttribute(ATTR_VIEW_WITHOUT_EDITOR);
158 }
159 if ( uidl.hasAttribute(ATTR_PROTECTED_BODY) ) {
160 boolean state = uidl.getBooleanAttribute(ATTR_PROTECTED_BODY);
161 if (protectedBody != state) {
162 protectedBody = state ;
163 needsProtectedBodyUpdate = true;
164 }
165 }
166 if ( uidl.hasVariable(VAR_TEXT) ) {
167 String data = uidl.getStringVariable(VAR_TEXT);
168 if ( ckEditor != null )
169 dataBeforeEdit = ckEditor.getData();
170 needsDataUpdate = ! data.equals(dataBeforeEdit);
171 dataBeforeEdit = data;
172 }
173
174
175 if ( ! paintableId.equals(getElement().getId()) ) {
176 getElement().setId(paintableId);
177 }
178
179 if ( viewWithoutEditor ) {
180 if ( ckEditor != null ) {
181
182 if ( ! needsDataUpdate ) {
183 dataBeforeEdit = ckEditor.getData();
184 }
185 ckEditor.destroy(true);
186 ckEditorIsReady = false;
187 ckEditor = null;
188 }
189 getElement().setInnerHTML(dataBeforeEdit);
190 }
191 else if ( ckEditor == null ) {
192 getElement().setInnerHTML("");
193
194 final String inPageConfig = uidl.hasAttribute(ATTR_INPAGECONFIG) ? uidl.getStringAttribute(ATTR_INPAGECONFIG) : null;
195
196 writerIndentationChars = uidl.hasAttribute(ATTR_WRITER_INDENTATIONCHARS) ? uidl.getStringAttribute(ATTR_WRITER_INDENTATIONCHARS) : null;
197
198 if ( uidl.hasAttribute(ATTR_FOCUS) ) {
199 setFocus(uidl.getBooleanAttribute(ATTR_FOCUS));
200 }
201
202
203 int i = 0;
204 while( true ) {
205 if ( ! uidl.hasAttribute(ATTR_WRITERRULES_TAGNAME+i) ) {
206 break;
207 }
208
209 String tagName = uidl.getStringAttribute(ATTR_WRITERRULES_TAGNAME+i);
210 String jsRule = uidl.getStringAttribute(ATTR_WRITERRULES_JSRULE+i);
211 if ( writerRules == null ) {
212 writerRules = new HashMap<String,String>();
213 }
214 writerRules.put(tagName, jsRule);
215 ++i;
216 }
217
218
219 i = 0;
220 while( true ) {
221 if ( ! uidl.hasAttribute(ATTR_KEYSTROKES_KEYSTROKE+i) ) {
222 break;
223 }
224
225 int keystroke = uidl.getIntAttribute(ATTR_KEYSTROKES_KEYSTROKE+i);
226 String command = uidl.getStringAttribute(ATTR_KEYSTROKES_COMMAND+i);
227 if ( keystrokeMappings == null ) {
228 keystrokeMappings = new HashMap<Integer,String>();
229 }
230 keystrokeMappings.put(keystroke, command);
231 ++i;
232 }
233
234
235 i = 0;
236 while( true ) {
237 if ( ! uidl.hasAttribute(ATTR_PROTECTED_SOURCE+i) ) {
238 break;
239 }
240
241 String regex = uidl.getStringAttribute(ATTR_PROTECTED_SOURCE+i);
242 if ( protectedSourceList == null ) {
243 protectedSourceList = new LinkedList<String>();
244 }
245 protectedSourceList.add(regex);
246 ++i;
247 }
248
249 ScheduledCommand scE = new ScheduledCommand() {
250 @Override
251 public void execute() {
252 ckEditor = loadEditor(inPageConfig);
253 }
254 };
255
256 CKEditorService.loadLibrary(scE);
257
258
259 } else if ( ckEditorIsReady ) {
260 if ( needsDataUpdate ) {
261 ckEditor.setData(dataBeforeEdit);
262 }
263
264 if ( needsProtectedBodyUpdate ) {
265 ckEditor.protectBody(protectedBody);
266 }
267
268 if (uidl.hasAttribute(ATTR_INSERT_HTML)) {
269 ckEditor.insertHtml(uidl.getStringAttribute(ATTR_INSERT_HTML));
270 }
271
272 if (uidl.hasAttribute(ATTR_INSERT_TEXT)) {
273 ckEditor.insertText(uidl.getStringAttribute(ATTR_INSERT_TEXT));
274 }
275
276 if ( uidl.hasAttribute(ATTR_FOCUS) ) {
277 setFocus(uidl.getBooleanAttribute(ATTR_FOCUS));
278 }
279
280 if ( readOnlyModeChanged ) {
281 ckEditor.setReadOnly(readOnly);
282 }
283 }
284
285 }
286
287
288 @Override
289 public void onSave() {
290 if ( ckEditorIsReady && ! readOnly ) {
291
292 String data = ckEditor.getData();
293 if ( ! data.equals(dataBeforeEdit) ) {
294 clientToServer.updateVariable(paintableId, VAR_TEXT, data, false);
295 dataBeforeEdit = data;
296 }
297 clientToServer.updateVariable(paintableId, VAR_VAADIN_SAVE_BUTTON_PRESSED,"",false);
298 clientToServer.sendPendingVariableChanges();
299 }
300 }
301
302
303 @Override
304 public void onBlur() {
305 if ( ckEditorIsReady ) {
306 boolean sendToServer = false;
307
308 if ( clientToServer.hasEventListeners(this, EventId.BLUR) ) {
309 sendToServer = true;
310 clientToServer.updateVariable(paintableId, EventId.BLUR, "", false);
311 }
312
313
314
315 if ( ! readOnly ) {
316 String data = ckEditor.getData();
317 if ( ! data.equals(dataBeforeEdit) ) {
318 clientToServer.updateVariable(paintableId, VAR_TEXT, data, false);
319 sendToServer = true;
320 dataBeforeEdit = data;
321 }
322 }
323
324 if (sendToServer) {
325 clientToServer.sendPendingVariableChanges();
326 }
327 }
328 }
329
330
331 @Override
332 public void onFocus() {
333 if ( ckEditorIsReady ) {
334 if ( clientToServer.hasEventListeners(this, EventId.FOCUS) ) {
335 clientToServer.updateVariable(paintableId, EventId.FOCUS, "", true);
336 }
337 }
338 }
339
340
341 @Override
342 public void onInstanceReady() {
343 ckEditor.instanceReady(this);
344
345 if ( writerRules != null ) {
346 Set<String> tagNameSet = writerRules.keySet();
347 for( String tagName : tagNameSet ) {
348 ckEditor.setWriterRules(tagName, writerRules.get(tagName));
349 }
350 writerRules = null;
351 }
352
353 if ( writerIndentationChars != null ) {
354 ckEditor.setWriterIndentationChars(writerIndentationChars);
355 writerIndentationChars = null;
356 }
357
358 if ( keystrokeMappings != null ) {
359 Set<Integer> keystrokeSet = keystrokeMappings.keySet();
360 for( Integer keystroke : keystrokeSet ) {
361 ckEditor.setKeystroke(keystroke, keystrokeMappings.get(keystroke));
362 }
363 keystrokeMappings = null;
364 }
365
366 if ( protectedSourceList != null ) {
367 for( String regex : protectedSourceList ) {
368 ckEditor.pushProtectedSource(regex);
369 }
370 protectedSourceList = null;
371 }
372
373 if ( dataBeforeEdit != null ) {
374 ckEditor.setData(dataBeforeEdit);
375 }
376
377 ckEditorIsReady = true;
378
379 if (setFocusAfterReady) {
380 setFocus(true);
381 }
382
383 if ( setTabIndexAfterReady ) {
384 setTabIndex(tabIndex);
385 }
386
387 doResize();
388
389 if (protectedBody) {
390 ckEditor.protectBody(protectedBody);
391 }
392
393 ckEditor.setReadOnly(readOnly);
394
395 ckeditorVersion = CKEditorService.version();
396 clientToServer.updateVariable(paintableId, VAR_VERSION, ckeditorVersion, true);
397 }
398
399
400 @Override
401 public void onChange() {
402 if ( ckEditor != null && ! readOnly ) {
403 String data = ckEditor.getData();
404 if ( ! data.equals(dataBeforeEdit) ) {
405 clientToServer.updateVariable(paintableId, VAR_TEXT, data, immediate);
406 dataBeforeEdit = data;
407 }
408 }
409 }
410
411
412 @Override
413 public void onModeChange(String mode) {
414 if ( ckEditor != null ) {
415 if ( ! readOnly ) {
416 String data = ckEditor.getData();
417 if ( ! data.equals(dataBeforeEdit) ) {
418 clientToServer.updateVariable(paintableId, VAR_TEXT, data, true);
419 dataBeforeEdit = data;
420 }
421 }
422
423 if ("wysiwyg".equals(mode)) {
424 ckEditor.protectBody(protectedBody);
425 }
426 }
427 }
428
429
430 @Override
431 public void onSelectionChange() {
432 if ( ckEditorIsReady ) {
433 if ( clientToServer.hasEventListeners(this, EVENT_SELECTION_CHANGE) ) {
434 String html = ckEditor.getSelectedHtml();
435 if ( html == null )
436 html = "";
437
438 boolean isBlankSelection = "".equals(html);
439 if ( ! isBlankSelection || notifyBlankSelection ) {
440 clientToServer.updateVariable(paintableId, EVENT_SELECTION_CHANGE, html, true);
441 notifyBlankSelection = ! isBlankSelection;
442 }
443 }
444 }
445 }
446
447
448 @Override
449 public void onDataReady() {
450 if ( ckEditor != null ) {
451 ckEditor.protectBody(protectedBody);
452 }
453 }
454
455 @Override
456 public void setWidth(String width) {
457 super.setWidth(width);
458 doResize();
459 }
460
461 @Override
462 public void setHeight(String height) {
463 super.setHeight(height);
464 doResize();
465 }
466
467 protected void doResize() {
468 if (ckEditorIsReady) {
469 Scheduler.get().scheduleDeferred(new ScheduledCommand() {
470 @Override
471 public void execute() {
472 ckEditor.resize(VCKEditorTextField.super.getOffsetWidth(), VCKEditorTextField.super.getOffsetHeight());
473 }
474 });
475 }
476 }
477
478 @Override
479 protected void onUnload() {
480 if ( ckEditor != null ) {
481 ckEditor.destroy();
482 ckEditor = null;
483 }
484 ckEditorIsReady = false;
485 }
486
487 @Override
488 public int getTabIndex() {
489 if (ckEditorIsReady) {
490 return ckEditor.getTabIndex();
491 } else {
492 return tabIndex;
493 }
494 }
495
496 @Override
497 public void setTabIndex(int tabIndex) {
498 if (ckEditorIsReady) {
499 ckEditor.setTabIndex(tabIndex);
500 } else {
501 setTabIndexAfterReady = true;
502 }
503 this.tabIndex = tabIndex;
504 }
505
506 @Override
507 public void setAccessKey(char arg0) {
508 return;
509 }
510
511 @Override
512 public void setFocus(boolean arg0) {
513 if (arg0) {
514 if (ckEditorIsReady)
515 ckEditor.focus();
516 else
517 setFocusAfterReady = true;
518 } else {
519 setFocusAfterReady = false;
520 }
521 }
522
523
524
525
526
527
528
529 protected CKEditor loadEditor(String inPageConfig) {
530 return (CKEditor) CKEditorService.loadEditor(paintableId,
531 VCKEditorTextField.this,
532 inPageConfig,
533 VCKEditorTextField.super.getOffsetWidth(),
534 VCKEditorTextField.super.getOffsetHeight());
535 }
536 }