2008年6月2日月曜日

[解決]AjaxRequestTarget#addComponent()

以前のエントリ、AjaxRequestTarget#addComponent()で以下のような事を書いた。
llegalArgumentExceptionが投げられるようになっている。 結局、ListViewをdiv wicket:id="ListViewContainer"みたいな要素の内側に配置して、 ajaxRequestTarget#addComponent()にこのContainerを渡す、みたいな方法をとった。で、このContainer の実装として「org.apache.wicket.markup.html.border.BoxBorder」を使用した。 これでちゃんと動作しているから、困ってるという訳でも無いかもしれんけど、気分的には、こういう時に使うPanelとかBoxみたいなContainerとしてしか機能がないContainerを使いたい、あんまキレイじゃない、という気分。
が、WicketのWikiにこの件について「Working With AjaxRequestTarget」というページがあり、そのものズバリだった!
  1. Do not add the view to a target. Use a parent component instead.
  2. If the view is within a Panel do not add the panel to the target. Use a WebMarkupContainer (or similar component) instead.
WebMarkupContainerというclassがそういった用途に使えるとの事。ちなみに、このclassはしょっちゅう出てくるので知ってはいたが、自分でインスタンスを作れるclassだとはちっとも思っていなかったw 基本的な事だろぅに、ちゃんと読めよ、俺。
  • Html
    <html>
      <head>
        <title>Wicket Quickstart Archetype Homepage</title>
      </head>
      <body>
        <div><form wicket:id="myform">
        <div><input type="text" wicket:id="text"/></div>
        <!-- Containerが必要。 -->
        <div wicket:id="listViewContainer"><ul>
        <li wicket:id="listView"><a href="#" wicket:id="label"></a></li>
        </ul></div></form></div>
      </body>
    </html>
  • Page
    import java.util.ArrayList;
    import java.util.List;
    
    import org.apache.wicket.PageParameters;
    import org.apache.wicket.ajax.AjaxRequestTarget;
    import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
    import org.apache.wicket.ajax.markup.html.AjaxLink;
    import org.apache.wicket.behavior.IBehavior;
    import org.apache.wicket.markup.ComponentTag;
    import org.apache.wicket.markup.MarkupStream;
    import org.apache.wicket.markup.html.WebMarkupContainer;
    import org.apache.wicket.markup.html.WebPage;
    import org.apache.wicket.markup.html.form.Form;
    import org.apache.wicket.markup.html.form.TextField;
    import org.apache.wicket.markup.html.list.ListItem;
    import org.apache.wicket.markup.html.list.ListView;
    import org.apache.wicket.model.Model;
    
    public class HomePage extends WebPage {
      @SuppressWarnings("serial")
      public HomePage(final PageParameters parameters) {
        // myform
        final Form form = new Form("myform");
        // myform/text
        final TextField text = new TextField("text", new Model(""));
        text.setOutputMarkupId(true);
        form.add(text);
        // myform/listViewContainer: WebMarkupContainerでListViewを包む。
        WebMarkupContainer listViewContainer = new WebMarkupContainer("listViewContainer");
        listViewContainer.setOutputMarkupId(true);
        form.add(listViewContainer);
        // myform/listViewContainer/listView
        listViewContainer.add(new ListView("listView", getLabelStrings()) {
          @Override
          protected void populateItem(ListItem item) {
            String labelString = item.getModelObjectAsString();
            item.add(new AjaxLink("label", new Model(labelString)) {
              @Override
              public void onClick(AjaxRequestTarget target) {
                text.setModel(new Model(this.getModelObjectAsString()));
                target.addComponent(form.get("text"));
              }
              @Override
              protected void onComponentTagBody(MarkupStream markupStream,
                  ComponentTag openTag) {
                super.replaceComponentTagBody(markupStream, openTag, this
                    .getModelObjectAsString());
              }
            });
          }
        });
        add(form);
        // 
        text.add(newOnChangeAjaxBehavior(form));
      }
    
      @SuppressWarnings("serial")
      private IBehavior newOnChangeAjaxBehavior(final Form form) {
        return new OnChangeAjaxBehavior() {
          @Override
          protected void onUpdate(AjaxRequestTarget target) {
            String textString = form.get("text").getModelObjectAsString();
            ListView listView = (ListView) form.get("listViewContainer:listView");
            List<String> filteredStrings = getLabelStrings();
            if (textString != "") {
              filteredStrings.clear();
              for (String label : getLabelStrings()) {
                if (label.indexOf(textString) >= 0) {
                  filteredStrings.add(label);
                }
              }
            }
            listView.setList(filteredStrings);
            target.addComponent(form.get("listViewContainer"));
          }
        };
      }
    
      private List<String> getLabelStrings() {
        List<String> labelStrings = new ArrayList<String>();
        labelStrings.add("apple");
        labelStrings.add("orange");
        labelStrings.add("lemon");
        labelStrings.add("banana");
        labelStrings.add("grape");
        return labelStrings;
      }
    }
  • TestCase
    import static org.junit.Assert.assertArrayEquals;
    import static org.junit.Assert.assertEquals;
    
    import org.apache.wicket.ajax.markup.html.AjaxLink;
    import org.apache.wicket.markup.html.WebMarkupContainer;
    import org.apache.wicket.markup.html.form.Form;
    import org.apache.wicket.markup.html.form.TextField;
    import org.apache.wicket.markup.html.list.ListView;
    import org.apache.wicket.util.tester.FormTester;
    import org.apache.wicket.util.tester.WicketTester;
    import org.junit.Before;
    import org.junit.Test;
    
    public class HomePageTest {
      private WicketTester tester;
    
      @Before public void setUp() {
        tester = new WicketTester();
        tester.startPage(HomePage.class);
        tester.assertRenderedPage(HomePage.class);
        tester.assertNoErrorMessage();
      }
    
      @Test public void testRenderMyPage() {
        tester.assertComponent("myform", Form.class);
        tester.assertComponent("myform:text", TextField.class);
        tester.assertComponent("myform:listViewContainer", WebMarkupContainer.class);
        tester.assertComponent("myform:listViewContainer:listView", ListView.class);
        tester.assertComponent("myform:listViewContainer:listView:0:label", AjaxLink.class);
      }
    
      @Test public void testAjaxLink() {
        FormTester formTester = tester.newFormTester("myform");
        formTester.setValue("text", "");
        // 0:"apple", 1:"orange", 2:"lemon", 3:"banana", 4:"grape"
        tester.clickLink("myform:listViewContainer:listView:0:label");
        assertEquals("apple", formTester.getTextComponentValue("text"));
        tester.clickLink("myform:listViewContainer:listView:1:label");
        assertEquals("orange", formTester.getTextComponentValue("text"));
      }
    
      @Test public void testAjaxEvent() {
        FormTester formTester = tester.newFormTester("myform");
        final String textPath = "myform:text";
        final String listViewPath = "myform:listViewContainer:listView";
        ListView listView = (ListView) tester.getComponentFromLastRenderedPage(listViewPath);
    
        // 空文字の時はフィルタが働かない。全部表示される。
        formTester.setValue("text", "");
        tester.executeAjaxEvent(textPath, "onchange");
        assertArrayEquals(new String[] { "apple", "orange", "lemon", "banana", "grape" }, 
            listView.getList().toArray());
        // "a"を含まない"lemon"が消える。
        formTester.setValue("text", "a");
        tester.executeAjaxEvent(textPath, "onchange");
        assertArrayEquals(new String[] { "apple", "orange", "banana", "grape" }, 
            listView.getList().toArray());
        // "ap"を含む"apple","grape"が残る。
        formTester.setValue("text", "ap");
        tester.executeAjaxEvent(textPath, "onchange");
        assertArrayEquals(new String[] { "apple", "grape" }, listView.getList().toArray());
      }
    }

コメントを投稿