Saturday, April 28, 2012

ZK: Make unfocusable component focusable and support keystroke


Introduction

Originally, the control keys (and other keystroke) only work with focusable comopnents such like textbox, combobox and listbox, the onCtrlKey event is triggered by keydown event if they have the focus.

The unfocusable components can not handle the onCtrlKey event properly since they can not get the focus.

This post is about how to make an unfocusable component such like div or label focusable and
can handle the onCtrlKey event properly.

The Program

make_unfocusable_component_focusable_and_handle_ctrlKeys.zul

<zk xmlns:w="client">
    <script type="text/javascript">
        /**
         * make a component focusable and can handle onCtrlKey event
         * by the given component's uuid
         */
        function makeFocusable (uuid) {
            var wgt = zk.Widget.$('#'+uuid);
            overrideFunctions(wgt);
        }
        /**
         * Override several functions to handle the focus/blur event
         * and handle the onCtrlKey event
         */
        function overrideFunctions (wgt) {
            if (wgt) {
                var oldClick = wgt.doClick_,
                    oldFocus = wgt.doFocus_,
                    oldBlur = wgt.doBlur_,
                    oldListen = wgt.isListen;
    
                wgt.doClick_ = function (evt) {
                    // the focusable anchor not exist, create it
                    if (!wgt.$n('custom-anchor')) {
                        createFocusableAnchor(wgt);
                        // rebind to enable added events
                        wgt.unbind().bind();
                    }
                     // focus the anchor if click on self to trigger the onFocus event
                     if (jq.isAncestor(wgt.$n(), evt.domTarget))
                         jq(wgt.$n('custom-anchor')).focus();
                     // do original function
                     oldClick.apply(wgt, arguments);
                 }
                 wgt.doFocus_ = function (evt) {
                     // do original function
                     oldFocus.apply(wgt, arguments);
                     // mantain focus status
                     if (evt.domTarget == wgt.$n('custom-anchor'))
                         wgt._focused = true;
                 }
                 wgt.doBlur_ = function (evt) {
                     // do original function
                     oldBlur.apply(wgt, arguments);
                     // mantain focus status
                     if (evt.domTarget == wgt.$n('custom-anchor'))
                         wgt._focused = false;
                 }
                 wgt.isListen = function (evt, opts) {
                    // ignore onCtrlKey event if self not focused
                    // see Widget.js#afterKeyDown_
                    if (evt == 'onCtrlKey')
                        return wgt._focused
                    // do original function
                    return oldListen.apply(wgt, arguments);
                }
            }
        }
        /**
         * Create a focusable anchor to make the widget focusable
         * and can handle the onCtrlKey event
         */
        function createFocusableAnchor (wgt) {
            if (wgt) {
                // create an anchor that can receive the focus
                var anchor = zk.ie || zk.gecko ? document.createElement("a") : document.createElement("button");
    
                // make it focusable
                anchor.href = 'javascript:;';
                // make it catchable
                anchor.id = wgt.uuid + '-custom-anchor';
    
                // make it out of the screen
                anchor.style.position = 'absolute';
                anchor.style.left = '-3000px';
                anchor.style.top = '-3000px';
    
                // make it as a part of the div component
                wgt.$n().appendChild(anchor);
                // listen to event focus, blur and keydown 
                wgt.domListen_(anchor, 'onFocus', 'doFocus_')
                    .domListen_(anchor, 'onKeyDown', 'doKeyDown_')
                    .domListen_(anchor, 'onBlur', 'doBlur_');
            }
        }
    </script>
    <div style="margin: 10px;">
        <div>1. Click on 1st label then press ctrl+s, you should see the textbox below not changed and the save dialog may appeared.</div>
        <div>2. Click on 1st block then press ctrl+s, you should see the textbox below not changed and the save dialog may appeared.</div>
        <div>3. Click on 2nd block then press ctrl+s, you should see the textbox below not changed and the save dialog may appeared.</div>
        <div>4. Click on 2nd label then press ctrl+s, you should see one line '2nd label onCtrlKey at [current time]' added to the textbox below.</div>
        <div>5. Click on 3rd block then press ctrl+s, you should see one line '3rd div onCtrlKey at [current time]' added to the textbox below.</div>
        <div>6. Click on 4th block then press ctrl+s, you should see one line '4th div onCtrlKey at [current time]' added to the textbox below.</div>
        <div>Note the save dialog should not appear in step 4, step 5 and step 6</div>
        <textbox id="msg" value="msg: " rows="6" width="500px" />
        <hbox>
            <label value="1st label"
                ctrlKeys="^s">
                <attribute name="onCtrlKey">
                    msg.setValue(msg.getValue() + "\n1st label onCtrlKey at " + new Date());
                </attribute>
            </label>
            <div width="100px" height="100px"
                style="background-color: #4477DD;"
                ctrlKeys="^s">
                <attribute name="onCtrlKey">
                    msg.setValue(msg.getValue() + "\n1st div onCtrlKey at " + new Date());
                </attribute>
                1st block
            </div>
            <div width="100px" height="100px"
                style="background-color: #6699DD;"
                ctrlKeys="^s">
                <attribute name="onCtrlKey">
                    msg.setValue(msg.getValue() + "\n2nd div onCtrlKey at " + new Date());
                </attribute>
                2nd block
            </div>
            <label value="2nd label"
                ctrlKeys="^s">
                <attribute name="onCreate">
                    <!-- make this component focusable and can handle onCtrlKey event -->
                    Clients.evalJavaScript("makeFocusable('"+self.getUuid()+"')");
                </attribute>
                <attribute name="onCtrlKey">
                    msg.setValue(msg.getValue() + "\n2nd label onCtrlKey at " + new Date());
                </attribute>
            </label>
            <div width="100px" height="100px"
                style="background-color: #44DD77;"
                ctrlKeys="^s">
                <attribute name="onCreate">
                    <!-- make this component focusable and can handle onCtrlKey event -->
                    Clients.evalJavaScript("makeFocusable('"+self.getUuid()+"')");
                </attribute>
                <attribute name="onCtrlKey">
                    msg.setValue(msg.getValue() + "\n3rd div onCtrlKey at " + new Date());
                </attribute>
                3rd block
            </div>
            <div width="100px" height="100px"
                style="background-color: #66DD99;"
                ctrlKeys="^s">
                <attribute name="onCreate">
                    <!-- make this component focusable and can handle onCtrlKey event -->
                    Clients.evalJavaScript("makeFocusable('"+self.getUuid()+"')");
                </attribute>
                <attribute name="onCtrlKey">
                    msg.setValue(msg.getValue() + "\n4th div onCtrlKey at " + new Date());
                </attribute>
                4th block
            </div>
        </hbox>
    </div>
</zk>

The Result

View demo flash on line:
http://screencast.com/t/rWelvvun24uE

