Saturday, August 11, 2012

ZK Component Development Tutorial: Getting Started


Introduction

This post is about how to create a new ZK Component, step by step, with a very simple component 'errmsg' in Eclipse.

The errmsg component will display specified msg in error style and store the specified description.

After this article, you can also refer to Index Page of ZK CDT (Component Development Tutorial) Walkthrough to see more advanced articles.

The Steps

Step 1

This is starts from http://ben-bai.blogspot.tw/2012/06/zk-quick-start.html, a simple Dynamic Web Project with basic ZK jars.

Step 2

Some files should at the pre-defined location,
where lang-addon.xml should under classpath/metainfo/zk,
and the widget js/css files should under classpath/web/js

You can simply create a folder under project root and prepare the folders described above,
then edit the Project -> Properties to add it as Source folder



Finally the project structure are as below:



where the folders under CustomComponentsResources will be copied to classpath after compile

Step 3

Prepare the lang-addon.xml to define this component,
please refer to the inline-document

lang-addon.xml

<!-- ZK will load this file
    (classpath/metainfo/zk/lang-addon.xml) automatically -->

<language-addon>
    <!-- addon name, 
        required, it's better to be unique -->
    <addon-name>custom</addon-name>
    <!-- specifies the name of a language definition
        should be unique in a language definition,
        xul/html is predefinied language in ZK
        so can be used in lang-addon directly -->
    <language-name>xul/html</language-name>

    <!-- version
        optional,
        if the version specified in version-class is not
        the same as version-uid, or the real ZK version is smaller
        then zk-version, the addon will be ignored -->
    <version>
        <version-class>custom.zk.Version</version-class>
        <version-uid>0.0.1</version-uid>
        <zk-version>5.0.0</zk-version><!-- or later -->
    </version>

    <!-- define a component -->
    <component>
        <!-- the tag name of this component
            required,
            must be unique -->
        <component-name>errmsg</component-name>
        <!-- fully-qualified java class name at server side
            required for a new component that not extends another component -->
        <component-class>custom.zk.components.Errmsg</component-class>
        <!-- widget class, 'custom.zk.components.Errmsg'
            also specify the
            package of widget class 'custom.zk.components'
            required for a new component that not extends another component

            from my test,
            when you say a widget class is custom.zk.components.Errmsg
            it denotes there should have a file
            web/js/custom/zk/components/Errmsg.js under classpath,
            and have "<package name="custom.zk.components" ...>...</package>"
            in web/js/custom/zk/components/zk.wpd
            and "custom.zk.components.Errmsg = ..."
            in Errmsg.js to define that client widget
            
            You may try play around to find whether you can
            make them different, but it's better to let them
            sync even if you can make them different -->
        <widget-class>custom.zk.components.Errmsg</widget-class>
        <!-- mold
            required for a new component that not extends another component
            or has self widget-class
            
            a mold denotes the files that to render and style this comopnent -->
        <mold>
            <!-- default mold is required -->
            <mold-name>default</mold-name>
            <!-- relative path based on widget-class' path
                (web/js/custom/zk/components/)
                
                where errmsg.js (required) contains a function that
                will render the html of the comopnent,
                errmsg.css.dsp (optional) contains
                css styles of this component -->
            <mold-uri>mold/errmsg.js</mold-uri>
            <css-uri>css/errmsg.css.dsp</css-uri>
        </mold>
    </component>
</language-addon>


Step 4

Prepare the widget js file for this component

Errmsg.js

