Saturday, June 1, 2013

ZK CDT: Fire Event to Server to Create a SelectableTextNote


Introduction

This is the 5th article of ZK CDT (ZK Component Development Tutorial) walkthrough, this article describe how to fire event to bring client data to server side.

Fire event to server.

The goal of this walkthrough is to create a quicknote component that help you do some quick note on web page (similar to you crop some screenshot, highlight something and add some text in photo editor), and describe each part of a component through each article in this walkthrough.

This is the 5th part: Fire event to server at client side.

Result

View demo online:
http://screencast.com/t/cDqjn5k3hU

As you can see, now you can update the selected note block by clicking on it instead of type index in intbox.
Also the selected note block will become the top most one.

Pre-request

ZK CDT: RenderableTextNote: Render Note Blocks with Server Side Data
http://ben-bai.blogspot.tw/2013/06/zk-cdt-renderabletextnote-render-note.html

Program

selectabletextnote.zul

Contains a selectabletextnote component, a control block that used to add/update/clear note blocks, a slider/colorbox that control the opacity/background-color of the selectabletextnote.

<zk>
    <!-- two new things
    
        selectedTextNoteIndex="@save(vm.indexToUpdate)":
        that will save selectedTextNoteIndex of selectabletextnote to vm
        chek the component definition in lang-addon.xml to see
        how it can work
        
        control of opacity and maskColor are moved into VM
     -->
    <div apply="org.zkoss.bind.BindComposer"
        viewModel="@id('vm') @init('custom.zk.samples.quicknote.SelectableTextNoteVM')">
        <hlayout>
            <!-- The selectabletextnote component that will cover
                its children by a mask
                you can click on the mask to add a textarea
                and type text in it
            -->
            <selectabletextnote width="700px" id="stn"
                opacity="@load(vm.opacity)" maskColor="@load(vm.maskColor)"
                model="@load(vm.model)"
                selectedTextNoteIndex="@save(vm.indexToUpdate)">
                <button label="ZK Website" />
                <iframe width="100%"
                    height="1000px"
                    src="http://www.zkoss.org/"></iframe>
            </selectabletextnote>
            <vlayout>
                <!-- controll block for add/update/clear note blocks -->
                <vlayout>
                    <hlayout>
                        x: <intbox value="@bind(vm.textNoteData.posX)" />
                    </hlayout>
                    <hlayout>
                        y: <intbox value="@bind(vm.textNoteData.posY)" />
                    </hlayout>
                    <hlayout>
                        width: <intbox value="@bind(vm.textNoteData.width)" />
                    </hlayout>
                    <hlayout>
                        height: <intbox value="@bind(vm.textNoteData.height)" />
                    </hlayout>
                    <hlayout>
                        text: <textbox value="@bind(vm.textNoteData.text)" />
                    </hlayout> 
                    <hlayout>
                        <button label="add" onClick="@command('addNoteBlock')" />
                    </hlayout>
                    <hlayout>
                        index to update: <label value="@load(vm.indexToUpdate)" />
                        <button label="update" onClick="@command('updateNoteBlock')" />
                    </hlayout>
                    <hlayout>
                        <button label="clear" onClick="@command('clearAllBlocks')" />
                    </hlayout>
                </vlayout>
                <hlayout>
                    <!-- slider used to control opacity of selectabletextnote -->
                    <slider curpos="@bind(vm.opacity)" maxpos="100"
                        onScroll="@command('updateOpacity')" />
                    <!-- colorbox used to control mask color of selectabletextnote -->
                    <colorbox color="@bind(vm.maskColor)"
                        onChange="@command('updateMaskColor')" />
                </hlayout>
            </vlayout>
        </hlayout>
    </div>
</zk>


SelectableTextNoteVM.java

VM used in selectabletextnote.zul, provide data, do command and update data.

package custom.zk.samples.quicknote;

import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.NotifyChange;