Download make_unfocusable_component_focusable_and_handle_ctrlKeys.swf
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/make_unfocusable_component_focusable_and_handle_ctrlKeys.swf?raw=true

Download
make_unfocusable_component_focusable_and_handle_ctrlKeys.zul
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/WebContent/make_unfocusable_component_focusable_and_handle_ctrlKeys.zul

Reference

Widget.js
http://zk1.svn.sourceforge.net/viewvc/zk1/releases/5.0.8/zul/src/archive/web/js/zul/Widget.js?revision=16107&view=markup

see line 545 afterKeyDown_:

Wednesday, April 25, 2012

ZK: Trigger upload button click by click on label


Introduction

This post is about how to trigger upload button's click by clicking on a label.
(Or, at least, looks like clicking on a label.)

The Program

trigger_button_click_by_click_label.zul

3 parts in this fragment, trigger by client side programming,
trigger by Clients.evalJavaScript from server side,
or triiger by real click on the invisible button

<zk xmlns:w="client">
    <!-- The style make upload button not visible
        instead of the red border -->
    <style>
        .z-button *,
        .z-upload * {
            background: transparent !important;
            margin: 0 !important;
            border: 0 !important;
            cursor: default !important;
        }
        .z-button-cm {
            display: none;
        }
        <!-- remove the red border below to make the
            upload button completely invisible -->
        .z-button {
            border: 1px solid red;
        }
    </style>
    <window>
        <!-- Click button by client side programming,
            override label's doClick_ function
            may not work in IE -->
        <label value="open upload dialog by client side programming">
            <attribute w:name="doClick_">
                function (evt) {
                    // call the original doClick_ function
                    this.$doClick_(evt);
                    //---- update domTarget and domEvent as need ----//
                    // evt.domEvent = THE_NEW_EVENT;                 //
                    // evt.domTarget = THE_NEW_TARGET;               //
                    //---- ------------------------------------- ----//
                    // click the upload button by JQuery
                    jq('$btn').next().find('input').click();
                }
            </attribute>
        </label>
        <div></div>
        <!-- Click button by Clients.evalJavascript
            may not work in some browsers -->
        <label value="open upload dialog by javascript that executed by server">
            <attribute name="onClick">
                Clients.evalJavaScript("jq('$btn').next().find('input').click();");
            </attribute>
        </label>
        <div></div>
        <!-- Click button directly
            the style of upload button is overridden
            to make it invisible, and will change position
            while mouseover one of the 3 labels below
            
            should work in most browsers -->
        <div style="position: relative; cursor: default;" height="30px"
            onMouseOver="uploadDiv.setParent(self);">
            <label value="open upload by real click on it" />
        </div>
        <div style="position: relative; cursor: default;" height="30px"
            onMouseOver="uploadDiv.setParent(self);">
            <label value="also open upload by real click on it" />
        </div>
        <div style="position: relative; cursor: default;" height="30px"
            onMouseOver="uploadDiv.setParent(self);">
            <label value="still open upload by real click on it" />
        </div>
        result: <label id="result" />
        <div style="position: relative;">
            <div id="uploadDiv" style="position: absolute; left: 0px; top: 0px; z-index: 100;">
                <button id="btn" label="Upload" upload="true"
                    width="200px" height="15px">
                    <attribute name="onUpload"><![CDATA[
                        import org.zkoss.util.media.Media;
                        Media[] media = event.getMedias();
                        if (media != null) { // show the infprmation of updated media
                            StringBuilder sb = new StringBuilder("");
                            for (int i = 0; i < media.length; i++) {
                                sb.append(media[i].getName() + ", "+media[i].getByteData().length +" bytes\n");
                            }
                            result.setValue(sb.toString());
                        }
                        ]]></attribute>
                </button>
            </div>
        </div>
    </window>
</zk>


The Result

view demo flash on line:
http://screencast.com/t/HIXvMRjMGhrb

download it:
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/trigger_button_click_by_click_label.swf?raw=true

Download

trigger_button_click_by_click_label.zul
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/WebContent/trigger_button_click_by_click_label.zul

Tuesday, April 24, 2012

ZK: Customize the mask for showBusy

Introduction


This post is about how to customize the mask that cover the specific area when showBusy in ZK.

The Program

customize_mask_for_show-busy.zul

<zk>
    <style>
        .busy-outer {
            position: absolute;
            left: -1000px;
            top: -1000px;
            border: 1px solid red;
            overflow: hidden;
            height: 200px;
            width: 200px;
            
            filter: alpha(opacity=60); <!-- IE -->
            opacity: .6;
            zoom: 1;
            background: #EE8888;
        }
    </style>
    <script type="text/javascript"><![CDATA[
        var override = setInterval(function () { // wait until zAu.cmd0 loaded
            if (zAu && zAu.cmd0) {
    // store the old functions
                var oldShowBusy = zAu.cmd0.showBusy;
                var oldClearBusy = zAu.cmd0.clearBusy;
                zAu.cmd0.showBusy = function (uuid, msg) {
                    var cls, // class of original busy box
                        custom, // the custom busy box
                        $body = jq(document.body),
                        bwid = $body.width()-5, // body width (slightly smaller)
                        bhgh = $body.height()-5, // body height (slightly smaller)
                        innerWid = 200, // width of inner content of custom busy box
                        innerHgh = 60, // height of inner content of custom busy box
                        wid, hgh, // width and height of busy box
                        mt, ml, key; // margin-top, margin-left and mapping key
                    // get the class of original busy box and mapping key
                    if (arguments.length == 1 || !uuid) {
                        cls = 'z-modal-mask';
                        key = 'null'
                    } else {
                        cls = 'z-apply-mask';
                        key = zk.Widget.$(uuid).uuid;
                    }
                    // do the original showBusy
                    oldShowBusy.apply(this, arguments);
                    // get the original busy box
                    var $dom = jq('.'+cls);

                    if ((custom = jq('$customBusy'))) { // custom busybox exists
                        var outer = jq(custom.find('div').clone()[0]), // clone the content of custom busy box
                            wid = $dom.width(), // get original width
                            hgh = $dom.height(), // get original height
                            inner; // the inner content

                        // shrink the size if too large
                        if (wid > bwid) wid = bwid;
                        if (hgh > bhgh) hgh = bhgh;

                        // put content into body
                        document.body.appendChild(outer[0]);
                        // get inner content
                        inner = jq(outer.find('div')[0]);

                        // apply style to outer content
                        outer.css({
                            left: $dom.offset().left + 'px',
                            top: $dom.offset().top + 'px',
                            width: wid + 'px',
                            height: hgh + 'px'
                        });
                        outer[0].style.zIndex = $dom[0].style.zIndex;

                        // calculate the appropriate margin
                        ml = wid > innerWid? ($dom.outerWidth(true) - innerWid)/2 : 0;
                        mt = hgh > innerHgh? ($dom.outerHeight(true) - innerHgh)/2 : 0;
                        // apply the style to inner content
                        inner.css({
                            marginTop: mt + 'px',
                            marginBottom: mt + 'px',
                            marginLeft: ml + 'px',
                            marginRight: ml + 'px'
                        })
                        
                        $dom.parent()[0].style.display = 'none';
                        // store the outer node with mapping key
                        zAu.cmd0[key] = outer[0];
                    }
                };
                zAu.cmd0.clearBusy = function (uuid) {
                    var key,
                        node;
                    // get the mapping key
                    if (!uuid)
                        key = 'null'
                    else
                        key = zk.Widget.$(uuid).uuid;

                    // get node and remove it by mapping key
                    if ((node = zAu.cmd0[key])
                        && node.parentNode)
                        node.parentNode.removeChild(node);
                    
                    delete node;
                    // do the original clearBusy
                    oldClearBusy.apply(this, arguments);
                }
                clearInterval(override);
            }
        });
    ]]></script>
    <!-- Show busy on full page and clear it after 3 seconds -->
    <button label="busy on page">
        <attribute name="onClick">
            Clients.showBusy("busy...");
            tmOne.start();
        </attribute>
    </button>
    <!-- Show busy on label 'lb' and clear it after 3 seconds -->
    <button label="busy on label">
        <attribute name="onClick">
            Clients.showBusy(lb, "busy...");
            tmTwo.start();
        </attribute>
    </button>
    <timer id="tmOne" delay="3000" repeats="false" running="false" onTimer="Clients.clearBusy();" />
    <timer id="tmTwo" delay="3000" repeats="false" running="false" onTimer="Clients.clearBusy(lb);" />
    <label id="lb" value="message: " width="100px" height="100px" />
    <!-- The custom busy box, can put anything in it
        currently put a window -->
    <div id="customBusy">
        <div class="busy-outer">
            <div style="background-color: white;">
                <!-- The window below can be anything -->
                <window border="normal" title="busy...">
                    <div>This is the</div>
                    <div>custom busy box</div>
                </window>
            </div>
        </div>
    </div>
