View Javadoc
1   // Copyright (C) 2010-2017 Yozons, Inc.
2   // CKEditor for Vaadin- Widget linkage for using CKEditor within a Vaadin application.
3   //
4   // This software is released under the Apache License 2.0 <http://www.apache.org/licenses/LICENSE-2.0.html>
5   //
6   // This software was originally based on the Vaadin incubator component TinyMCEEditor written by Matti Tahvonen.
7   //
8   package org.vaadin.openesignforms.ckeditor.widgetset.client.ui;
9   
10  import java.util.HashMap;
11  import java.util.LinkedList;
12  import java.util.Set;
13  import java.util.logging.Logger;
14  
15  import com.google.gwt.core.client.Scheduler;
16  import com.google.gwt.core.client.Scheduler.ScheduledCommand;
17  import com.google.gwt.dom.client.DivElement;
18  import com.google.gwt.dom.client.Document;
19  import com.google.gwt.dom.client.Style.Overflow;
20  import com.google.gwt.dom.client.Style.Visibility;
21  import com.google.gwt.user.client.ui.Focusable;
22  import com.google.gwt.user.client.ui.Widget;
23  import com.vaadin.client.ApplicationConnection;
24  import com.vaadin.client.LayoutManager;
25  import com.vaadin.client.Paintable;
26  import com.vaadin.client.UIDL;
27  import com.vaadin.client.ui.layout.ElementResizeEvent;
28  import com.vaadin.client.ui.layout.ElementResizeListener;
29  import com.vaadin.shared.EventId;
30  
31  /**
32   * Client side CKEditor widget which communicates with the server. Messages from the
33   * server are shown as HTML and mouse clicks are sent to the server.
34   */
35  @SuppressWarnings("deprecation")
36  public class VCKEditorTextField extends Widget implements Paintable, CKEditorService.CKEditorListener, Focusable {
37  	
38  	/** Set the CSS class name to allow styling. */
39  	public static final String CLASSNAME = "v-ckeditortextfield";
40  	
41  	public static final String ATTR_FOCUS = "focus";
42  	public static final String ATTR_IMMEDIATE = "immediate";
43  	public static final String ATTR_READONLY = "readonly";
44  	public static final String ATTR_VIEW_WITHOUT_EDITOR = "viewWithoutEditor";
45  	public static final String ATTR_INPAGECONFIG = "inPageConfig";
46  	public static final String ATTR_PROTECTED_SOURCE = "protectedSource";
47  	public static final String ATTR_WRITERRULES_TAGNAME = "writerRules.tagName";
48  	public static final String ATTR_WRITERRULES_JSRULE = "writerRules.jsRule";
49  	public static final String ATTR_WRITER_INDENTATIONCHARS = "writerIndentationChars";
50  	public static final String ATTR_KEYSTROKES_KEYSTROKE = "keystrokes.keystroke";
51  	public static final String ATTR_KEYSTROKES_COMMAND = "keystrokes.command";
52  	public static final String ATTR_INSERT_HTML = "insert_html";
53  	public static final String ATTR_INSERT_TEXT = "insert_text";
54  	public static final String ATTR_PROTECTED_BODY = "protected_body";
55  	public static final String VAR_TEXT = "text";
56  	public static final String VAR_VAADIN_SAVE_BUTTON_PRESSED = "vaadinsave";
57  	public static final String VAR_VERSION = "version";
58  	
59  	public static final String EVENT_SELECTION_CHANGE = "selectionChange";
60  	
61  	private static String ckeditorVersion;
62  
63  	/** The client side widget identifier */
64  	protected String paintableId;
65  	protected String ckeditorContainerId = null;
66  
67  	/** Reference to the server connection object. */
68  	protected ApplicationConnection clientToServer;
69  	
70  	private String inPageConfig = null;
71  	private String dataBeforeEdit = null;
72  	private boolean ignoreDataChangesUntilReady = false;
73  	
74  	private boolean immediate;
75  	private boolean readOnly;
76  	private boolean viewWithoutEditor; // Set to true and the editor will not be displayed, just the contents.
77  	private boolean protectedBody;
78  	
79  	private CKEditor ckEditor = null;
80  	private boolean ckEditorIsBeingLoaded = false;
81  	private boolean ckEditorIsReady = false;
82  	private boolean resizeListenerInPlace = false;
83  	private boolean notifyBlankSelection = false;
84  	
85  	private LinkedList<String> protectedSourceList = null;
86  	private HashMap<String,String> writerRules = null;
87  	private String writerIndentationChars = null;
88  	private HashMap<Integer,String> keystrokeMappings = null;
89  	
90  	private int tabIndex;
91  	private boolean setFocusAfterReady;
92  	private boolean setTabIndexAfterReady;
93  	
94  	/**
95  	 * The constructor should first call super() to initialize the component and
96  	 * then handle any initialization relevant to Vaadin.
97  	 */
98  	public VCKEditorTextField() {
99  		// CKEditor prefers a textarea, but found too many issues trying to use createTextareaElement() instead of a simple div, 
100 		// which is okay in Vaadin where an HTML form won't be used to send the data back and forth.
101 		DivElement rootDiv = Document.get().createDivElement();
102 		rootDiv.getStyle().setOverflow(Overflow.HIDDEN);
103 		rootDiv.getStyle().setVisibility(Visibility.VISIBLE); // required for FF to show in popup windows repeatedly
104 		setElement(rootDiv);
105 
106 		// This method call of the Paintable interface sets the component
107 		// style name in DOM tree
108 		setStyleName(CLASSNAME);
109 	}
110 	
111 	/**
112 	 * Called whenever an update is received from the server
113 	 */
114 	@Override
115 	public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
116 		clientToServer = client;
117 		paintableId = uidl.getId();
118 		
119 		//logState("updateFromUIDL()");
120 		
121 		boolean needsDataUpdate = false;
122 		boolean needsProtectedBodyUpdate = false;
123 		boolean readOnlyModeChanged = false;
124 		
125 		// This call should be made first.
126 		// It handles sizes, captions, tooltips, etc. automatically.
127 		// If clientToServer.updateComponent returns true there have been no changes
128 		// and we do not need to update anything.
129 		if ( clientToServer.updateComponent(this, uidl, true) ) {
130 			return;
131 		}
132 			
133 		if ( ! resizeListenerInPlace ) {
134 			//logState("updateFromUIDL() Adding resize listener to element: " + getElement());
135 			LayoutManager.get(client).addElementResizeListener(getElement(), new ElementResizeListener() {
136 
137 				@Override
138 				public void onElementResize(ElementResizeEvent e) {
139 					doResize();
140 				}
141 			});
142 			resizeListenerInPlace = true;
143 		}
144 		
145 		if ( uidl.hasAttribute(ATTR_IMMEDIATE) ) {
146 	 		immediate = uidl.getBooleanAttribute(ATTR_IMMEDIATE);
147 		}
148 		if ( uidl.hasAttribute(ATTR_READONLY) ) {
149 			boolean newReadOnly = uidl.getBooleanAttribute(ATTR_READONLY);
150 			readOnlyModeChanged = newReadOnly != readOnly;
151 			readOnly = newReadOnly;
152 		}
153 		if ( uidl.hasAttribute(ATTR_VIEW_WITHOUT_EDITOR) ) {
154 			viewWithoutEditor = uidl.getBooleanAttribute(ATTR_VIEW_WITHOUT_EDITOR);
155 		}
156 		if ( uidl.hasAttribute(ATTR_PROTECTED_BODY) ) {
157 			boolean state = uidl.getBooleanAttribute(ATTR_PROTECTED_BODY);
158 			if (protectedBody != state) {
159 				protectedBody = state ;
160 				needsProtectedBodyUpdate = true;
161 			}
162 		}
163 		if ( uidl.hasVariable(VAR_TEXT) ) {
164 			String data = uidl.getStringVariable(VAR_TEXT);
165 			if ( ckEditor != null )
166 				dataBeforeEdit = ckEditor.getData();
167 			needsDataUpdate = ! data.equals(dataBeforeEdit);
168 			//logState("updateFromUIDL() includes VAR_TEXT: " + data + "; needsDataUpdate: " + needsDataUpdate);
169 			dataBeforeEdit = data;
170 		}
171 		
172 		// Save the client side identifier (paintable id) for the widget
173 		if ( ckeditorContainerId == null )
174 			ckeditorContainerId = "VCKE_" + paintableId;
175 		if ( ! ckeditorContainerId.equals(getElement().getId()) ) {
176 			getElement().setId(ckeditorContainerId);
177 		}
178 		
179 		if ( viewWithoutEditor ) {
180 			if ( ckEditor != null ) {
181 				// may update the data and change to viewWithoutEditor at the same time 
182 				if ( ! needsDataUpdate ) {
183 					dataBeforeEdit = ckEditor.getData();
184 				}
185 				//logState("updateFromUIDL() unloading editor for viewWithoutEditor");
186 				unloadEditor();
187 			}
188 			getElement().setInnerHTML(dataBeforeEdit);
189 		}
190 		else if ( ckEditor == null ) {
191 			//logState("updateFromUIDL() no editor, gathering UIDL attributes");
192 			getElement().setInnerHTML(""); // in case we put contents in there while in viewWithoutEditor mode
193 			
194 			// If we have the config, use it, otherwise attempt to use any prior config we have
195 			if ( uidl.hasAttribute(ATTR_INPAGECONFIG) )
196 				inPageConfig = uidl.getStringAttribute(ATTR_INPAGECONFIG); 
197 			
198 			if ( uidl.hasAttribute(ATTR_WRITER_INDENTATIONCHARS) )
199 				writerIndentationChars = uidl.getStringAttribute(ATTR_WRITER_INDENTATIONCHARS);
200 			
201 			if ( uidl.hasAttribute(ATTR_FOCUS) ) {
202 				setFocus(uidl.getBooleanAttribute(ATTR_FOCUS));
203 			}
204 			
205 			// See if we have any writer rules
206 			int i = 0;
207 			while( true ) {
208 				if ( ! uidl.hasAttribute(ATTR_WRITERRULES_TAGNAME+i)  ) {
209 					break;
210 				}
211 				if ( i == 0 && writerRules != null )
212 					writerRules.clear();
213 				// Save the rules until our instance is ready
214 				String tagName = uidl.getStringAttribute(ATTR_WRITERRULES_TAGNAME+i);
215 				String jsRule  = uidl.getStringAttribute(ATTR_WRITERRULES_JSRULE+i);
216 				if ( writerRules == null ) {
217 					writerRules = new HashMap<String,String>();
218 				}
219 				writerRules.put(tagName, jsRule);
220 				++i;
221 			}
222 			
223 			// See if we have any keystrokes
224 			i = 0;
225 			while( true ) {
226 				if ( ! uidl.hasAttribute(ATTR_KEYSTROKES_KEYSTROKE+i)  ) {
227 					break;
228 				}
229 				if ( i == 0 && keystrokeMappings != null )
230 					keystrokeMappings.clear();
231 				// Save the keystrokes until our instance is ready
232 				int keystroke = uidl.getIntAttribute(ATTR_KEYSTROKES_KEYSTROKE+i);
233 				String command  = uidl.getStringAttribute(ATTR_KEYSTROKES_COMMAND+i);
234 				if ( keystrokeMappings == null ) {
235 					keystrokeMappings = new HashMap<Integer,String>();
236 				}
237 				keystrokeMappings.put(keystroke, command);
238 				++i;
239 			}
240 			
241 			// See if we have any protected source regexs
242 			i = 0;
243 			while( true ) {
244 				if ( ! uidl.hasAttribute(ATTR_PROTECTED_SOURCE+i)  ) {
245 					break;
246 				}
247 				if ( i == 0 && protectedSourceList != null )
248 					protectedSourceList.clear();
249 				// Save the regex until our instance is ready
250 				String regex = uidl.getStringAttribute(ATTR_PROTECTED_SOURCE+i);
251 				if ( protectedSourceList == null ) {
252 					protectedSourceList = new LinkedList<String>();
253 				}
254 				protectedSourceList.add(regex);
255 				++i;
256 			}
257 			
258 			//logState("updateFromUIDL() creating editor");
259 			loadEditor();
260 			
261 			// editor data and some options are set when the instance is ready....
262 		} else if ( ckEditorIsReady ) {
263 			//logState("updateFromUIDL() editor is ready and being updated");
264 			if ( needsDataUpdate ) {
265 				setEditorData(dataBeforeEdit);
266 			}
267 			
268 			if ( needsProtectedBodyUpdate ) {
269 				ckEditor.protectBody(protectedBody);
270 			}
271 			
272 			if (uidl.hasAttribute(ATTR_INSERT_HTML)) {
273 				ckEditor.insertHtml(uidl.getStringAttribute(ATTR_INSERT_HTML));
274 			}
275 			
276 			if (uidl.hasAttribute(ATTR_INSERT_TEXT)) {
277 				ckEditor.insertText(uidl.getStringAttribute(ATTR_INSERT_TEXT));
278 			}
279 
280 			if ( uidl.hasAttribute(ATTR_FOCUS) ) {
281 				setFocus(uidl.getBooleanAttribute(ATTR_FOCUS));
282 			}
283 			
284 			if ( readOnlyModeChanged ) {
285 				ckEditor.setReadOnly(readOnly);
286 			}			
287 		}
288 	}
289 	
290 	void setEditorData(String html) {
291 		if ( ckEditorIsReady ) {
292 			dataBeforeEdit = html;
293 			ignoreDataChangesUntilReady = true;
294 			ckEditor.setData(dataBeforeEdit); // We reset our flag above when the editor tells us the data is ready
295 		}
296 	}
297 	
298 	void unloadEditor() {
299 		if ( ckEditor != null ) {
300 			//logState("unloadEditor() with editor");
301 			ckEditor.destroy(true);
302 			ckEditor = null;
303 		} else {
304 			//logState("unloadEditor() without editor");
305 		}
306 		ignoreDataChangesUntilReady = false;
307 		ckEditorIsReady = false;
308 		ckEditorIsBeingLoaded = false;
309 		setFocusAfterReady = false;
310 		setTabIndexAfterReady = false;
311 	}
312 	
313 	protected void loadEditor() {
314 		if ( ckEditor == null && inPageConfig != null && ! ckEditorIsBeingLoaded ) {
315 			ckEditorIsBeingLoaded = true;
316 			//logState("loadEditor() calling CKEditorService.loadLibrary");
317 			CKEditorService.loadLibrary(new ScheduledCommand() {
318 				@Override
319 				public void execute() {
320 					//logState("loadEditor().execute() calling CKEditorService.loadEditor with inPageConfig: " + inPageConfig);
321                    ckEditor = loadEditor(inPageConfig);
322 					ckEditorIsBeingLoaded = false; // Don't need this as we have ckEditor set now.
323 					//logState("loadEditor().execute() CKEditorService.loadEditor done");
324 				}
325 			});
326 		} else {
327 			//logState("loadEditor() ignored, no editor, inPageConfig and/or is being loaded already");			
328 		}
329 	}
330 
331 	/**
332 	 * Expose <code>loadEditor</code> command to subclasses, so that they can perform additional logic
333 	 * before/after creating CKEditor instance on the page, e.g. register external plugins.
334 	 * <p>
335 	 * This method is executed as a callback scheduled command when loading the CKEditor library has completed.
336 	 */
337 	protected CKEditor loadEditor(String inPageConfig) {
338 		return (CKEditor) CKEditorService.loadEditor(ckeditorContainerId,
339 				VCKEditorTextField.this,
340 				inPageConfig,
341 				VCKEditorTextField.super.getOffsetWidth(),
342 				VCKEditorTextField.super.getOffsetHeight());
343 	}
344 
345 	// Listener callback
346 	// Called if the user clicks the (Vaadin) Save button. 
347 	@Override
348 	public void onSave() {
349 		//logState("onSave() - readOnly: " + readOnly);
350 		if ( ckEditorIsReady && ! readOnly ) {
351 			ignoreDataChangesUntilReady = false; // If they give us data by saving, we don't ignore whatever it is
352 			String data = ckEditor.getData();
353 			if ( ! data.equals(dataBeforeEdit) ) {
354 				//logState("onSave() - data has changed");
355 				clientToServer.updateVariable(paintableId, VAR_TEXT, data, false);
356 				dataBeforeEdit = data;
357 			} else {
358 				//logState("onSave() - data has NOT changed");
359 			}
360 			clientToServer.updateVariable(paintableId, VAR_VAADIN_SAVE_BUTTON_PRESSED,"",false); // inform that the button was pressed too
361 			logState("onSave() - sending pending changes to the server; rpc queue count: " + clientToServer.getServerRpcQueue().size() + "; isFlushPending: " + clientToServer.getServerRpcQueue().isFlushPending());
362 			clientToServer.getServerRpcQueue().flush(); // ensure anything queued up goes now on SAVE
363 			logState("onSave() - after sent pending changes to the server; rpc queue count: " + clientToServer.getServerRpcQueue().size() + "; isFlushPending: " + clientToServer.getServerRpcQueue().isFlushPending());
364 		}
365 	}
366 
367 	// Listener callback
368 	@Override
369 	public void onBlur() {
370 		//logState("onBlur()");
371 		if ( ckEditorIsReady ) {
372 			boolean sendToServer = false;
373 			
374 			if ( clientToServer.hasEventListeners(this, EventId.BLUR) ) {
375 				//logState("onBlur() - has blur event listener, will send");
376 				sendToServer = true;
377 	            clientToServer.updateVariable(paintableId, EventId.BLUR, "", false);
378 			}
379 			else {
380 				//logState("onBlur() - has no blur event listener, will not send");
381 			}
382 			
383 			// Even though CKEditor 4.2 introduced a change event, it doesn't appear to fire if the user stays in SOURCE mode,
384 			// so while we do use the change event, we still are stuck with the blur listener to detect other such changes.
385 			if ( ! readOnly && ! ignoreDataChangesUntilReady ) {
386 				String data = ckEditor.getData();
387 				if ( ! data.equals(dataBeforeEdit) ) {
388 					//logState("onBlur() - data has changed");
389 					clientToServer.updateVariable(paintableId, VAR_TEXT, data, false);
390 	            	sendToServer = true;
391 	            	dataBeforeEdit = data;
392 				} else {
393 					//logState("onBlur() - data has NOT changed");
394 				}
395 			} else {
396 				//logState("onBlur() - ignoring data has changed");
397 			}
398 			
399 	        if (sendToServer) {
400 				logState("onBlur() - flushing changes to server; rpc queue count: " + clientToServer.getServerRpcQueue().size() + "; isFlushPending: " + clientToServer.getServerRpcQueue().isFlushPending());
401 	        	clientToServer.getServerRpcQueue().flush();
402 				logState("onBlur() - after flushed changes to server; rpc queue count: " + clientToServer.getServerRpcQueue().size() + "; isFlushPending: " + clientToServer.getServerRpcQueue().isFlushPending());
403 			}
404 		}
405 	}
406 
407 	// Listener callback
408 	@Override
409 	public void onFocus() {
410 		if ( ckEditorIsReady ) {
411 			if ( clientToServer.hasEventListeners(this, EventId.FOCUS) ) {
412 	            clientToServer.updateVariable(paintableId, EventId.FOCUS, "", true);
413 			}
414 		}
415 	}
416 
417 	// Listener callback
418 	@Override
419 	public void onInstanceReady() {
420 		ckEditorIsReady = true;
421 
422 		//logState("onInstanceReady()");
423 		ckEditor.instanceReady(this);
424 		
425 		if ( writerRules != null ) {
426 			//logState("onInstanceReady() - doing writerRules: " + writerRules.size());
427 			Set<String> tagNameSet = writerRules.keySet();
428 			for( String tagName : tagNameSet ) {
429 				ckEditor.setWriterRules(tagName, writerRules.get(tagName));
430 			}
431 		}
432 		
433 		if ( writerIndentationChars != null ) {
434 			//logState("onInstanceReady() - doing writerIndentationChars: " + writerIndentationChars);
435 			ckEditor.setWriterIndentationChars(writerIndentationChars);
436 		}
437 		
438 		if ( keystrokeMappings != null ) {
439 			Set<Integer> keystrokeSet = keystrokeMappings.keySet();
440 			//logState("onInstanceReady() - doing keystrokeMappings: " + keystrokeSet.size());
441 			for( Integer keystroke : keystrokeSet ) {
442 				ckEditor.setKeystroke(keystroke, keystrokeMappings.get(keystroke));
443 			}
444 		}
445 		
446 		if ( protectedSourceList != null ) {
447 			//logState("onInstanceReady() - doing protectedSourceList: " + protectedSourceList.size());
448 			for( String regex : protectedSourceList ) {
449 				ckEditor.pushProtectedSource(regex);
450 			}
451 		}
452 		
453 		if ( dataBeforeEdit != null ) {
454 			//logState("onInstanceReady() - doing setEditorData(dataBeforeEdit): " + dataBeforeEdit);
455 			setEditorData(dataBeforeEdit);
456 		}
457 				
458 		
459 		if (setFocusAfterReady) {
460 			//logState("onInstanceReady() - doing setFocusAfterReady");
461 			setFocus(true);
462 		}
463 		
464 		if ( setTabIndexAfterReady ) {
465 			//logState("onInstanceReady() - doing setTabIndexAfterReady tabIndex: " + tabIndex);
466 			setTabIndex(tabIndex);
467 		}
468 		
469 		//logState("onInstanceReady() - doing doResize()");
470 		doResize();
471 		
472 		if (protectedBody) {
473 			//logState("onInstanceReady() - doing protectedBody: " + protectedBody);
474 			ckEditor.protectBody(protectedBody);
475 		}
476 
477 		//logState("onInstanceReady() - doing setReadOnly: " + readOnly);
478 		ckEditor.setReadOnly(readOnly);
479 		
480 		ckeditorVersion = CKEditorService.version();
481 		//logState("onInstanceReady() - retrieved ckeditorVersion: " + ckeditorVersion);
482 		clientToServer.updateVariable(paintableId, VAR_VERSION, ckeditorVersion, true);
483 	}
484 	
485 	// Listener callback
486 	@Override
487 	public void onChange() {
488 		if ( ckEditor != null && ! readOnly && ! ignoreDataChangesUntilReady ) {
489 			//logState("onChange()");
490 			String data = ckEditor.getData();
491 			if ( ! data.equals(dataBeforeEdit) ) {
492 				//logState("onChange() - data has changed");
493 				clientToServer.updateVariable(paintableId, VAR_TEXT, data, immediate);
494             	dataBeforeEdit = data;
495 			} else {
496 				//logState("onChange() - data has NOT changed");
497 			}
498 		} else {
499 			//logState("onChange() changes ignored");
500 		}
501 	}
502 	
503 	// Listener callback
504 	@Override
505 	public void onModeChange(String mode) {
506 		if ( ckEditor != null ) {
507 			//logState("onModeChange() mode: " + mode);
508 			if ( ! readOnly ) {
509 				String data = ckEditor.getData();
510 				if ( ! data.equals(dataBeforeEdit) ) {
511 					//logState("onModeChange() mode: " + mode + " - data has changed");
512 					clientToServer.updateVariable(paintableId, VAR_TEXT, data, immediate);
513 	            	dataBeforeEdit = data;
514 	            	ignoreDataChangesUntilReady = false; // if there's a change while doing SOURCE mode buttons, we'll accept any of those changes.
515 				} else {
516 					//logState("onModeChange() mode: " + mode + " - data has NOT changed");	
517 				}
518 			} else {
519 				//logState("onModeChange() is read-only so ignoring data changes in mode: " + mode);
520 			}
521 			
522 			if ("wysiwyg".equals(mode)) {
523 				ckEditor.protectBody(protectedBody);
524 			}
525 		}
526 	}
527 	
528 	// Listener callback
529 	@Override
530 	public void onSelectionChange() {
531 		if ( ckEditorIsReady ) {
532 			if ( clientToServer.hasEventListeners(this, EVENT_SELECTION_CHANGE) ) {
533 				//logState("onSelectionChange()");
534 				String html = ckEditor.getSelectedHtml();
535 				if ( html == null )
536 					html = "";
537 				// We'll send an update for nothing selected (unselected) only if we've sent out an event for a prior selected event.
538 				boolean isBlankSelection = "".equals(html);
539 				if ( ! isBlankSelection || notifyBlankSelection ) {
540 		            clientToServer.updateVariable(paintableId, EVENT_SELECTION_CHANGE, html, true);
541 		            notifyBlankSelection = ! isBlankSelection;
542 				}
543 			}
544 		}
545 	}
546 
547 	// Listener callback
548 	@Override
549 	public void onDataReady() {
550 		if ( ckEditorIsReady ) {
551 			//logState("onDataReady() - data changes will be accepted after this");
552 			String data = ckEditor.getData();
553 			if ( ! data.equals(dataBeforeEdit) && ! ignoreDataChangesUntilReady ) {
554 				clientToServer.updateVariable(paintableId, VAR_TEXT, data, immediate);				
555 			}
556 			ignoreDataChangesUntilReady = false;
557 			dataBeforeEdit = data;
558 			ckEditor.protectBody(protectedBody);
559 		} else {
560 			//logState("onDataReady() ignored as editor not ready");
561 		}
562 	}
563 
564 	@Override
565 	public void setWidth(String width) {
566 		super.setWidth(width);
567 		doResize();
568 	}
569 	
570 	@Override
571 	public void setHeight(String height) {
572 		super.setHeight(height);
573 		doResize();
574 	}
575 	
576 	protected void doResize() {
577 		if (ckEditorIsReady) {
578 			//logState("doResize() editor ready doing ScheduledCommand for resize");
579 			Scheduler.get().scheduleDeferred(new ScheduledCommand() {				
580 				@Override
581 				public void execute() {
582 					//logState("doResize().execute() doing ckEditor.resize");
583 					ckEditor.resize(VCKEditorTextField.super.getOffsetWidth(), VCKEditorTextField.super.getOffsetHeight());
584 					//logState("doResize().execute() done");
585 				}
586 			});
587 		} else {
588 			//logState("doResize() ignored - editor not ready");			
589 		}
590 	}
591 
592 	private void logState(String methodMessage) {
593 		Logger.getGlobal().info("VCKEditorTextField." + methodMessage + "; ckEditor: " + (ckEditor==null?"None":ckEditor.getId()) + 
594 				"; ckEditorIsReady: " + ckEditorIsReady + "; ckEditorIsLoading: " + ckEditorIsBeingLoaded + "; ignoreDataChangesUntilReady: " + ignoreDataChangesUntilReady + "; paintableId: " + paintableId);
595 	}
596 	
597 	@Override
598 	protected void onLoad() {
599 		//logState("onLoad()");
600 		if ( ! viewWithoutEditor ) {
601 			loadEditor();
602 		}
603 	}
604 
605 	@Override
606 	protected void onUnload() {
607 		//logState("onUnload()");
608 		unloadEditor();
609 	}
610 
611 	@Override
612 	public int getTabIndex() {
613 		if (ckEditorIsReady) {
614 			return ckEditor.getTabIndex();
615 		} else {
616 			return tabIndex;
617 		}
618 	}
619 
620 	@Override
621 	public void setTabIndex(int tabIndex) {
622 		if (ckEditorIsReady) {
623 			ckEditor.setTabIndex(tabIndex);
624 		} else {
625 			setTabIndexAfterReady = true;
626 		}
627 		this.tabIndex = tabIndex;
628 	}
629 
630 	@Override
631 	public void setAccessKey(char arg0) {
632 		return;
633 	}
634 
635 	@Override
636 	public void setFocus(boolean arg0) {
637 		if (arg0) {
638 			if (ckEditorIsReady)
639 				ckEditor.focus();
640 			else
641 				setFocusAfterReady = true;
642 		} else {
643 			setFocusAfterReady = false;
644 		}
645 	}
646 
647 }