/** VM used in selectabletextnote.zul,
 * extends RenderableTextNoteVM then simply
 * define a member field and its getter/setter
 * 
 * @author benbai123
 *
 */
public class SelectableTextNoteVM extends RenderableTextNoteVM {

    private int _opacity = 20;
    private String _maskColor = "#00FF00";
    // getters/setters

    public int getOpacity () {
        return _opacity;
    }
    public void setOpacity (int opacity) {
        _opacity = opacity;
    }

    public String getMaskColor () {
        return _maskColor;
    }
    public void setMaskColor (String maskColor) {
        _maskColor = maskColor;
    }
    // commands
    @Command
    @NotifyChange("opacity")
    public void updateOpacity () {
        // do nothing, just for trigger NotifyChange
    }
    @Command
    @NotifyChange("maskCOlor")
    public void updateMaskColor () {
        // do nothing, just for trigger NotifyChange
    }
}


SelectableTextNote.java

Java class of SelectableTextNote component, extends RenderableTextNote and handle note block selection.

package custom.zk.components.quicknote;

import java.util.Map;

import org.zkoss.zk.ui.event.Events;

/**
 * SelectableTextNote, will receive and store which note block is selected from client side action
 * 
 * Three new things:
 * 
 * addClientEvent: add an event that the client might send to server with specific settings,
 *         refer to http://www.zkoss.org/javadoc/latest/zk/org/zkoss/zk/ui/AbstractComponent.html#addClientEvent(java.lang.Class, java.lang.String, int)
 * 
 * service: Handles an AU request. It is invoked internally.
 *         refer to http://www.zkoss.org/javadoc/latest/zk/org/zkoss/zk/ui/AbstractComponent.html#service(org.zkoss.zk.au.AuRequest, boolean)
 *
 * postEvent: post an event to self instance so the composer can be notified
 *             this is also required for save data with MVVM
 *             refer to http://books.zkoss.org/wiki/ZK_Developer's_Reference/Event_Handling/Event_Firing
 * 
 * @author benbai123
 *
 */
public class SelectableTextNote extends RenderableTextNote {

    private static final long serialVersionUID = -6589891861074953359L;

    private int _selectedTextNoteIndex = -1;

    static {
        /* CE_IMPORTANT: always fire it to server,
         *             without this flag, the au engine will only fire event to server
         *             if and only if there is an EventListener listen to this event
         * 
         * CE_DUPLICATE_IGNORE: ignore multiple event in an au request
         * 
         * CE_NON_DEFERRABLE: always fire it immediately,
         *             without this flag, au engine will queue this event at client side
         *             and fire it to server with other non-defferrable event
         */
        addClientEvent(SelectableTextNote.class, "onTextNoteBlockSelect", CE_IMPORTANT | CE_DUPLICATE_IGNORE | CE_NON_DEFERRABLE);
    }

    // setter/getter
    public void setSelectedTextNoteIndex (int selectedTextNoteIndex) {
        _selectedTextNoteIndex = selectedTextNoteIndex;
    }
    public int getSelectedTextNoteIndex () {
        return _selectedTextNoteIndex;
    }

    // process client event
    public void service(org.zkoss.zk.au.AuRequest request, boolean everError) {
        final String cmd = request.getCommand();
        if (cmd.equals("onTextNoteBlockSelect")) {
            Map data = request.getData(); // get data map
            // get index by the key "index" since
            // we define the data as {index: idx}
            // while firing onTextNoteBlockSelect event
            // in SelectableTextNote.js
            Integer index = (Integer)data.get("index");
            // store value
            _selectedTextNoteIndex = index;
            // post event to trigger listeners if any
            Events.postEvent("onTextNoteBlockSelect", this, data);
        } else 
            super.service(request, everError);
    }
}


SelectableTextNote.js

Widget class of SelectableTextNote component, extends RenderableTextNote and handle click event of textarea in text note block.