</zk>

The Result
View it on line:
http://screencast.com/t/JXaDIxgbrw

or

Download demo flash:
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/customize_mask_for_show-busy.swf?raw=true


Download
customize_mask_for_show-busy.zul
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/WebContent/customize_mask_for_show-busy.zul


Reference
http://zk1.svn.sourceforge.net/viewvc/zk1/branches/6.0/zk/src/archive/web/js/zk/au.js?revision=15987&view=markup

Saturday, April 14, 2012

Simple Linked List in ANSI C

Introduction

This post is about how to implement a Linked List in c, can add item to head, tail or any position, get item from head, tail or any position, display all items.

The Program

LinkedList.c

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
/**
 * This sample is about how to implement a queue in c
 *
 * Type of item is int
 * Add item to head, tail or any position
 * Get item from head, tail or any position
 * Get and remove item from head, tail or any position
 * Can get the size
 * Can display all item
 */
/**
 * The Node struct,
 * contains item and the pointers that point to previous node/next node.
 */
typedef struct Node {
    int item;
    // previous node
    struct Node* prev;
    // next node
    struct Node* next;
} Node;
/**
 * The LinkedList struct, contains the pointers that
 * point to first node and last node, the size of the LinkedList,
 * and the function pointers.
 */
typedef struct LinkedList {
    Node* head;
    Node* tail;
    // size of this LinkedList
    int size;

    // add item to any position
    void (*add) (struct LinkedList*, int, int);
    // add item after tail
    void (*addLast) (struct LinkedList*, int);
    // add item before head
    void (*addFirst) (struct LinkedList*, int);

    // insert node
    void (*insertBefore) (struct LinkedList*, Node*, Node*);
    // get item from any position
    int (*get) (struct LinkedList*, int);
    // get last item
    int (*getLast) (struct LinkedList*);
    // get first item
    int (*getFirst) (struct LinkedList*);

    // remove item from any position
    int (*remove) (struct LinkedList*, int);
    // remove last item
    int (*removeLast) (struct LinkedList*);
    // remove first item
    int (*removeFirst) (struct LinkedList*);

    // display all element in the LinkedList
    void (*display) (struct LinkedList*);
    // create a node with item
    Node* (*createNode) (int);
} LinkedList;

/** add item to any position
 */
void add (LinkedList* _this, int item, int position);
/** add item to head
 */
void addFirst (LinkedList* _this, int item);
/** add item to tail
 */
void addLast (LinkedList* _this, int item);
/** insert one node before another,
 * newNdoe, node and node->prev should not be null.
 */
void insertBefore (LinkedList* _this, Node* node, Node* newNode);
/** get item from specific position
 */
int get (LinkedList* _this, int position);
/** get item from head
 */
int getFirst (LinkedList* _this);
/** get item from tail
 */
int getLast (LinkedList* _this);
/** get item and remove it from any position
 */
int _remove (LinkedList* _this, int position);
/** get and remove item from head
 */
int _removeFirst (LinkedList* _this);
/** get and remove item from tail
 */
int _removeLast (LinkedList* _this);
/** display the items in the list
 */
void display (LinkedList* _this);
/** create a LinkedList
 */
LinkedList createLinkedList ();
/** create a Node
 */
Node* createNode (int item);

int main () {
    LinkedList list = createLinkedList();

    // 3
    list.addFirst(&list, 3);
    // 3, 5
    list.addLast(&list, 5);
    // 3, 4, 5
    list.add(&list, 4, 1);
    list.display(&list);

    // 3, 4, 5, 6
    list.addLast(&list, 6);
    // 3, 4, 5, 6, 7
    list.addLast(&list, 7);
    list.display(&list);
    printf("Get item: %d\n", list.get(&list, 2));
    printf("Get item: %d\n", list.get(&list, 4));
    list.display(&list);
    // 4, 5, 6, 7
    printf("Remove item: %d\n", list.removeFirst(&list));
    // 4, 5, 6
    printf("Remove item: %d\n", list.removeLast(&list));
    // 4, 6
    printf("Remove item: %d\n", list.remove(&list, 1));
    list.display(&list);

    system("PAUSE");
}
/** add item to any position
 */
void add (LinkedList* _this, int item, int position) {
     // index out of list size
     if (position > _this->size) {
        printf("LinkedList#add: Index out of bound");
        system("PAUSE");
        exit(0);
    }
    // add to head
    if (position == 0) {
        _this->addFirst(_this, item);
    } else if (position == _this->size) {
        // add to tail
        _this->addLast(_this, item);
    } else {
        // insert between head and tail

        Node* node = _this->head;
        int i = 0;
        // loop until the position
        while (i < position) {
            node = node->next;
            i++;
        }
        // insert new node to position
        Node* newNode = _this->createNode(item);
        _this->insertBefore(_this, node, newNode);
        _this->size++;
    }
}
/** add item to head
 */
