Wicket: FileUploadField with ListView

2019-07-03 23:59发布

  • I have a page that is used to upload several files. For each file, the user must specify a type and a description, that's why I can't use MultiFileUploadField... so I use a RepeatingView with a FileUploadField in each element along with the other two fields I need.

  • The problem is that whenever the "add File" button is clicked (AjaxLink), the FileUploadFields that already had a file, are reset to null...

What can I do?

Here is the ListView (sorry, it wasn't a RepeatingView but a ListView):

IModel<List<EtdDokument>> listModel = getListModel();
ListView<EtdDokument> dokumenteList = new ListView<EtdDokument>("dokumenteList", listModel) {

    private static final long serialVersionUID = 1L;

    @Override
    protected void populateItem(ListItem<EtdDokument> item) {
        final boolean showHeaders = ((getList() == null) || getList().size() == 0);

        final WebMarkupContainer headRow = new WebMarkupContainer("headRow");
        headRow.setVisible(showHeaders);
        item.add(headRow);

        EtdDokumentRowPanel etdDokumentRow = new EtdDokumentRowPanel("bodyRow", item.getModel());
        item.add(etdDokumentRow);

    }
};
dokumenteList.setReuseItems(true);
add(dokumenteList);

AjaxLink<Void> addLink = new AjaxLink<Void>("addDokument") {

    private static final long serialVersionUID = 1L;

    @Override
    public void onClick(AjaxRequestTarget target) {
        EtdConfiguration etdConfig = EtdConfigForm.this.getModelObject();
        final EtdDokument newValue = new EtdDokument(etdConfig);
        tempEtdDokumente.add(newValue);
        target.addComponent(EtdConfigForm.this);
    }
};
add(addLink);
  • The EtdDokumentRowPanel has nothing interesting, I just show a FileUploadField, a TextField for the file description and a DropDownChoice to select the type of document (our own classification).

1条回答
手持菜刀,她持情操
2楼-- · 2019-07-04 00:22

Ok, it's a bit tricky, because html's <input type="file"> losts it's selection after any update, and real model object (choosen file) for FileUploadField is set only when post event occurs. So, even if we add AjaxEventBehavior with onchange event into our file panel - it's model will be null after user selects file.

Actually, we have access to the chosen file before post request only from js and you can implement 'saving' script, to hold already selected files in some array, while ajax update is proccessed, and then set them back, but it is tediously.

So, another way to solve this issue is to proccess update only on newly added component and don't touch others.

I will use RepeatingView, because I need to generate new wicket ids only when I click addLink and not according to the model. Here's the code (read comments carefully):

/* Method, which init your form */
private void init()
{
    /* Container, which will hold all FileUploadFields,
       as RepeatingView adds children to it's parent object. */
    WebMarkupContainer container = new WebMarkupContainer("container");

    /* We need DOM id for container component. */
    container.setOutputMarkupId(true);
    add(container);

    final RepeatingView rv = new RepeatingView("dokumenteList");
    container.add (rv);

    /* We need to add all default model values by ourselfs for RepeatingView: */
    for(EtdDokument doc : getListModel().getObject())
    {
        createDocumentRow(rv, doc);
    }

    final AjaxLink<String> addLink = new AjaxLink<String>("addDokument") {
        @Override
        public void onClick(AjaxRequestTarget target) {
            final EtdDokument newValue...;
            final EtdDokumentRowPanel r = createDocumentRow(rv, newValue);

            ...

            /* This is it. We dynamicly adding created earlier component to markup.
               This allows us to update this component via ajax. */
            target.prependJavaScript(
                    "var item=document.createElement('div');" + //creating empty 'div' tag
                    "item.id='" + r.getMarkupId() + "'; " + // set markup id for this 'div'.
                    "Wicket.$('" + container.getMarkupId() + "').appendChild(item);" // add this 'div' as container child.
            );

            /* Added 'div' is still empty, but this update will replace
               it, by real component's markup.*/
            target.add(r);
        }
    };
    add(addLink);
}

/* This method creates new instance of EDRowP (with random id) and adds
   it to RepeatingView. 
   I have dropped the implementation of your headRow, but you can include it 
   into the EDRowPanel or implement something similar. 
*/
private EtdDokumentRowPanel createDocumentRow( RepeatingView rv, EtdDokument doc )
{
    EtdDokumentRowPanel row = new EtdDokumentRowPanel(rv.newChildId(), doc);
    rv.add(row);
    return row;
}

And in markup:

<form...>
    ...
    <div wicket:id="container">
        <div wicket:id="dokumenteList"></div>
    </div>
    <a href wicket:id="addDokument">Add</a>
    ....
</form>

It looks like too cumbersome solution for small problem, but I think, that there is no more elegant one (or may be I am too sleepy now, to see it ). And this should work.

查看更多
登录 后发表回答