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_:

7 comments:

  1. Hi ben can we use + and -(Minus) with CTRL key something like this CTRL+ and CTRL- for increment and decrement of values by +1 or -1

    ReplyDelete
    Replies
    1. Yes, you can override afterKeyDown_ function as needed.

      e.g.,
      http://zkfiddle.org/sample/237fnb0/1-Custom-ctrl-keys

      Delete
    2. References:

      https://github.com/zkoss/zk/blob/6.0/zul/src/archive/web/js/zul/Widget.js

      http://books.zkoss.org/wiki/ZK_Developer's_Reference/UI_Patterns/Keystroke_Handling

      Delete
    3. I tried your example and try to press CTRL+ and CTRL- but nothing happens.Is i am doing something wrong

      Delete
    4. This sample use the '+' '-' besides numpad (at the right side of keyboard) and only works when intbox has the focus

      Delete
  2. Still not worked Can we d6 5t with CTRl+ only?

    ReplyDelete
    Replies
    1. Maybe you can try log the key code in the overridden $afterKeyDown event:

      function (evt/*, simulated*/) {
      this.$afterKeyDown_(evt);
      zk.log(evt.keyCode);
      ...

      Maybe different browser have different keyCode, it works well for me on chrome.

      Delete