Saturday, June 1, 2013

ZK CDT: RenderableTextNote: Render Note Blocks with Server Side Data


Introduction

This is the 4th article of ZK CDT (ZK Component Development Tutorial) walkthrough, this article describe how to render dom elements at client side with server side data.

Again attributes and extends.

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 4th part: Render client side element with server side data

Result

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

As you can see you can update note blocks by server now.

Note: the 'index to update' is the index of data object in model to update, only used to update data when you click 'update' button.

Pre-request

ZK CDT: Handle Client Side Event to Create Simple Text Note
http://ben-bai.blogspot.tw/2013/06/zk-cdt-handling-client-side-event-to.html

ZK Basic MVVM Pattern
http://ben-bai.blogspot.tw/2012/12/zk-basic-mvvm-pattern.html

Program

renderabletextnote.zul

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

<zk>
    <div apply="org.zkoss.bind.BindComposer"
        viewModel="@id('vm') @init('custom.zk.samples.quicknote.RenderableTextNoteVM')">
        <hlayout>
            <!-- The renderabletextnote component that will cover
                its children by a mask
                you can click on the mask to add a textarea
                and type text in it
            -->
            <renderabletextnote width="700px" id="stn"
                opacity="20" maskColor="#00FF00"
                model="@load(vm.model)">
                <button label="ZK Website" />
                <iframe width="100%"
                    height="1000px"
                    src="http://www.zkoss.org/"></iframe>
            </renderabletextnote>
            <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: <intbox value="@save(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 renderabletextnote -->
                    <slider curpos="20" maxpos="100">
                        <attribute name="onScroll"><![CDATA[
                            stn.setOpacity(((ScrollEvent)event).getPos());
                        ]]></attribute>
                        <attribute name="onScrolling"><![CDATA[
                            stn.setOpacity(((ScrollEvent)event).getPos());
                        ]]></attribute>
                    </slider>
                    <!-- colorbox used to control mask color of renderabletextnote -->
                    <colorbox color="#00FF00">
                        <attribute name="onChange"><![CDATA[
                            stn.setMaskColor(self.getValue());
                        ]]></attribute>
                    </colorbox>
                </hlayout>
            </vlayout>
        </hlayout>
    </div>
</zk>


RenderableTextNoteVM.java

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

package custom.zk.samples.quicknote;

import java.util.ArrayList;
import java.util.List;

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

import custom.zk.components.quicknote.Data.TextNoteData;
import custom.zk.components.quicknote.model.TextNoteModel;

/** VM used in renderabletextnote.zul
 * 
 * @author benbai123
 *
 */
public class RenderableTextNoteVM {
    private TextNoteModel _model;
    private TextNoteData _textNoteDataToUpdate;
    private int _indexToUpdate = -1;
    // getters, setters
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public TextNoteModel getModel () {
        if (_model == null) {
            List l = new ArrayList();
            l.add(new TextNoteData(5, 5, 100, 35, "test"));
            l.add(new TextNoteData(55, 55, 150, 50, "test 2"));
            l.add(new TextNoteData(105, 105, 75, 25, "test 3"));
            _model = new TextNoteModel(l);
        }
        return _model;
    }
    public TextNoteData getTextNoteData () {
        if (_textNoteDataToUpdate == null) {
            _textNoteDataToUpdate = new TextNoteData(0, 0, 0, 0, "");
        }
        return _textNoteDataToUpdate;
    }
    public int getIndexToUpdate () {
        return _indexToUpdate;
    }
    public void setIndexToUpdate (int indexToUpdate) {
        _indexToUpdate = indexToUpdate;
    }
    // add note block then update model to client
    @Command
    @NotifyChange ("model")
    public void addNoteBlock () {
        _model.add(new TextNoteData(_textNoteDataToUpdate));
    }
    // update note block then update model to client
    @Command
    @NotifyChange ("model")
    public void updateNoteBlock () {
        _model.update(_indexToUpdate, new TextNoteData(_textNoteDataToUpdate));
    }
    // clear note blocks then update model to client
    @Command
    @NotifyChange ("model")
    public void clearAllBlocks () {
        _model.clear();
    }
}


RenderableTextNote.java

Java class of RenderableTextNote component, extends enhancedmask and handle model rendering.

package custom.zk.components.quicknote;

import java.util.List;

import org.zkoss.json.JSONArray;
import custom.zk.components.quicknote.Data.TextNoteData;
import custom.zk.components.quicknote.model.TextNoteModel;