void addFirst (LinkedList* _this, int item) {
    Node* newNode = _this->createNode(item);
    Node* head = _this->head;
    // list is empty
    if (head == NULL)
        _this->head = newNode;
    else { // has item(s)
        Node* last = _this->tail;
        if (last == NULL) // only head node
            last = head;
        newNode->next = head;
        head->prev = newNode;
        _this->head = newNode;
        _this->tail = last;
    }

    _this->size++;
}
/** add item to tail
 */
void addLast (LinkedList* _this, int item) {
    Node* newNode = _this->createNode(item);
    Node* head = _this->head;
    Node* tail = _this->tail;
    // list is empty
    if (head == NULL)
        _this->head = newNode;
    else { // has item(s)
        Node* lastNode = tail;
        if (tail == NULL) // only head node
            lastNode = head;
        lastNode->next = newNode;
        newNode->prev = lastNode;
        _this->tail = newNode;
    }
    _this->size++;
}

/** insert one node before another,
 * newNdoe, node and node->prev should not be null.
 */
void insertBefore (LinkedList* _this, Node* node, Node* newNode) {
    Node* prev = node->prev;

    node->prev = newNode;
    newNode->next = node;
    prev->next = newNode;
    newNode->prev = prev;
}
/** get item from specific position
 */
int get (LinkedList* _this, int position) {
    // list is empty
    if (_this->size == 0) {
        printf("LinkedList#get: The list is empty.");
        system("PAUSE");
        exit(0);
    } else if (position >= _this->size) {
        // out of bound
        printf("LinkedList#get: Index out of bound");
        system("PAUSE");
        exit(0);
    }
    // get head item
    if (position == 0) {
        return _this->getFirst(_this);
    } else if (position+1 == _this->size) {
        // get tail item
        return _this->getLast(_this);
    } else {
        Node* node = _this->head;
        int i = 0;
        // loop until position
        while (i < position) {
            node = node->next;
            i++;
        }
        return node->item;
    }
}
/** get item from head
 */
int getFirst (LinkedList* _this) {
    // list is empty
    if (_this->size == NULL) {
        printf("LinkedList#getFirst: The list is empty.");
        system("PAUSE");
        exit(0);
    }
    return _this->head->item;
}
/** get item from tail
 */
int getLast (LinkedList* _this) {
    // list is empty
    if (_this->size == 0) {
        printf("LinkedList#getLast: The list is empty.");
        system("PAUSE");
        exit(0);
    }
    // only head node
    if (_this->size == 1) {
        return getFirst(_this);
    }
    return _this->tail->item;
}
/** get item and remove it from any position
 */
int _remove (LinkedList* _this, int position) {
    // list is empty
    if (_this->size == 0) {
        printf("LinkedList#_remove: The list is empty.");
        system("PAUSE");
        exit(0);
    } else if (position >= _this->size) {
        // out of bound
        printf("LinkedList#_remove: Index out of bound");
        system("PAUSE");
        exit(0);
    }

    // remove from head
    if (position == 0) {
        return _this->removeFirst(_this);
    } else if (position+1 == _this->size) {
        // remove from tail
        return _this->removeLast(_this);
    } else {
        Node* node = _this->head;
        Node* prev;
        Node* next;
        int i = 0, item;
        // loop until position
        while (i < position) {
            node = node->next;
            i++;
        }
        item = node->item;
        // remove node from list
        prev = node->prev;
        next = node->next;
        prev->next = next;
        next->prev = prev;
        free(node);
        _this->size--;
        return node->item;
    }
}
/** get and remove item from head
 */
int _removeFirst (LinkedList* _this) {
    Node* head = _this->head;
    Node* next;
    int item;
    // list is empty
    if (head == NULL) {
        printf("LinkedList#_removeFirst: The list is empty.");
        system("PAUSE");
        exit(0);
    }
    item = head->item;
    next = head->next;
    _this->head = next;
    if (next != NULL) // has next item
        next->prev = NULL;
    free(head);
    _this->size--;
    if (_this->size <= 1) // empty or only head node
        _this->tail = NULL;
    return item;
}
/** get and remove item from tail
 */
int _removeLast (LinkedList* _this) {
    // list is empty
    if (_this->size == 0) {
        printf("LinkedList#_removeLast: The list is empty.");
        system("PAUSE");
        exit(0);
    }
    if (_this->size == 1) { // only head node
        return _this->removeFirst(_this);
    } else {
        Node* tail = _this->tail;
        Node* prev = tail->prev;
        int item = tail->item;
        prev->next = NULL;
        if (_this->size > 1)
            _this->tail = prev;
        _this->size--;
        if (_this->size <= 1) // empty or only head node
            _this->tail = NULL;
        return item;
    }
}
/** display the items in the list
 */
void display (LinkedList* _this) {
     int i, size = _this->size;
     if (size == 0)
        printf("no item\n\n");
     else {
        printf("has %d items\n", size);
        Node* node = _this->head;
        for (i = 0; i < size; i++) {
            if (i > 0)
                printf(", ");
            printf("%d", node->item);
            node = node->next;
        }
        printf("\n\n");
    }
}
/** create a LinkedList
 */
LinkedList createLinkedList () {
    LinkedList list;
    list.head = NULL;
    list.tail = NULL;
    list.add = &add;
    list.addFirst = &addFirst;
    list.addLast = &addLast;
    list.insertBefore = &insertBefore;
    list.get = &get;
    list.getFirst = &getFirst;
    list.getLast = &getLast;
    list.remove = &_remove;
    list.removeFirst = &_removeFirst;
    list.removeLast = &_removeLast;
    list.display = &display;
    list.createNode = &createNode;
    return list;
}
/** create a Node
 */
Node* createNode (int item) {
    Node* node = (Node*) malloc (sizeof(Node));
    node->item = item;
    node->prev = NULL;
    node->next = NULL;
    return node;
}

The Result



Download
The file is available at github
https://github.com/benbai123/C_Cplusplus_Practice/blob/master/C_DataStructure/LinkedLsit.c

Reference
http://en.wikipedia.org/wiki/LinkedList

ZK Tree with mix-state checkbox

Introducton

This post is about implement the mix-state of the checkbox in the tree,
tested with ZK 5.0.8.

How it works

A Treeitem store the selected child in an array,
and it will be set to mix-state if it has selected child and not self selected.

The Program

tree_mix-state_checkbox.zul