//error message widget class
//this file will be loaded because
//the widget-class --  'custom.zk.components.Errmsg'
//specified in lang-addon.xml and zk.wpd
custom.zk.components.Errmsg = zk.$extends(zul.Widget, {
//    block for define attributes
//    where 'anAttr: null' in this block will
//    generate 3 items in this widget
//    1. this._anAttr
//    2. this.setAnAttr(value)
//    3. this.getAnAttr(value)
    
//    'anAttr: function (v)' will also generate the 3 items above,
//    the function will be called by setter,
//    i.e., setAnAttr(value) {
//        ...
//        function(v)
//        ...
//    }
//    so you can specify what you want to do while the setter is called

//    for more information, please refer to
//    http://www.zkoss.org/javadoc/latest/jsdoc/_global_/zk.html#$extends(zk.Class, _global_.Map, _global_.Map)
//    http://www.zkoss.org/javadoc/latest/jsdoc/_global_/zk.html#define(zk.Class, _global_.Map)
    $define: {
        msg: function (v) {
            var n;
            if (v
                && (n = this.$n()))
                n.innerHTML = v;
        },
        description: null
    },
    // function to provide css class
    getZclass: function () {
        var zcls = this._zclass;
        return zcls? zcls : 'z-errmsg';
    }
});


Step 5

Prepare the renderer js file / css style file for default mold

errmsg.js

//error message html renderer
//this file will be loaded because
//the package of widget-class --  'custom.zk.components'
//and the mold-uri of mold -- 'mold/errmsg.js'
//specified in lang-addon.xml
function (out) {
    // the rendered msg
    var msg = this._msg;

    // this.uuid is the default attribute that
    // will assigned by ZK framework
    
    // this.getZclass() is overridden in Errmsg.js
    
    // after this line, the tmp result is
    // <span id="xxxxx" class="z-errmsg">
    out.push('<span id="', this.uuid,
            '" class="', this.getZclass(), '">');

    // output msg if exists
    if (msg)
        out.push(msg);
    // output end tag of span
    out.push('</span>');

    // finally, the result will be
    // <span id="xxxxx" class="z-errmsg">some message</span>
    // or
    // <span id="xxxxx" class="z-errmsg"></span>
}


errmsg.css.dsp

/* error message style */
/* this file will be loaded because */
/* the package of widget-class 'custom.zk.components' */
/* and the css-uri of mold 'css/errmsg.css.dsp' */
/* specified in lang-addon.xml */
.z-errmsg {
    font-weight: bold;
    font-size: 22px;
    color: red;
}


Step 6

Prepare zk.wpd to config widget's loading

zk.wpd

<!-- this file define the package name and the order to load widgets -->
<!-- depends="zul" denotes this package should -->
<!-- be loaded after the zul package, -->
<!-- i.e., if you load this package but the zul package is not loaded, -->
<!-- the framework will load zul package first automatically -->
<package name="custom.zk.components" language="xul/html" depends="zul">
    <widget name="Errmsg"/>
</package>


Step 7

Prepare the Version class to specify version of this addon and component class.

Version.java

package custom.zk;

// the Versio file that version UID will be compared
// with the version-uid specified in lang-addon.xml
public class Version {
    /** Returns the version UID.
     */
    public static final String UID = "0.0.1";
}


Errmsg.java


package custom.zk.components;


import org.zkoss.zul.impl.XulElement;
// this file will be loaded because the
// <component-class>custom.zk.components.Errmsg</component-class>
// specified in lang-addon.xml
public class Errmsg extends XulElement {
    private String _msg;
    private String _description;

    public void setMsg (String msg) {
        _msg = msg;
        // this will call client side widget's setMsg(_msg)
        smartUpdate("msg", _msg);
    }
    public String getMsg () {
        return _msg;
    }
    public void setDescription (String description) {
        _description = description;
        // this will call client side widget's setDescription(_description)
        smartUpdate("description", _description);
    }
    public String getDescription () {
        return _description;
    }
    public String getZclass() {
        return _zclass == null ? "z-errmsg" : _zclass;
    }
    //-- ComponentCtrl --//
//    the renderProperties will be called by framework automatically,
//    remember to render super's properties first
    protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer)
    throws java.io.IOException {
        super.renderProperties(renderer);
        // this will call client side widget's setMsg(_msg)
        if (_msg != null)
            render(renderer, "msg", _msg);
        // this will call client side widget's setDescription(_description)
        if (_description != null)
            render(renderer, "description", _description);
    }
}


