Tuesday, December 11, 2012

Pass Event to Other Component



Introduction

Some times we want to change a component while some event of another component is triggered, we can achieve this by posting an event with some data to the component we want to change instead of modify it directly in the event listener of another component.

This seems a little bit weird and useless but this would be helpful in some situation such like implementing inplace editing of grid with renderer under MVVM (will be described in another article later)

Pre-request

(must)
Basic MVC with SelectorComposer
http://ben-bai.blogspot.tw/2012/10/zk-basic-mvc-pattern-with_31.html

The Composer

PasseventTestComposer.java

Simply post an event to label while the value of textbox is changed, update label's value in the event listener itself.

package test.event.passeventtest;

import java.util.HashMap;
import java.util.Map;

import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.InputEvent;
import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.select.annotation.Listen;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zul.Label;

/**
 * Tested with ZK 6.0.2
 * 
 * Test passing event with data from one component to another
 * 
 * This seems a little bit weird and useless here
 * but this would be helpful in some situation
 * such like implement inplace editing of grid with renderer
 * under MVVM
 * (will be described in another article later)
 * 
 * @author benbai
 *
 */
public class PasseventTestComposer extends SelectorComposer {
    @Wire
    Label lb;

    /**
     * Post an event with the new value to label
     * while the value of textbox is changed
     * instead of modify the value of label directly
     * @param event
     */
    @Listen("onChange=#tbx")
    public void onChange$tbx (InputEvent event) {
        Map data = new HashMap();
        data.put("value", event.getValue());
        Events.postEvent("onValueChange", lb, data);
    }
    /**
     * Really modify the value of label here
     * @param event
     */
    @Listen("onValueChange=#lb")
    public void onValueChange$lb (Event event) {
        Map data = (Map)event.getData();
        String value = (String)data.get("value");
        lb.setValue(value);
    }
}


The ZUL Page

pass_event_to_other_component.zul

<zk>
    <!-- Tested with ZK 6.0.2 -->
    <window apply="test.event.passeventtest.PasseventTestComposer">
        <label id="lb" value="label" />
        <textbox id="tbx" />
    </window>
</zk>


The Result

View demo on line
http://screencast.com/t/9wYBLE3lubaF

Reference

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

Download

Files at github

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

PasseventTestComposer.java
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/src/test/event/passeventtest/PasseventTestComposer.java

pass_event_to_other_component.swf
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/pass_event_to_other_component.swf