/**
 * Widget class of SelectableTextNote component,
 * extends custom.zk.components.quicknote.RenderableTextNote
 * 
 * One new thing:
 * fire: fire event to server,
 *         widget.fire('EVENT_NAME', {DATA}, {OPTIONS}, TIMEOUT);
 *         refer to http://www.zkoss.org/javadoc/latest/jsdoc/zk/Widget.html#fire(_global_.String, zk.Object, _global_.Map, int)
 * 
 */
custom.zk.components.quicknote.SelectableTextNote = zk.$extends(custom.zk.components.quicknote.RenderableTextNote, {
    _selectedTextNoteIndex: -1,
    // called while onclick of any dom elements
    // under root element is triggered
    doClick_: function (evt) {
        // call super at first
        this.$supers('doClick_', arguments);
        var target = evt.domTarget;
        // clicked in textarea in text note block
        if (jq(target).hasClass(this.getZclass() + '-noteblock-textarea')) {
            this._doTextNoteBlockClick(evt);
        }
    },
    /** processing onclick of textarea in note block
     * pass event into this function (instead of just pass target)
     * since we probably need some information (e.g., pageX/Y, etc) in
     * the future
     * 
     * @param evt
     */
    _doTextNoteBlockClick: function (evt) {
        // cls: css class of textarea within text note block
        // target: the clicked dom element
        var scls = this.getZclass() + '-noteblock-selected',
            target = evt.domTarget,
            idx;

        // clear selected class of old selected block
        jq('.' + scls).each(function () {
            jq(this).removeClass(scls);
        });
        // add class to make it become top most note block
        jq(target.parentNode).addClass(scls);
        // fire event to update index to server side
        if ((idx = this.getTextBlockIndex(target)) >= 0) {
            this._selectedTextNoteIndex = idx;
            this.fire('onTextNoteBlockSelect', {index: idx});
        }
    },
    getTextBlockIndex: function (textarea) {
        var cls = this.getZclass() + '-noteblock-textarea';
        // current: a copy of text note block for while loop
        // idx: index of current text note block
        var current = this.$n('mask').nextSibling,
            idx = 0;

        // for each text note block
        while (current) {
            // found clicked block
            if (jq(current).find('.'+cls)[0] == textarea) {
                // return index
                return idx;
            }
            current = current.nextSibling;
            idx++;
        }
        return -1;
    },
    // override with new css class name
    getZclass: function () {
        var zcls = this._zclass;
        return zcls? zcls : 'z-selectabletextnote';
    }
});


selectableTextNote.css.dsp

CSS classes for SelectableTextNote component

<%--// ------------------------------------------- --%>
<%--//                                             --%>
<%--//            SelectableTextNote component           --%>
<%--//                                             --%>
<%--// ------------------------------------------- --%>
<%--// root element --%>
.z-simpletextnote {
    <%--// be the anchor of absolute positioned children --%>
    position: relative;
    overflow: hidden;
}
<%--// the mask that cover whole element --%>
<%--// no background-color and opacity specified --%>
<%--// since we specified them in widget class --%>
.z-selectabletextnote-cover {
    <%--// absoluted positioned --%>
    position: absolute;
    <%--// align the left-top corner of parent (root) element --%>
    left: 0px;
    top: 0px;
    <%--// cover whole root element --%>
    height: 100%;
    width: 100%;
    <%--// make it the top most element under root element --%>
    z-index: 99999;
}

.z-selectabletextnote .z-selectabletextnote-noteblock {
    <%--// absoluted positioned --%>
    position: absolute;
    <%--// in front of mask --%>
    z-index: 999999;
}

.z-selectabletextnote .z-selectabletextnote-noteblock-textarea {
    <%--// h/v resizable --%>
    resize: both;
    <%--// default width and height --%>
    <%--// NOTE: the specified value of width/height will  --%>
    <%--// be the minimum value, you cannot shrink textarea --%>
    <%--// smaller than these values (at least on chrome) --%>
    width: 50px;
    height: 30px;
}
.z-selectabletextnote .z-selectabletextnote-noteblock-selected {
    <%--// in front other text note blocks --%>
    z-index: 1000000;
}


