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