<zk xmlns:w="client">
    <!--
        tree_mix-state_checkbox.zul
        tested with ZK 5.0.8
    -->
    <style>
        <!-- The mix-state style,
             remove the margin then add blue border -->
        .mixed-state {
            margin: 0px;
            border: 2px solid #3E48AE;
        }
    </style>
    <!-- Override the Treeitem#doSelect_ -->
    <script type="text/javascript"><![CDATA[
    zk.afterLoad("zul.sel", function () {
        var oldSelect = zul.sel.Treeitem.prototype.doSelect_;
        zul.sel.Treeitem.prototype.doSelect_ = function (evt) {
            // do original doSelect_
            oldSelect.apply(this, arguments);

            var $dom = jq(evt.domTarget), // the dom within the target row
                row = $dom.hasClass('z-treerow')? $dom[0] : $dom.parents('.z-treerow')[0], // the root dom of target row
                isSelected = jq(row).hasClass('z-treerow-seld'), // the selected status of target row
                isSelfSelected = jq(this.treerow.$n()).hasClass('z-treerow-seld'), // the selected status of self
                _selectedChild = this._selectedChild, // the array that store the dom of the selected children
                index, isRecord;

            // create _selectedChild array if not exists.
            if (!_selectedChild)
                _selectedChild = this._selectedChild = [];

            // find the position of target row
            index = _selectedChild.indexOf(row);
            // whether the target row is already stored
            isRecord = index > -1;
            // push the root dom of target row into array if
            // it is selected but not stored
            if (isSelected && !isRecord)
                _selectedChild.push(row);
            // remove the root dom of target row from array if
            // it is not selected but stored
            else if (!isSelected && isRecord)
                _selectedChild.splice(index, 1);

            // set self style to mix-state if self is not selected
            // and some children is selected
            if (_selectedChild.length > 0 && !isSelfSelected)
                jq(this.treerow.$n()).find('.z-treerow-img-checkbox').addClass('mixed-state');
            else
                jq(this.treerow.$n()).find('.z-treerow-img-checkbox').removeClass('mixed-state');
        }
    });
    ]]></script>
    <div style="margin: 30px;">
        <tree id="tree" rows="5" multiple="true" checkmark="true" width="500px" vflex="200px">
            <treecols>
                <treecol label="Name"/>
                <treecol label="Description"/>
            </treecols>
            <treechildren>
                <treeitem>
                    <treerow>
                        <treecell label="Item 1"/>
                        <treecell label="Item 1 description"/>
                    </treerow>
                </treeitem>
                <treeitem>
                    <treerow>
                        <treecell label="Item 2"/>
                        <treecell label="Item 2 description"/>
                    </treerow>
                    <treechildren>
                        <treeitem>
                            <treerow>
                                <treecell label="Item 2.1"/>
                            </treerow>
                            <treechildren>
                                <treeitem>
                                    <treerow>
                                        <treecell label="Item 2.1.1"/>
                                    </treerow>
                                </treeitem>
                                <treeitem>
                                    <treerow>
                                        <treecell label="Item 2.1.2"/>
                                    </treerow>
                                </treeitem>
                            </treechildren>
                        </treeitem>
                        <treeitem>
                            <treerow>
                                <treecell label="Item 2.2"/>
                                <treecell label="Item 2.2 is something who cares"/>
                            </treerow>
                        </treeitem>
                    </treechildren>
                </treeitem>
                <treeitem label="Item 3"/>
            </treechildren>
        </tree>
    </div>
</zk>

The Result

See the demo flash at screencast.com:
http://screencast.com/t/ht55sGpsUK

Download
Thie files are available at github:

full project
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Components_Practice
tree_mix-state_checkbox.zul
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/WebContent/tree_mix-state_checkbox.zul
demo movie (click View Raw to download)
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/Tree_with_mix-state_checkbox.swf

ZK Datebox with Custom Server Side Parser

Introducton

This post is about use the server-side custom parser with ZK Datebox,
tested with ZK 5.0.8.

How it works

Process flow:
1. User input some value and blur the input node of datebox

2. At this moment, the client side widget store the parsed value, send the inputed value to server side with 'onCustomParse' event.
We also set the busy state at client side to prevent any operation while parsing.

3. Server receive the 'onCustomParse' event and try to parse the value inputed by the user.

4. Two possible result,
4.A The server parse the value without any problem, and update the value to client side by Datebox#setValue.
4.B The server can not parse the value, ask the client reset to the original parsed value.

Finally clear the busy state.

The Program

CustomParserDatebox.java

package test.custom.component.datebox;

import java.util.Date;

import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.Datebox;

/**
 * The Custom Datebox that can apply the service side custom date parser
 *
 */
public class CustomParserDatebox extends Datebox {

    private static final long serialVersionUID = 3683801858115113682L;
    static {
        // listen to the "onCustomParse" event
        addClientEvent(CustomParserDatebox.class, "onCustomParse", CE_IMPORTANT|CE_DUPLICATE_IGNORE);
    }
    // override
    // the 'service' method is used to process the component's event from client side
    public void service(org.zkoss.zk.au.AuRequest request, boolean everError) {
        final String cmd = request.getCommand();
        if (cmd.equals("onCustomParse")) {  // the 'onCustomParse' event
            String value = (String)request.getData().get("value");
            // assume we have the custom parser that
            // only parse the value '1' and '2'
            if ("1".equals(value)) { // parse value to date
                setValue(new Date(System.currentTimeMillis()));
                smartUpdate("backToOriginal", "clear");
            } else if ("2".equals(value)) { // parse value to date
                setValue(new Date(System.currentTimeMillis() - (1000L * 60 * 60 * 24 * 365)));
                smartUpdate("backToOriginal", "clear");
            }
            else // can not parse, use original client side value
                smartUpdate("backToOriginal", "reset");
            Events.postEvent(new Event("onCustomParse", this, null));
        } else 
            super.service(request, everError);
    }
}

datebox_with_server_side_custom_parser.zul

<zk xmlns:w="client">
    <div style="margin: 20px;">
        <!-- The datebox that use the custom comopnent at server side
             Note that the 'onCustomParse=""' is required for
             listen to onCustomParse event -->
        <datebox lenient="true"
            use="test.custom.component.datebox.CustomParserDatebox"
            onCustomParse="">
            <attribute w:name="doBlur_">
                function (evt) {
                    // store the user input
                    var userInput = this.$n('real').value;
                    // do original doBlur_
                    this.$doBlur_(evt);
                    // store the client side parsed value
                    this._oriParsedValue = this.$n('real').value;
                    // tmp set the old value to input
                    this.$n('real').value = userInput;
                    // ask a custom parse from server
                    // the parsed value will be updated by setValue at server side
                    this.fire('onCustomParse', {value: userInput});
                    // show busy to prevent any action when parse date
                    zAu.cmd0.showBusy(null, 'Busy, parsing...');
                    // show client side parsed value in label valO, can be removed
                    jq('$valO')[0].innerHTML = this._oriParsedValue;
                }
            </attribute>
            <attribute w:name="setBackToOriginal">
                // called when the server side parse is finished,
                // clear the stored client side parsed value if
                // custom parser parse the value without any problem
                // if the parse failed at server side,
                // reset input value with the original client side parsed value
                function (v) {
                    // reset to original value as need then clear it
                    if (this._oriParsedValue) {
                        if (v == 'reset') {
                            this.$n('real').value = this._oriParsedValue;
                            // clear valN label, can be removed
                            jq('$valN')[0].innerHTML = "";
                        } else { // the else fragment can be removed
                            // show client side parsed value in label valN
                            jq('$valN')[0].innerHTML = this.$n('real').value;
                        }
                        this._oriParsedValue = null;
                    }
                    // clear the busy state
                    zAu.cmd0.clearBusy(null);
                }
            </attribute>
        </datebox>
        <div height="25px"></div>
        the client side parsed value: <label id="valO" />
        <div height="25px"></div>
        the value parsed by custom parser: <label id="valN" />
    </div>
