This is an automated email from the ASF dual-hosted git repository. mgrigorov pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/wicket.git
commit d22d154ff4d3245df6ecb52cfb00f412b0a25bef Author: Martin Tzvetanov Grigorov <[email protected]> AuthorDate: Mon Aug 16 11:43:36 2021 +0300 WICKET-6914 Visibility change of "File Upload" via ajax causes "missing" form-data Update the <form>'s enctype whenever a multipart form component is repainted via Ajax (cherry picked from commit 06e9f53dbe79a11bf6512263351edc9481dd5619) --- .../http/MultipartFormComponentListener.java | 52 ++++++++++++++ .../wicket/protocol/http/WebApplication.java | 6 +- .../http/MultipartFormComponentListenerBean.java | 58 ++++++++++++++++ .../http/MultipartFormComponentListenerPage.html | 17 +++++ .../http/MultipartFormComponentListenerPage.java | 80 ++++++++++++++++++++++ .../http/MultipartFormComponentListenerTest.java | 51 ++++++++++++++ 6 files changed, 262 insertions(+), 2 deletions(-) diff --git a/wicket-core/src/main/java/org/apache/wicket/protocol/http/MultipartFormComponentListener.java b/wicket-core/src/main/java/org/apache/wicket/protocol/http/MultipartFormComponentListener.java new file mode 100644 index 0000000..f5572ed --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/protocol/http/MultipartFormComponentListener.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.protocol.http; + +import java.util.Map; + +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.form.FormComponent; +import org.apache.wicket.util.visit.IVisitor; + +/** + * This listener updates the {@link Form}'s <em>enctype</em> whenever a multipart {@link FormComponent} + * is added to the {@code AjaxRequestTarget}. + * This is needed because the multipart form component may change its visibility/enablement and thus + * change the multipart-ness of the whole form. + */ +public class MultipartFormComponentListener implements AjaxRequestTarget.IListener +{ + static final String ENCTYPE_URL_ENCODED = "application/x-www-form-urlencoded"; + + @Override + public void onBeforeRespond(final Map<String, Component> map, final AjaxRequestTarget target) + { + target.getPage().visitChildren(FormComponent.class, (IVisitor<FormComponent<?>, Void>) (formComponent, visit) -> { + if (formComponent.isMultiPart()) + { + Form<?> form = formComponent.getForm(); + boolean multiPart = form.isMultiPart(); + String enctype = multiPart ? Form.ENCTYPE_MULTIPART_FORM_DATA : ENCTYPE_URL_ENCODED; + target.appendJavaScript(String.format("Wicket.$('%s').form.enctype='%s'", + formComponent.getMarkupId(), enctype)); + visit.stop(); + } + }); + } +} diff --git a/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java b/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java index 1f4df9d..9825038 100644 --- a/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java +++ b/wicket-core/src/main/java/org/apache/wicket/protocol/http/WebApplication.java @@ -758,8 +758,10 @@ public abstract class WebApplication extends Application setSessionStoreProvider(HttpSessionStore::new); setAjaxRequestTargetProvider(AjaxRequestHandler::new); - getAjaxRequestTargetListeners().add(new AjaxEnclosureListener()); - + AjaxRequestTargetListenerCollection ajaxRequestTargetListeners = getAjaxRequestTargetListeners(); + ajaxRequestTargetListeners.add(new AjaxEnclosureListener()); + ajaxRequestTargetListeners.add(new MultipartFormComponentListener()); + // Configure the app. configure(); if (getConfigurationType() == RuntimeConfigurationType.DEVELOPMENT) diff --git a/wicket-core/src/test/java/org/apache/wicket/protocol/http/MultipartFormComponentListenerBean.java b/wicket-core/src/test/java/org/apache/wicket/protocol/http/MultipartFormComponentListenerBean.java new file mode 100644 index 0000000..41b6ca4 --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/protocol/http/MultipartFormComponentListenerBean.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.protocol.http; + +import java.io.Serializable; + +public class MultipartFormComponentListenerBean implements Serializable { + private String textField; + private String dropDown; + + /** + * @return the textField + */ + public String getTextField() { + return textField; + } + + + /** + * @return the dropDown + */ + public String getDropDown() { + return dropDown; + } + + + /** + * @param textField + * the textField to set + */ + public void setTextField(String textField) { + this.textField = textField; + } + + + /** + * @param dropDown + * the dropDown to set + */ + public void setDropDown(String dropDown) { + this.dropDown = dropDown; + } + +} diff --git a/wicket-core/src/test/java/org/apache/wicket/protocol/http/MultipartFormComponentListenerPage.html b/wicket-core/src/test/java/org/apache/wicket/protocol/http/MultipartFormComponentListenerPage.html new file mode 100644 index 0000000..eb741e8 --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/protocol/http/MultipartFormComponentListenerPage.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html xmlns:wicket="http://wicket.apache.org"> +<head> +<meta charset="utf-8" /> +<title>Apache Wicket 6914</title> +</head> +<body> + <form wicket:id="form"> + <input type="text" wicket:id="textField" /><br/> + <select wicket:id="dropDown"></select><br/> + <input wicket:id="fileUpload" type="file" /><br/> + <input type="submit" value="save" wicket:id="submitButton" /><br/><br/><br/> + </form> + content of textfield or error: <div wicket:id="label"></div> + +</body> +</html> diff --git a/wicket-core/src/test/java/org/apache/wicket/protocol/http/MultipartFormComponentListenerPage.java b/wicket-core/src/test/java/org/apache/wicket/protocol/http/MultipartFormComponentListenerPage.java new file mode 100644 index 0000000..d0b7842 --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/protocol/http/MultipartFormComponentListenerPage.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.protocol.http; + +import java.util.ArrayList; + +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.form.OnChangeAjaxBehavior; +import org.apache.wicket.markup.html.WebPage; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.Button; +import org.apache.wicket.markup.html.form.DropDownChoice; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.form.RequiredTextField; +import org.apache.wicket.markup.html.form.upload.FileUploadField; +import org.apache.wicket.model.CompoundPropertyModel; +import org.apache.wicket.model.Model; + +public class MultipartFormComponentListenerPage extends WebPage { + private static final long serialVersionUID = 1L; + + public MultipartFormComponentListenerPage() { + CompoundPropertyModel<MultipartFormComponentListenerBean> model = new CompoundPropertyModel<>(new MultipartFormComponentListenerBean()); + Form<MultipartFormComponentListenerBean> form = new Form<>("form", model); + add(form); + + RequiredTextField<String> textField = new RequiredTextField<>("textField"); + form.add(textField); + + ArrayList<String> list = new ArrayList<>(); + list.add("Option 1"); + list.add("Option 2"); + + FileUploadField fileUpload = new FileUploadField("fileUpload", new Model<>(new ArrayList<>())); + fileUpload.setOutputMarkupPlaceholderTag(true); + form.add(fileUpload); + + DropDownChoice<String> dropDown = new DropDownChoice<>("dropDown", list); + dropDown.add(new OnChangeAjaxBehavior() { + + @Override + protected void onUpdate(AjaxRequestTarget target) { + fileUpload.setVisible(!fileUpload.isVisible()); + target.add(fileUpload); + + } + }); + form.add(dropDown); + + final Label label = new Label("label"); + add(label); + + form.add(new Button("submitButton") { + @Override + public void onSubmit() { + label.setDefaultModel(new Model<>(model.getObject().getTextField())); + } + + @Override + public void onError() { + label.setDefaultModel(new Model<>("Validation Error...")); + } + }); + + } +} diff --git a/wicket-core/src/test/java/org/apache/wicket/protocol/http/MultipartFormComponentListenerTest.java b/wicket-core/src/test/java/org/apache/wicket/protocol/http/MultipartFormComponentListenerTest.java new file mode 100644 index 0000000..b5cbf26 --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/protocol/http/MultipartFormComponentListenerTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.protocol.http; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.util.tester.TagTester; +import org.apache.wicket.util.tester.WicketTestCase; +import org.junit.jupiter.api.Test; + +/** + * https://issues.apache.org/jira/browse/WICKET-6914 + */ +class MultipartFormComponentListenerTest extends WicketTestCase +{ + @Test + void updateFormEnctype() + { + tester.startPage(MultipartFormComponentListenerPage.class); + tester.assertRenderedPage(MultipartFormComponentListenerPage.class); + + TagTester formTagTester = tester.getTagByWicketId("form"); + assertEquals(Form.ENCTYPE_MULTIPART_FORM_DATA, formTagTester.getAttribute("enctype")); + + tester.getRequest().setAttribute("form:dropDown", 1); + tester.executeAjaxEvent("form:dropDown", "change"); + String ajaxResponse = tester.getLastResponseAsString(); + assertTrue(ajaxResponse.contains(".form.enctype='" + MultipartFormComponentListener.ENCTYPE_URL_ENCODED + "'})();")); + + tester.getRequest().setAttribute("form:dropDown", 2); + tester.executeAjaxEvent("form:dropDown", "change"); + ajaxResponse = tester.getLastResponseAsString(); + assertTrue(ajaxResponse.contains(".form.enctype='" + Form.ENCTYPE_MULTIPART_FORM_DATA + "'})();")); + } +}
