ZKTM Small Talks |
Integrating FCKeditor |
| Simply Rich | Tom M. Yeh Chief Architect Potix Corporation December 27, 2005 Revised on February 8, 2006 |
Version
Applicable to ZK 2.1.1 and later.
This article described the process to integrate FCKeditor as a ZK component. It is aimed to illustrate the component development, rather FCKeditor itself.
FCKeditor is a popular HTML on-line text edit developed by Frederico Caldeira Knabben.

ZK is an event-driven, XHTML/XUL-based, AJAX-embedded, all Java framework to enable rich user interfaces for Web applications.
Goal: to have a ZK component that encapsulates a FCKeditor.
The component is called fckeditor. Its use is
similar to textbox.
http://www.zkoss.org/zkdemo/test/fckeditor.zul
<fckeditor id="ed" value="Some" onChange="tb.value = self.value"/>
<textbox id="tb" rows="6" onChange="ed.value = self.value"/>
The source codes could be downloaded from sourceforge. It is recommended to download one of zk-FCKeditor*.zip and unzip it first, before reading the rest of this article.
Notice that the onChange event works only on Internet Explorer, because it is based on the onBlur event which FCKeditor 2.2 doesn't support other browser yet.
Copy Files from the FCKeditor Distribution
Basically we have to copy
fckconfig.js,fckeditor.js,fckstyles.xml,fcktemplates.xml, and all files, except_source, under theeditordirectory. Refer to FCKeditor documents for details.As mentioned in ZK Developer's Guide, we could embed web resources in JAR file. Simply put, we have to put them under the
webdirectory. To avoid being confused with other projects, we put them under the/web/js/ext/FCKeditordirectory locatable by the Java classpath.In other words, files located under this directory are all copied from the FCKeditor distribution without modification.
For the
fckeditorcomponent, we need to prepare only four files:lang-addon.xml,fckez.js,fckeditor.dsp, andFCKeditor.java.lang-addon.xml
First, we have to prepare a file called lang-addon.xml. It must be put under the /metainfo/zk directory locatable by the Java classpath.
It describes components that will be added to an existent language as depicted below.
<language-addon> <addon-name>fckez</addon-name> <language-name>xul/html</language-name> <zscript> import org.zkforge.fckez.*; </zscript> <component> <component-name>fckeditor</component-name> <component-class>org.zkforge.fckez.FCKeditor</component-class> <mold> <mold-name>default</mold-name> <mold-uri>~./fckez/fckeditor.dsp</mold-uri> </mold> </component> </language-addon>
- <addon-name>
- The name of this language addon.
- <language-name>
- Specifies the language to which the components shall be added.
- A language is defined by a file called lang.xml. Its format is similar but it assumes the specified language doesn't exist yet.
- <zscript>
- Specifies any Java codes that need to execute (via BeanShell) before interpreting a page.
- <component>
- Defines a component by specifying the component name and the Java class to use. You could define as many as component as you want.
- In addition to specify a new component, you could override any existent one here. For example, you might prefer your own class instead of
org.zkoss.zul.Window.- <component><mold>
- A component might have zero, one or multiple molds. If it doesn't have any mold, it has to take care of rendering by overriding the
redrawmethod. If it does, the mold specified here will be used to render the component into HTML tags. The mold calleddefaultis the default mold.fckeditor.dsp
This file is a template to generate the corresponding HTML tags for each
fckeditorcomponent.<%@ taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" %> <c:set var="self" value="${requestScope.arg.self}"/>
<c:set var="edid" value="${self.uuid}!ed"/>
<c:set var="eduri" value="~./js/ext/FCKeditor/editor/fckeditor.html?InstanceName=${edid}"/>
<c:set var="eduri" value="${eduri}&Toolbar=${self.toolbarSet}" unless="${empty self.toolbarSet}"/>
<div id="${self.uuid}"${self.htmlAttributes} zk_type="fckez.fckez.FCKeditor" zk_src="${c:encodeURL(eduri)}">
<input type="hidden" id="${edid}" value="${c:escapeXML(self.value)}"/>
<input type="hidden" id="${edid}___Config" value="${self.configString}"/>
<iframe id="${edid}___Frame" src="${c:encodeURL('~./img/spacer.gif')}" ${c:attr('width',self.width)}${c:attr('height',self.height)} frameborder="no" scrolling="no"></iframe>
</div>The skeleton is based on the document of FCKeditor. You need to declare two input field to hold the initial value and configuration, and an inline frame to hold the HTML editor.
There are several things worth to mention, when we generate HTML tags for components.
- DSP is similar to JSP. Refer to ZK Developer's Guide for details. However, you could use JSP or other technologies, if you would like to.
- You must group the generated HTML tags under one HTML tag, because ZK Update Engine assumes it. If your component consists of multiple parts, you could use SPAN or DIV to group them.
- The topmost HTML tag must have the id attribute and the value must be the component's UUID.
- When ZK renders a page (DSP or not), a map is stored in the request's attribute called
arg. The map currently has only item calledself. It is the component being rederered.- The zk_type attribute of the topmost HTML tag is optional. If specified, the Client Engine will initialize it by loading the module (a JavaScript file) and then invoke the init method, if any.
In this case, we specify
fckez.fckez.FCKeditor. It means the JavaScript codes are located at the file called/web/js/fckez/fckez.js, and this file must be loaded when the FCKeditor component is encountered. It also means the method calledzkFCKeditor.initmust be called, when any instance the FCKeditor component is created.Furthmore, the
zkFCKeditor.setAttrmethod and others will be called when the server has updated an attribute according to application's codes. It will be described more detailedly later.- We don't specify the
srcattribute of iframe to the correct URL. Rather, we initialize it by copying from the zk_src attribute ofdiv, whenzkFCKeditor.initis called.Why?
To have better performance, ZK won't load JavaScript files until the corresponding components are encountered. On the other hand, to have better responsiveness, HTML is generated and render first. Thus, we to ensure our JavaScript codes for FCKeditor are loaded, when FCKeditor starts to initialize itself.
For most components, such as DOJO, it is not necessary to applying this trick. Rather, you usually use the
zk.loadmethod to load additional JavaScript files.FCKeditor.java
It is the Java class to represent the
fckeditorcomponent. First, we have to extend it fromorg.zkoss.zk.ui.AbstractComponent. Second, we implement theorg.zkoss.zk.ui.event.Inputableinterface for the input-type components, such asfckeditor. Then, you implement whatever behavior you want.If a method (usually a setter, aka., mutator) is going to change the visual representation of a component, you could do one of the following.
- Invoke the
smartUpdatemethod to notify the client the value of an attribute is changed. Once called, the client will receive thesetAttrorrmAttrcommand, depending on whether the value isnull. ZK Client Engine will handle it automatically.
- For
fckeditor,smartUpdateis all we need.- However, the default behavior of
setAttrandrmAttris not. Thus, we have to provide some JavaScript codes to handle.public void setValue(String value) { if (value == null) value = ""; if (!value.equals(_value)) { _value = value; smartUpdate("value", value); } }- Invoke
invalidate(INNER)orinvalidate(OUTER)to redraw the whole component.- Invoke the
responsemethod to send a specific command. Notice thatsmartUpdateis similar toresponse, except all smart updates will be removed onceinvalidateis called against the same component.
fckez.js
It is common that we have to provide some JavaScript codes to handle the behavior of a component at the client. In this example, the JavaScript file is called
fckez.js. Its content is straightforward.function zkFCKeditor() {} zkFCKeditor.init = function (cmp) { FCKeditorAPI = __FCKeditorNS = null; var ifr = document.getElementById(cmp.id + "!ed___Frame"); ifr.src = cmp.getAttribute("zk_src"); }; zkFCKeditor.setAttr = function (ed, name, value) { var ifr = document.getElementById(ed.id + "!ed___Frame"); var fed = FCKeditorAPI.GetInstance(ed.id + "!ed"); if (ifr) { switch (name) { case "width": ifr.width = value; return true; case "height": ifr.height = value; return true; case "value": if (fed) fed.SetHTML(value); return true; } } return false; };
- zkFCKeditor.init
- As mentioned, it is called when a component is initialized. For FCKeditor, we simple initialize the
srcattribute ofiframeto the correct URL.However, FCKeditor 2.2 has a bug that it doesn't clean up some variables when unloaded. Thus, We have to clean up them here.
- zkFCeditor.setAttr
- As mentioned above, the
fckeditorcomponent usessmartUpdateto notify the client what attribute to modify. However, the attributes, infckeditor, are not embedded as part of the topmost HTML tag, so we cannot let ZK Client Engine to handle it. Rather, we have to provide a JavaScript file to handle it.Behind the Scene
When ZK Client Engine receives the
setAttrandrmAttrcommand, it checks whether thezk_typeattribute is defined. Forfckeditor, the zk_type attribute defined with FCKeditor. ZK Client Engine then invokes thezkFCKeditor.setAttrfunction, if any.Thus, you could implement
zkFCKeditor.setAttrto intercept thesetAttrcommand. This method could return true to notify ZK Client Engine that the attribute has been updated, or false to notify that ZK Client Engine shall handle it.You could explode more attribute to the
fckeditorcomponent by intercepting more attributes here. In the current release, we process onlywidth,heightandvalue, as illustrated above.
ZK has been deliberately designed to simplify not only the application development, but also the component development. As shown in this example, the development process of a component is as simple as four steps.