</zk>

The Result

See the demo flash at screencast.com:
http://screencast.com/t/m3OPonnftp

Download
Thie files are available at github:

full project
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Components_Practice

CustomParserDatebox.java
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/src/test/custom/component/datebox/CustomParserDatebox.java

datebox_with_server_side_custom_parser.zul
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/WebContent/datebox_with_server_side_custom_parser.zul

demo movie (click 'View Raw' to download)
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/datebox_with_server_side_custom_parser.swf

Reference
http://www.zkoss.org/javadoc/latest/jsdoc/zk/AuCmd0.html

Monday, April 9, 2012

Java Simple Web Crawler

Introduction

This is a simple web crawler, it is really simple,
do not have ability to prevent robot detection,
and may not parse link perfectly.

Just use it to do something what a good user will do.

The Program

SimpleCrawler.java


package test;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;

/**
 * This is a simple web crawler, it is really simple,
 * do not have ability to prevent robot detection,
 * and may not parse link perfectly.
 * 
 * Just use it to do something what a good user will do.
 *
 */
public class SimpleCrawler implements Runnable {
    public static final String DISALLOW = "Disallow:";

    // the result, <url, content>
    private Map<String, String> result = new HashMap<String, String>();
    // the url list to crawl
    private List<String> urlToCrawl = new ArrayList<String>();
    // the url list to skip
    private List<String> badUrls = new ArrayList<String>();
    // The current url object, used to build relative path
    private URL url;
    // the maximum amount of result 0 or smaller denotes no limitation
    private int resultLimit = 0;
    // whether the crawler is stopped
    private boolean stopped = false;

    // the delay between crawl
    private long delayBetweenCrawl = 5000;
    // the delay between check url
    private long delayBetweenCheck = 1000;