Step 8

Finally use a zul file to test it

errmsg_component_test.zul


<zk>
<!--     an errmsg thst display msg and will -->
<!--     alert its discription while clicked -->

<!--     the id attribute is specified in the super class -->
<!--     the onClick event also listened by super class -->
<!--     we can just simply use them -->
    <errmsg id="errMsg" msg="error"
        description="this is a test message to test component errmsg"
        onClick="alert(self.getDescription());" />
<!--     use a ZK button to change errmsg's property -->
<!--     the 'errMsg' below is an errmsg instance that has id 'errMsg' -->
    <button label="test">
        <attribute name="onClick">
            errMsg.setMsg(" new message");
            errMsg.setDescription(" new description");
        </attribute>
    </button>
</zk>


Export component(s) to a jar

Apply following steps to export component(s) to a jar file

File -> Export



Select Jar file -> Next



Select CustomComponentsResources and src, specify jar location then click Finish



Finally you can use the component in other ZK Project with the generated jar file.

Result

See demo on line
http://screencast.com/t/3LUWQOdn

Download

Full project:
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Components_Development

Demo:
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/component_development/zk_custom_component__errmsg_demo.swf

Exported jar:
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Development/target/ZKCustomComponents.jar


References:

http://www.zkoss.org/javadoc/latest/jsdoc/_global_/zk.html#$extends(zk.Class, _global_.Map, _global_.Map)

http://www.zkoss.org/javadoc/latest/jsdoc/_global_/zk.html#define(zk.Class, _global_.Map)

http://books.zkoss.org/wiki/ZK_Client-side_Reference/Language_Definition

11 comments:

  1. its not very clear how to create component and neither how to use it!
    its not a good pratice doing tutorials with something already made, we suposed to learn all those steps needed for creating components, if you put it all together is much like that maven or maybe ruby tutorials, you just hit a button, run a Ant Script and doesn't know anything about whats going on behind scenes!
    its not a good way of learning new technologies.

    ReplyDelete
    Replies
    1. Thanks for your opinion, but these are already the fewest steps to create the most basic part of a ZK Component.

      By the way, most of the explanation is in the comment ( // ... or

      Delete
  2. Hello! I want to create my own component but no matter how i do it, I keep getting the Processing... screen, and that's it.
    I am using zk7 with Tomcat8 and java8u60

    ReplyDelete
    Replies
    1. I would like to know why this keeps happening. If you require more information, please let me know

      Delete
    2. This kind of issue is usually caused by js error, you can press F12 to open Developer Tools and check whether anything went wrong from console in modern browsers (e.g., Chrome or Firefox).

      Delete
    3. The debugger shows no errors, only warnings that do not appear to influence the page.

      Delete
    4. Is the JS files correctly loaded? For example, assume their is a bind_ function then you can try add "console.log('test')" to see whether it output 'test' to browser console.

      Delete
    5. Before i answer that (I've been away for about 3 days), I wanted to test something: I re-downloaded eclipse, I put it in a new folder, created a new workspace, installed zk studio and tried the whole thing again. The result is quite confusing. I get two different problems: the first one is the one already mentioned and the new one: I get com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: Invalid byte 1 of 1-byte UTF-8 sequence. I validated and revalidated my lang-addon.xml and it's fine. It's getting quite frustrating.

      As for the console.log() part, now i get these two errors:
      1) TypeError: com.test.zul is undefined
      2)uncaught exception: Unknown widget: com.test.zk.Stamp

      I don't understand the part with "com.test.zul", as i don't remember writing this and nor do i see it in my project. Really confused right about now

      Delete
    6. You can also try to follow official document to create component. If it still not work, is it possible that upload your project somewhere (e.g., github) for investigation?

      The official document: http://books.zkoss.org/wiki/ZK_Component_Development_Essentials

      By the way, you can Search -> Files to find com.test.zul in your project, probably ZK Studio generated something automatically for you.

      Delete