/** RenderableTextNote, can render text note block with server side data
 * 
 * @author benbai123
 *
 */
public class RenderableTextNote extends EnhancedMask {

    private static final long serialVersionUID = -6824067586007799573L;

    /** data model that contains information of text note blocks
     * 
     */
    private TextNoteModel _model;
    /**
     * setter
     * @param model
     */
    public void setModel (TextNoteModel model) {
        _model = model;
        updateNoteBlocks();
    }
    /**
     * getter
     * @return
     */
    public TextNoteModel getModel () {
        return _model;
    }
    /**
     * used to update text note blocks at client side
     */
    public void updateNoteBlocks () {
        String blockToRender = getRenderedTextNoteData();
        if (blockToRender == null) {
            blockToRender = "";
        }
        // update client note blocks while setter is called
        smartUpdate("noteBlocks", blockToRender);
    }
    /**
     * create a json string used to update text note blocks at client side
     * @return
     */
    @SuppressWarnings("rawtypes")
    public String getRenderedTextNoteData () {
        if (_model != null
            && _model.getTextNoteData() != null
            && _model.getTextNoteData().size() > 0) {
            // json arrays of
            // left, top, width, height and text of note blocks
            JSONArray jsArr = new JSONArray();
            JSONArray jsXposArr = new JSONArray(); // left
            JSONArray jsYposArr = new JSONArray(); // top
            JSONArray jsWidthArr = new JSONArray(); // width
            JSONArray jsHeightArr = new JSONArray(); // height
            JSONArray jsTextArr = new JSONArray(); // text
            // get all data
            List datas = _model.getTextNoteData();
            // put each data into json array accordingly
            for (int i = 0; i < datas.size(); i++) {
                TextNoteData data = (TextNoteData)datas.get(i);
                jsXposArr.add(data.getPosX());
                jsYposArr.add(data.getPosY());
                jsWidthArr.add(data.getWidth());
                jsHeightArr.add(data.getHeight());
                jsTextArr.add(data.getText());
            }
            // put each data array into another json array
            // i.e., will be a 2-D array at client side
            jsArr.add(jsXposArr);
            jsArr.add(jsYposArr);
            jsArr.add(jsWidthArr);
            jsArr.add(jsHeightArr);
            jsArr.add(jsTextArr);
            return jsArr.toJSONString();
        } else {
            return null;
        }
    }
    // render noteBlocks as needed
    protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer)
        throws java.io.IOException {
        super.renderProperties(renderer);
        String blockToRender = getRenderedTextNoteData();
        if (blockToRender != null) {
            render(renderer, "noteBlocks", blockToRender);
        }
    }
}


RenderableTextNote.js

Widget class of RenderableTextNote component, extends SimpleTextNote and handle model rendering.

/**
 * Widget class of RenderableTextNote component,
 * extends custom.zk.components.quicknote.SimpleTextNote,
 * handle update command (setNoteBlocks) to render note blocks
 * with the data updated from server
 * 
 * one new thing:
 * bind_: Callback when this widget is bound (aka., attached) to the DOM tree.
 * 
 * 
 */
custom.zk.components.quicknote.RenderableTextNote = zk.$extends(custom.zk.components.quicknote.SimpleTextNote, {
    _noteBlocks: null,
    /** setter for noteBLocks,
     * will render note blocks if dom element exists
     * 
     * @param noteBlocks information of note blocks from server
     */
    setNoteBlocks: function (noteBlocks) {
        this._noteBlocks = noteBlocks;
        if (this.$n()) {
            // dom exists, render note blocks
            this._renderNoteBlocks();
        }
    },
    // doms are ready
    bind_: function (desktop, skipper, after) {
        // call super
        this.$supers(custom.zk.components.quicknote.RenderableTextNote, 'bind_', arguments);
        // render note blocks if any
        this._renderNoteBlocks();
    },
    // render note blocks
    _renderNoteBlocks: function () {
        var noteBlocks = this._noteBlocks;

        // clear all old note blocks
        // so we do not need to care about the mapping (keep, rerender, add, etc)
        jq(this.$n()).find('.' + this.getZclass() + '-noteblock')
            .each(function () {
                // 'this' is each block here
                this.parentNode.removeChild(this);
            });

        // has note blocks
        if (noteBlocks) {
            var datas = jq.evalJSON(noteBlocks), // eval to get a 2-D array
                x = datas[0], // left array
                y = datas[1], // top array
                w = datas[2], // width array
                h = datas[3], // height array
                text = datas[4], // text array
                len = x.length, // amount of note blocks
                idx = 0; // index

            // render each note block
            for ( ; idx < len; idx++) {
                this._renderNoteBlock(x[idx], y[idx], w[idx], h[idx], text[idx]);
            }
        }
    },
    // create dom element of note block
    _renderNoteBlock: function (x, y, w, h, txt) {
        // note block created by _createNoteBlock defined in SimpleTextNote
        var noteBlock = this._createNoteBlock(x, y),
            textArea = noteBlock.firstChild;
        // add width and height, insert text
        jq(textArea).css({'width': w+'px',
                        'height': h+'px'});
        textArea.innerHTML = txt;
        // add note block under root element of widget
        this.$n().appendChild(noteBlock);
    }
});