36 comments:

  1. How to pass KeyStroke event in Child Pages I have hierarchy MainParent->MainParentChild->MainParentChildsChild now i will want to pass the KeyStroke Event in the MainParentChildsChild page like If User Press Ctrl+q or Ctril+S or Ctrl+A i have pass these keys in the Child pages.

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. The pages are included like this..

    <tabpanels>
    <tabpanel style="color:#333399;">
    <include src="dashboard.zul" />
    </tabpanel>



    What i will want here to send Keypress event code in Child page ViewModel. As i added
    ctrlKeys="^a^s^d#f8" onCtrlKey="@command('ctrlKeyClick',code=event.getKeyCode())"

    In My parent ZUL and now i will want KeyCode in my Children viewmodel How can i do this?

    ReplyDelete
    Replies
    1. Child page can access parent vm directly, please refer to the sample at zkfiddle http://zkfiddle.org/sample/2cptkn6/6-MVVM-Inner-Page-Ctrlkeys

      Delete
    2. I think you miss understood my problem in my case i have send data to Child viewmodel from parent viewmodel.In you example you are passing Ctrl event from child to parent but i need opposite..I have asked a question here..http://forum.zkoss.org/question/84920/how-to-call-child-viewmodel-method-from-parent-window/

      Delete
    3. Maybe you can try Global Command Binding: http://books.zkoss.org/wiki/ZK%20Developer's%20Reference/MVVM/Data%20Binding/Global%20Command%20Binding

      Delete
    4. Thanks i have done this with GlobalCommand can you please tell me is any drawback to use GlobalCommand ?

      Delete
    5. One more thing i have to fire Ctrl key event on active tab as my parent window can open lots of tab and if i will use Global command how i will figure out which tab is active so that i can fire the command only on that tab.

      Delete
    6. If the page is cached and then refreshed, it may bind command multiple times.

      Delete
    7. You can try pass the information of selected tab (e.g., index, label, etc) and use it as a condition in command fumction.

      Delete
    8. Here in my case tab contain a wholee page different tab means a different page also tab are created dynamically from java code and when tab or page is selected and user doing something then if user use ctrl key then I have to fire save, refresh, delete etc.

      Delete
    9. Are the tabs under parent VM or child VM?

      Delete
    10. I have home.zul which contain menu with item and when you will click on any menu item it will open a tab for each menu item a new tab will open now each tab contain a zul page where user can do certain operation and by Ctrl keys like Ctrl+s for Saving, Ctrl+r for Refresh, Ctrl+q for query etc will perform. If I will use global command I have to write that global command in each tab view model or we can say child of home. Now suppose I open a tab then ctrl key will fire now I open another tab now 2 times ctrl key will fire , while I will want only active tab view model will fire a event on ctrl key

      Delete
    11. Maybe you can try the form binding (http://books.zkoss.org/wiki/ZK%20Developer's%20Reference/MVVM/Data%20Binding/Form%20Binding), if this still not work in your case, you can try customize the tabbox as needed, e.g., like this sample at zkfiddle (http://zkfiddle.org/sample/vmmp6p/1-MVVM-Test)

      Delete
    12. Thanks Let me try your example

      Delete
    13. Here you added ctrl key event in tab while in my case i added ctrl key event in windows compoentn so it will available for each tab because my all tab is added from java code and its too complex to add ctrl key in tab component

      Delete
    14. I've tested it and the onCtrlKey event fired for each tab without any problem, basically the parent component (tabbox) will receive the events of its children (all tabs)

      Delete
    15. By the way, the sample above firing event to selected tab child from tabbox instead of using global command

      Delete
    16. can it possible we can change this line
      Events.postEvent("onActionRequest", tp.getFirstChild().getFellow("div"), data);

      As you are doing id binding i will want to give command name or other thing because i have plenty with different component if i will add id static id in each pages i have to change plenty of places .Can we have any other solution here?

      Delete
    17. You can also use tp.getFirstChild().getFirstChild() if the first element of each inner page is the element that apply child vm.

      Delete
  4. In my case its very hard to do that Can we do something like postcommand where we can give command name and the command will fire child viewmodel ?

    ReplyDelete
    Replies
    1. Maybe you can try global command in this way:

      1. Store the selected tab in parent vm, and pass it to each child vm with global command

      2. Store any component (maybe the first one under child vm) in the child zul within child vm, and continuously get parent component until find a tab then check whether it is the selected tab when global command triggered.

      Delete
    2. Ok but If we are adding a calling a GlobalComamnd from Parent View Model and creating a global command in each of the Child viewmodel then it is going to each viewmodel global command because name is same in each child viewmodel

      Delete
    3. That's right, then you can find the parent tab (from child vm) and detect whether it is the selected tab (in the global event from parent vm).

      Delete
    4. Can it possible to run bind.postCommand("methodName", map); here ?

      Delete
    5. According to the javadoc (http://www.zkoss.org/javadoc/6.5.1/zk/org/zkoss/bind/impl/BinderImpl.html#postCommand(java.lang.String, java.util.Map)), postCommand will post a command to current binder, it can not post command from parent vm to child vm. You can try use EventQueue directly as needed.

      Delete
    6. There's an idea coming in to my mind, since requests are thread safe, we can assume the create event of tab and the init of child vm are occur at the same time, in other words, if we maintain two list say tabList and vmList, we can assume the order of tab in tabList will match the order of child vm in vmList, i.e., we can do something as below:

      1. find the order of selected tab in parent vm

      2. get the corresponding child vm based on the order found before in vmList

      3. call binder.postCommand of child vm

      just a rough concept, not tested

      Delete
    7. Thanks Again Ben ...i got another idea and look like it is working fine with me What i did? I made a Singleton Class and added a variable of Component class with get/set method. Now in each of my view Model afterCompose() i am calling setter method of Component variable from singleton class, and passing current viewModel Component object to it and in my HomeViewModel CTRL key event method i adding this code so it calling method from Selected tab .

      Component ctrlkeyComp = idBinder.getCompObject();
      if(ctrlkeyComp != null){
      Binder bind = (Binder) ctrlkeyComp.getAttribute("binder");

      if (bind == null)
      return;
      bind.postCommand("doActionInChildVM", map);
      }

      Delete
    8. Yeah this seems a good way to go.

      Delete
    9. These is one issue with this approach here in using Singleton Class .Let us suppose i have open A<B,C,D tab and i am in D tab so Singleton class have COmponent class of D now if i will click on tab A then methods of TabD will be call Which is a issue.

      Delete
    10. Is there a sample that can reproduce this issue?

      Delete
    11. I have asked one question here http://stackoverflow.com/questions/15001670/how-to-get-viewmodel-from-selectedtab-in-zk

      Delete
  5. Ben do you have any idea about this http://stackoverflow.com/questions/14879269/zk-listbox-onselect-issue

    ReplyDelete