zk.wpd

Define components under "custom.zk.components.quicknote"

* only the added part, not the full code

NOTE: more components will be added with other articles later

    ...

    <widget name="SelectableTextNote" />

    ...


lang-addon.xml

Define all components in the project

* only the added part, not the full code

NOTE: more components will be added with other articles later

    ...

    <!-- 5th, selectabletextnote component
        extends renderabletextnote,
        update zclass, handle click event of
        textareas and fire event to server
        to update the selected note block index

        one new thing, the 'annotation' block that define
        how an attribute (selectedTextNoteIndex here) works with ZKBIND
        
        as you can see opacity and maskColor are not defined here
        since they only require the 'load' direction
        the 'load' direction is supported by default so
        you just need to provide setter (in component) and getter (in VM) properly
        
        refer to:
        Document: http://books.zkoss.org/wiki/ZK_Developer's_Reference/MVVM/Advanced/Binding_Annotation_for_a_Custom_Component
        Code: https://github.com/zkoss/zk/blob/master/zkbind/src/archive/metainfo/zk/lang-addon.xml
     -->
     <component>
        <component-name>selectabletextnote</component-name>
        <extends>renderabletextnote</extends>
        <component-class>custom.zk.components.quicknote.SelectableTextNote</component-class>
        <widget-class>custom.zk.components.quicknote.SelectableTextNote</widget-class>
        <mold>
            <mold-name>default</mold-name>
            <css-uri>css/selectableTextNote.css.dsp</css-uri>
        </mold>
        <annotation>
            <!-- ZKBIND is the zkbind system annotation -->
            <annotation-name>ZKBIND</annotation-name>
            <!-- property name -->
            <property-name>selectedTextNoteIndex</property-name>
            <attribute>
                <!-- ACCESS is the access direction:
                    can be "both", "save", "load";
                    default to "load" if not found -->
                <attribute-name>ACCESS</attribute-name>
                <attribute-value>both</attribute-value>
            </attribute>
            <attribute>
                <!-- SAVE_EVENT is the save trigger event;
                    meaningful only when ACCESS is "both" or "save" -->
                <attribute-name>SAVE_EVENT</attribute-name>
                <attribute-value>onTextNoteBlockSelect</attribute-value>
            </attribute>
            <attribute>
                <!-- LOAD_TYPE is the type of attribute for loading -->
                <attribute-name>LOAD_TYPE</attribute-name>
                <attribute-value>java.lang.Integer</attribute-value>
            </attribute>
        </annotation>
    </component>

    ...


References

addClientEvent java method
http://www.zkoss.org/javadoc/latest/zk/org/zkoss/zk/ui/AbstractComponent.html#addClientEvent(java.lang.Class, java.lang.String, int)

service java method
http://www.zkoss.org/javadoc/latest/zk/org/zkoss/zk/ui/AbstractComponent.html#service(org.zkoss.zk.au.AuRequest, boolean)

postEvent java method
http://books.zkoss.org/wiki/ZK_Developer's_Reference/Event_Handling/Event_Firing

fire client widget method
http://www.zkoss.org/javadoc/latest/jsdoc/zk/Widget.html#fire(_global_.String, zk.Object, _global_.Map, int)

ZKBIND annotation
Document: 
http://books.zkoss.org/wiki/ZK_Developer's_Reference/MVVM/Advanced/Binding_Annotation_for_a_Custom_Component
Code:
https://github.com/zkoss/zk/blob/master/zkbind/src/archive/metainfo/zk/lang-addon.xml


Download

Full project at github
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Components_Development__Series/001_walkthrough/ZKQuickNote

selectabletextnote_component.swf
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/component_development_series/001_walkthrough/selectabletextnote_component.swf

No comments:

Post a Comment