TextNoteData.java

Java class that represent the attributes of textarea in a note block.

package custom.zk.components.quicknote.Data;

/** Java bean that represent a text note block
 * 
 * @author benbai123
 *
 */
public class TextNoteData {
    private int _posX; // left
    private int _posY; // top
    private int _width; // width
    private int _height; // height
    private String _text; // text

    // constructor that construct with each attributes
    public TextNoteData (int posX, int posY, int width, int height, String text) {
        _posX = posX;
        _posY = posY;
        _width = width;
        _height = height;
        _text = text;
    }
    // constructor that construct with another data bean
    public TextNoteData (TextNoteData dataToCopy) {
        _posX = dataToCopy.getPosX();
        _posY = dataToCopy.getPosY();
        _width = dataToCopy.getWidth();
        _height = dataToCopy.getHeight();
        _text = dataToCopy.getText();
    }
    // setters, getters
    public void setPosX (int posX) {
        _posX = posX;
    }
    public int getPosX () {
        return _posX;
    }
    public void setPosY (int posY) {
        _posY = posY;
    }
    public int getPosY () {
        return _posY;
    }
    public void setWidth (int width) {
        _width = width;
    }
    public int getWidth () {
        return _width;
    }
    public void setHeight (int height) {
        _height = height;
    }
    public int getHeight () {
        return _height;
    }
    public void setText (String text) {
        _text = text;
    }
    public String getText () {
        return _text;
    }
}


TextNoteModel.java

Java class that contains a list of TextNoteData, provide add/insert/remove/update APIs.

package custom.zk.components.quicknote.model;

import java.util.ArrayList;
import java.util.List;

import custom.zk.components.quicknote.Data.TextNoteData;

/** basic model to hold text note block datas
 * 
 * @author benbai123
 *
 */
@SuppressWarnings({"unchecked", "rawtypes"})
public class TextNoteModel {
    // text note block data list
    private List _textNodeDatas;

    // constructor
    public TextNoteModel (List textNodeDatas) {
        if (textNodeDatas == null) {
            textNodeDatas = new ArrayList();
        }
        _textNodeDatas = textNodeDatas;
    }
    // add
    public void add (TextNoteData data) {
        _textNodeDatas.add(data);
    }
    // add at specific position
    public void add (int index, TextNoteData data) {
        _textNodeDatas.add(index, data);
    }
    // remove
    public void remove (TextNoteData data) {
        _textNodeDatas.remove(data);
    }
    // update specific data (by position)
    public void update (int index, TextNoteData data) {
        _textNodeDatas.remove(index);
        add(index, data);
    }
    // return data list
    public List getTextNoteData () {
        return _textNodeDatas;
    }
    // clear all data
    public void clear () {
        _textNodeDatas.clear();
    }
}


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="RenderableTextNote" />
    ...


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

    ...
    <!-- 4th, renderabletextnote component
        extends simpletextnote and handle data rendering with model
        
        reuse css style,
        extends java and widget classes,
        add functions to handle model rendering
     -->
     <component>
        <component-name>renderabletextnote</component-name>
        <extends>simpletextnote</extends>
        <component-class>custom.zk.components.quicknote.RenderableTextNote</component-class>
        <widget-class>custom.zk.components.quicknote.RenderableTextNote</widget-class>
    </component>
    ...


References

eval json
http://www.zkoss.org/javadoc/latest/jsdoc/_global_/jq.html#evalJSON(_global_.String)

bind_
http://www.zkoss.org/javadoc/latest/jsdoc/zk/Widget.html#bind_(zk.Desktop, zk.Skipper, _global_.Array)

Download

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

renderabletextnote_component.swf

No comments:

Post a Comment