View Javadoc
1   // Copyright (C) 2010-2015 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.ArrayList;
11  import java.util.List;
12  
13  import com.google.gwt.core.client.GWT;
14  import com.google.gwt.core.client.JavaScriptObject;
15  import com.google.gwt.core.client.Scheduler;
16  import com.google.gwt.core.client.Scheduler.RepeatingCommand;
17  import com.google.gwt.core.client.Scheduler.ScheduledCommand;
18  import com.google.gwt.dom.client.Document;
19  import com.google.gwt.dom.client.ScriptElement;
20  
21  /**
22   * GWT wrapper for CKEDITOR for use by our Vaadin-based CKEditorService.
23   */
24  public class CKEditorService {
25  	
26  	private static boolean libraryLoadInited = false;
27  	private static boolean libraryLoaded = false;
28  	private static List<ScheduledCommand> afterLoadedStack = new ArrayList<ScheduledCommand>();
29  	
30  	public static synchronized void loadLibrary(ScheduledCommand afterLoad) {
31  		if (! libraryLoadInited) {
32  			libraryLoadInited = true;
33  			String url = GWT.getModuleBaseURL() + "ckeditor/ckeditor.js";
34  			ScriptElement se = Document.get().createScriptElement();
35  			se.setSrc(url);
36  			se.setType("text/javascript");
37  			Document.get().getElementsByTagName("head").getItem(0).appendChild(se);
38  			Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {				
39  				@Override
40  				public boolean execute() {
41  					if (libraryReady()) {
42  						reduceBlurDelay();
43  						for (ScheduledCommand sc: afterLoadedStack) {
44  							sc.execute();
45  						}
46  						libraryLoaded = true;
47  						return false;
48  					}
49  					return true;
50  				}
51  			}, 50);
52  		}
53  		if (libraryLoaded) {
54  			afterLoad.execute();
55  		} else {
56  			afterLoadedStack.add(afterLoad);
57  		}
58  	}
59  	
60  	public static native boolean libraryReady()
61  	/*-{
62  		if($wnd.CKEDITOR) {
63  			return true;
64  		} 
65  		return false;
66  	}-*/;
67  	
68  	/**
69  	 * Use this method to load editor to given identifier.
70  	 * 
71  	 * @param id the string DOM <div> 'id' attribute value for the element you want to replace with CKEditor
72  	 * @param listener the CKEditorService.CKEditorListener will get notified when the editor instance is ready, changed, etc.
73  	 * @param jsInPageConfig the String possible custom "in page" configuration; note that this must be an expected JSON for the CKEDITOR in page config.
74  	 * sent "as is" without any real syntax or security testing, so be sure you know it's valid and not malicious, 
75  	 * such as: <code>{toolbar : 'Basic', language : 'en'}</code>
76  	 */
77  	public static native JavaScriptObject loadEditor(String id, CKEditorService.CKEditorListener listener, String jsInPageConfig, int compWidth, int compHeight)
78  	/*-{
79  	 	// Build our inPageConfig object based on the JSON jsInPageConfig sent to us.
80  	 	var inPageConfig = @org.vaadin.openesignforms.ckeditor.widgetset.client.ui.CKEditorService::convertJavaScriptStringToObject(Ljava/lang/String;)(jsInPageConfig);
81  	 	
82  	 	var myEditor;
83  	 	
84  	 	if (inPageConfig == null) {
85  	 		inPageConfig = new Object;
86  	 		inPageConfig.width = compWidth;
87  	 		inPageConfig.height = compHeight;
88  	 	} else {
89  	 		if (!inPageConfig.width) inPageConfig.width = compWidth;
90  	 		if (!inPageConfig.height) inPageConfig.height = compHeight;
91  	 	}
92  	 	
93  	 	myEditor = $wnd.CKEDITOR.appendTo( id, inPageConfig );
94  	 		 	
95  	 	// The 'listener' passed to us is used as 'listenerData' for the callback.
96  		myEditor.on( 'instanceReady', function( ev ) {
97      		ev.listenerData.@org.vaadin.openesignforms.ckeditor.widgetset.client.ui.CKEditorService.CKEditorListener::onInstanceReady()();
98  		}, null, listener);
99  		
100 		return myEditor;
101 
102 	}-*/;
103 	
104 	public native static String version()
105 	/*-{
106 		return $wnd.CKEDITOR.version;
107 	}-*/;
108 	
109 	// This is a hack attempt to resolve issues with Vaadin when the CKEditorTextField widget is set with BLUR and FOCUS listeners.
110 	// In particular, the Safari browser could not deal well with PASTE, right clicking in a table cell, etc.
111 	// because those operations resulted in BLUR then FOCUS events in rapid succession, causing the UI to update.
112 	// But the 200 value is too long and we find that often the button acts faster than the BLUR can fire from CKEditor
113 	// so Vaadin doesn't get the latest contents.
114 	// Even though CKEditor 4.2 introduced a change event, it doesn't appear to fire if you stay in SOURCE mode, which many people do use.
115 	public native static void reduceBlurDelay()
116 	/*-{
117 		$wnd.CKEDITOR.focusManager._.blurDelay = 20; // the default is 200 even if the documentation says it's only 100
118 	}-*/;
119 	
120 	/**
121 	 * Returns a javascript CKEDITOR.editor instance for given id.
122 	 * 
123 	 * @param id the String id of the editor instance
124 	 * @return the overlay for CKEDITOR.editor or null if not yet initialized
125 	 */
126 	public native static CKEditor get(String id)
127 	/*-{
128 		return $wnd.CKEDITOR.instances[ id ];
129 	}-*/;
130 	
131 	// TODO: Never tested yet
132 	public native static void addStylesSet(String name, String jsStyles)
133 	/*-{
134 	 	var styles = @org.vaadin.openesignforms.ckeditor.widgetset.client.ui.CKEditorService::convertJavaScriptStringToObject(Ljava/lang/String;)(jsStyles);
135 		$wnd.CKEDITOR.addStylesSet(name,styles);
136 	}-*/;
137 	
138 	// TODO: Never tested yet
139 	public native static void addTemplates(String name, String jsDefinition)
140 	/*-{
141 	 	var definition = @org.vaadin.openesignforms.ckeditor.widgetset.client.ui.CKEditorService::convertJavaScriptStringToObject(Ljava/lang/String;)(jsDefinition);
142 		$wnd.CKEDITOR.addTemplates(name,definition);
143 	}-*/;
144 
145 	public native static JavaScriptObject convertJavaScriptStringToObject(String jsString)
146 	/*-{
147 	    try {
148 	 		return eval('('+jsString+')');
149 	 	} catch (e) { 
150 	 		alert('convertJavaScriptStringToObject() INVALID JAVASCRIPT: ' + jsString); 
151 	 		return {}; 
152 	 	}
153 	}-*/;
154 
155 
156 	/**
157 	 * An interface for the VCKEditorTextField to get events from the CKEditor.
158 	 */
159 	public interface CKEditorListener {
160 		public void onInstanceReady();
161 		public void onChange();
162 		public void onSelectionChange();
163 		public void onModeChange(String mode);
164 		public void onDataReady();
165 		public void onBlur();
166 		public void onFocus();
167 		public void onSave();
168 	}
169 
170 }