    /**
     * Crawl the given url,
     * @param rootUrl The Url to Crawl
     * @param limit The maximum size of result, 0 or negative denotes no limitation
     */
    public void Crawl (String rootUrl, int limit) {
        this.urlToCrawl.add(rootUrl);
        this.resultLimit = limit;
        
        new Thread(this).start();
    }
    public void run () {
        try {
            while (true) {
                if (urlToCrawl.size() == 0) {
                    break;
                }
                String strUrl = urlToCrawl.remove(0);
                url = new java.net.URL(strUrl);
                // get the content of first url
                String content = getResponse(strUrl, url).toString();
                // put the url/content to result map
                if (content != null) {
                    result.put(strUrl, content);
                    // stop
                    if (resultLimit > 0 && result.size() >= resultLimit)
                        break;
                    // get all url that have not be crawled 
                    urlToCrawl.addAll(getSubUrls(content, url));
                }

                // take a rest, do not crawl too fast
                try {
                    Thread.sleep(delayBetweenCrawl);
                } catch (Exception e) {
                    e.printStackTrace();
                    continue;
                }
            }
            stopped = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * get all html links in the given content
     * @param content The content that crawled by current url
     * @param url the current url
     * @return List, all html links in the given content
     */
    private List<String> getSubUrls (String content, URL url) {
        List<String> subUrls = new ArrayList<String>();
        String lowerCaseContent = content.toLowerCase();
        int index = 0;
        // has anchor tag
        while ((index = lowerCaseContent.indexOf("<a", index)) != -1) {
            // take a rest, do not check too fast
            try {
                Thread.sleep(delayBetweenCheck);
            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }

            // has href="..."
            if ((index = lowerCaseContent.indexOf("href", index)) == -1) 
                break;
            if ((index = lowerCaseContent.indexOf("=", index)) == -1) 
                break;

            // get next part of url
            index++;
            String remaining = content.substring(index);
            StringTokenizer st = new StringTokenizer(remaining, "\t\n\r\"<>#");
            String strLink = st.nextToken();
            // shift to the first http if exists
            if (!strLink.startsWith("http") && strLink.contains("http")) {
                strLink = strLink.substring(strLink.indexOf("http"));
            }
            // cut the tail after htm or html
            if ((!strLink.endsWith("html") && strLink.contains("html"))
                || (!strLink.endsWith("htm") && strLink.contains("htm"))) {
                boolean hasHtml = false;
                if (strLink.contains("html")) {
                    strLink = strLink.substring(0, strLink.lastIndexOf("html") + 4);
                    hasHtml = true;
                } else
                    strLink = strLink.substring(0, strLink.lastIndexOf("htm") + 3);
                System.out.println(hasHtml + " modified tail " + strLink);
            }
            if (badUrls.contains(strLink)) {
                System.out.println(" is bad url");
                continue;
            }
            // check to see if this URL has already been 
            // searched or is going to be searched
            if (!result.containsKey(strLink) 
                && !urlToCrawl.contains(strLink)) {

                URL urlLink;
                try {
                    // absolute link
                    if (strLink.startsWith("http"))
                        urlLink = new URL(strLink);
                    else // relative link
                        urlLink = new URL(url, strLink);
                    strLink = urlLink.toString();
                    System.out.println(strLink);
                } catch (MalformedURLException e) {
                    System.out.println("ERROR: bad URL " + strLink);
                    if (!badUrls.contains(strLink)) {
                        badUrls.add(strLink);
                    }
                    continue;
                }
    
                // only look at http links
                if (urlLink.getProtocol().compareTo("http") != 0) {
                    System.out.println("Not http");
                    if (!badUrls.contains(strLink)) {
                        badUrls.add(strLink);
                    }
                    continue;
                }
    
                // test and store the url
                try {
                    // try opening the URL
                    URLConnection urlLinkConnection 
                        = urlLink.openConnection();
                    urlLinkConnection.setAllowUserInteraction(false);
                    InputStream linkStream = urlLink.openStream();
                    String strType
                        = urlLinkConnection.guessContentTypeFromStream(linkStream);
                    String strTypeTwo = urlLinkConnection.getContentType();
                    linkStream.close();
    
                    // is text/html
                    if ((strTypeTwo != null && strTypeTwo.contains("text/html"))
                        || (strType != null && strType.contains("text/html"))) {
                        // add new url to list
                        urlToCrawl.add(strLink);
                    }
                } catch (IOException e) {
                    System.out.println("ERROR: couldn't open URL " + strLink);
                    if (!badUrls.contains(strLink)) {
                        badUrls.add(strLink);
                    }
                    continue;
                }
            }
            // add to bad urls if not added
            if (!result.containsKey(strLink) 
                && !urlToCrawl.contains(strLink)
                && !badUrls.contains(strLink)) {
                badUrls.add(strLink);
            }
        }
        return subUrls;
    }

    public static StringBuilder getResponse(String path, URL url){
        try {
            java.net.HttpURLConnection uc = (java.net.HttpURLConnection) url.openConnection();
            uc.setRequestProperty("User-agent", "Mozilla/10.0");

            uc.setRequestProperty("Accept-Charset", "UTF-8"); // encoding
            uc.setReadTimeout(30000);// timeout limit
            uc.connect();// connect
            int status = uc.getResponseCode();

            switch (status) {
                case java.net.HttpURLConnection.HTTP_GATEWAY_TIMEOUT://504 timeout
                    break;
                case java.net.HttpURLConnection.HTTP_FORBIDDEN://403 forbidden
                    break;
                case java.net.HttpURLConnection.HTTP_INTERNAL_ERROR://500 server error
                    break;
                case java.net.HttpURLConnection.HTTP_NOT_FOUND://404 not exist
                    break;
                case java.net.HttpURLConnection.HTTP_OK: // ok
                    InputStreamReader reader = new InputStreamReader(uc.getInputStream(), "UTF-8");

                    int ch;
                    StringBuilder sb = new StringBuilder("");
                    while((ch = reader.read())!= -1){
                        sb.append((char)ch);
                    }
                    return sb;
            }

        } catch (java.net.MalformedURLException e) { // invalid address format
            e.printStackTrace();
        } catch (java.io.IOException e) { // connection broken
            e.printStackTrace();
        }
        return new StringBuilder("");
    }
    public Map getResult () {
        return result;
    }
    /**
     * Whether the crawler is stopped
     * @return boolean
     */
    public boolean isStopped () {
        return stopped;
    }
    public static void main (String[] args) {
        SimpleCrawler crawler = new SimpleCrawler();
        // crawl this url
        crawler.Crawl("http://java.sun.com", 2);
        // wait until crawler stopped
        while (!crawler.isStopped()) {
            try{
                Thread.sleep(10000);
            } catch (Exception e) {
                continue;
            }
        }
        // show the results
        Map result = crawler.getResult();
        System.out.println(result.size());
        for (Object key : result.keySet()) {
            System.out.println(key.toString());
            if (result.get(key).toString().length() > 50)
                System.out.println(result.get(key).toString().substring(0, 50));
            else
                System.out.println(result.get(key));
        }
    }
}

The Result



Download
The program is available at github
https://github.com/benbai123/JSP_Servlet_Practice/blob/master/Practice/JAVA/Net/src/test/SimpleCrawler.java

Reference
http://java.sun.com/developer/technicalArticles/ThirdParty/WebCrawler/

Sunday, April 1, 2012

Simple Queue Data Structure in ANSI C

Introduction

This post is about how to implement a queue in c, can push item to tail, pop item from head, peek item without delete from head, display all items and get its size.

The program

Queue.c


#include <stdio.h>
#include <stdlib.h>
/**
  * This sample is about how to implement a queue in c
  *
  * Type of item is int
  * Add item to tail
  * Get item from head
  * Can get the size
  * Can display all content
  */
/**
 * The Node struct,
 * contains item and the pointer that point to next node.
 */
typedef struct Node {
    int item;
    struct Node* next;
} Node;
/**
 * The Queue struct, contains the pointers that
 * point to first node and last node, the size of the Queue,
 * and the function pointers.
 */
typedef struct Queue {
    Node* head;
    Node* tail;

    void (*push) (struct Queue*, int); // add item to tail
    // get item from head and remove it from queue
    int (*pop) (struct Queue*);
    // get item from head but keep it in queue
    int (*peek) (struct Queue*);
    // display all element in queue
    void (*display) (struct Queue*);
    // size of this queue
    int size;
} Queue;
/**
 * Push an item into queue, if this is the first item,
 * both queue->head and queue->tail will point to it,
 * otherwise the oldtail->next and tail will point to it.
 */
void push (Queue* queue, int item);
/**
 * Return and remove the first item.
 */
int pop (Queue* queue);
/**
 * Return but not remove the first item.
 */
int peek (Queue* queue);
/**
 * Show all items in queue.
 */
void display (Queue* queue);
/**
 * Create and initiate a Queue
 */
Queue createQueue ();
int main () {
    Queue queue = createQueue();
    queue.display(&queue);

    printf("push item 2\n");
    queue.push(&queue, 2);    
    printf("push item 3\n");
    queue.push(&queue, 3);
    printf("push item 6\n");
    queue.push(&queue, 6);

    queue.display(&queue);

    printf("peek item %d\n", queue.peek(&queue));
    queue.display(&queue);

    printf("pop item %d\n", queue.pop(&queue));
    printf("pop item %d\n", queue.pop(&queue));
    queue.display(&queue);

    printf("pop item %d\n", queue.pop(&queue));
    queue.display(&queue);
    printf("push item 6\n");
    queue.push(&queue, 6);

    queue.display(&queue);
    system("PAUSE");
}

/**
 * Push an item into queue, if this is the first item,
 * both queue->head and queue->tail will point to it,
 * otherwise the oldtail->next and tail will point to it.
 */
void push (Queue* queue, int item) {
    // Create a new node
    Node* n = (Node*) malloc (sizeof(Node));
    n->item = item;
    n->next = NULL;

    if (queue->head == NULL) { // no head
        queue->head = n;
    } else{
        queue->tail->next = n;
    }
    queue->tail = n;
    queue->size++;
}
/**
 * Return and remove the first item.
 */
int pop (Queue* queue) {
    // get the first item
    Node* head = queue->head;
    int item = head->item;
    // move head pointer to next node, decrease size
    queue->head = head->next;
    queue->size--;
    // free the memory of original head
    free(head);
    return item;
}
/**
 * Return but not remove the first item.
 */
int peek (Queue* queue) {
    Node* head = queue->head;
    return head->item;
}
/**
 * Show all items in queue.
 */
void display (Queue* queue) {
    printf("\nDisplay: ");
    // no item
    if (queue->size == 0)
        printf("No item in queue.\n");
    else { // has item(s)
        Node* head = queue->head;
        int i, size = queue->size;
        printf("%d item(s):\n", queue->size);
        for (i = 0; i < size; i++) {
            if (i > 0)
                printf(", ");
            printf("%d", head->item);
            head = head->next;
        }
    }
    printf("\n\n");
}
/**
 * Create and initiate a Queue
 */
Queue createQueue () {
    Queue queue;
    queue.size = 0;
    queue.head = NULL;
    queue.tail = NULL;
    queue.push = &push;
    queue.pop = &pop;
    queue.peek = &peek;
    queue.display = &display;
    return queue;
}

The result



Download
The file is available at github
https://github.com/benbai123/C_Cplusplus_Practice/blob/master/C_DataStructure/Queue.c

Reference
http://en.wikipedia.org/wiki/Queue_(data_structure)

ZK Flow Control: Detect inactive time and do notify before logout.

This post is about how to detect the last access time and notify user before automatically logout in ZK.

The Files

web.xml


<?xml version="1.0" encoding="UTF-8"?>

<!-- web.xml
    Purpose:
        
    Description:
        
    History:
        Wed Dec 29 12:13:00     2004, Created by tomyeh

Copyright (C) 2004 Potix Corporation. All Rights Reserved.
-->

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> 

    <description><![CDATA[My ZK Application]]></description>
    <display-name>MyApp</display-name>

    <!-- //// -->
    <!-- ZK -->
    <listener>
        <description>ZK listener for session cleanup</description>
        <listener-class>org.zkoss.zk.ui.http.HttpSessionListener</listener-class>
    </listener>
    <servlet>
        <description>ZK loader for ZUML pages</description>
        <servlet-name>zkLoader</servlet-name>
        <servlet-class>org.zkoss.zk.ui.http.DHtmlLayoutServlet</servlet-class>

        <!-- Must. Specifies URI of the update engine (DHtmlUpdateServlet).
        It must be the same as <url-pattern> for the update engine.
        -->
        <init-param>
            <param-name>update-uri</param-name>
            <param-value>/zkau</param-value>
        </init-param>
        <!-- Optional. Specifies whether to compress the output
        of the ZK loader. It speeds up the transmission over slow Internet.
        However, if you configure a filter to post-processing the
        output, you might have to disable it.

        Default: true
        <init-param>
            <param-name>compress</param-name>
            <param-value>true</param-value>
        </init-param>
        -->
        <!-- Optional. Specifies the default log level: OFF, ERROR, WARNING,
            INFO, DEBUG and FINER. If not specified, the system default is used.
        <init-param>
            <param-name>log-level</param-name>
            <param-value>OFF</param-value>
        </init-param>
        -->
        <load-on-startup>1</load-on-startup><!-- Must -->
    </servlet>
    <servlet-mapping>
        <servlet-name>zkLoader</servlet-name>
        <url-pattern>*.zul</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>zkLoader</servlet-name>
        <url-pattern>*.zhtml</url-pattern>
    </servlet-mapping>
    <!-- Optional. Uncomment it if you want to use richlets.
    <servlet-mapping>
        <servlet-name>zkLoader</servlet-name>
        <url-pattern>/zk/*</url-pattern>
    </servlet-mapping>
    -->
    <servlet>
        <description>The asynchronous update engine for ZK</description>
        <servlet-name>auEngine</servlet-name>
        <servlet-class>org.zkoss.zk.au.http.DHtmlUpdateServlet</servlet-class>

        <!-- [Optional] Specifies whether to compress the output
        of the ZK loader. It speeds up the transmission over slow Internet.
        However, if your server will do the compression, you might have to disable it.

        Default: true
        <init-param>
            <param-name>compress</param-name>
            <param-value>true</param-value>
        </init-param>
        -->
        <!-- [Optional] Specifies the AU extension for particular prefix.
        <init-param>
            <param-name>extension0</param-name>
            <param-value>/upload=com.my.MyUploader</param-value>
        </init-param>
        -->
    </servlet>
    <servlet-mapping>
        <servlet-name>auEngine</servlet-name>
        <url-pattern>/zkau/*</url-pattern>
    </servlet-mapping>

    <!-- The filter put the real last access time into session -->
    <filter>
        <filter-name>lastAccessFilter</filter-name>
        <filter-class>test.LastAccessFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>lastAccessFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- Uncomment if you want to use the ZK filter to post process the HTML output
    generated by other technology, such as JSP and velocity.
    <filter>
        <filter-name>zkFilter</filter-name>
        <filter-class>org.zkoss.zk.ui.http.DHtmlLayoutFilter</filter-class>
        <init-param>
            <param-name>extension</param-name>
            <param-value>html</param-value>
        </init-param>
        <init-param>
            <param-name>compress</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>zkFilter</filter-name>
        <url-pattern>/test/filter.dsp</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>zkFilter</filter-name>
        <url-pattern>/test/filter2.dsp</url-pattern>
    </filter-mapping>
    -->
    <!-- //// -->

    <!-- /////////// -->
    <!-- Timeout in 60 minutes (3600 seconds) -->
    <session-config>
        <session-timeout>60</session-timeout>
    </session-config>

    <!-- MIME mapping -->
    <mime-mapping>
        <extension>doc</extension>
        <mime-type>application/vnd.ms-word</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>gif</extension>
        <mime-type>image/gif</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>htm</extension>
        <mime-type>text/html</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>html</extension>
        <mime-type>text/html</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>jpeg</extension>
        <mime-type>image/jpeg</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>jpg</extension>
        <mime-type>image/jpeg</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>js</extension>
        <mime-type>text/javascript</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>pdf</extension>
        <mime-type>application/pdf</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>png</extension>
        <mime-type>image/png</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>txt</extension>
        <mime-type>text/plain</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>xls</extension>
        <mime-type>application/vnd.ms-excel</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>xml</extension>
        <mime-type>text/xml</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>zhtml</extension>
        <mime-type>text/html</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>zul</extension>
        <mime-type>text/html</mime-type>
    </mime-mapping>

    <welcome-file-list>
        <welcome-file>index.zul</welcome-file>
        <welcome-file>index.zhtml</welcome-file>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
    </welcome-file-list>
</web-app>

In web.xml, we specify a filter called lastAccessFilter,
which will put the real last access time into session.

We also specify the session will timeout in 60 minutes,
i.e., session.getMaxInactiveInterval() will return 3600 (seconds).

LastAccessFilter.java


package test;

import java.io.IOException;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.FilterChain;
import javax.servlet.Filter;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class LastAccessFilter implements Filter {
    public void init(FilterConfig fc) {}
    public void doFilter (ServletRequest req, ServletResponse resp, FilterChain chain) {
        HttpServletRequest hreq = (HttpServletRequest)req;
        HttpSession sess = hreq.getSession();
        String checker_id = (String)sess.getAttribute("checker_id");
        String componentId = hreq.getParameter("uuid_0");

        if (componentId == null || !componentId.equals(checker_id)) {

            // only do if not request by check timer
            long last = sess.getLastAccessedTime();
            sess.setAttribute("real_last", last);
        } else if (sess.getAttribute("real_last") == null) {
            // need the initiate however
            long last = sess.getLastAccessedTime();
            sess.setAttribute("real_last", last);
        }
        try {
            chain.doFilter(req, resp);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ServletException e) {
            e.printStackTrace();
        }
    }
    public void destroy() {}
}