diff --git a/testing/wicket-js-tests/Gruntfile.js b/testing/wicket-js-tests/Gruntfile.js index b92ccc74f93..c31e090821c 100644 --- a/testing/wicket-js-tests/Gruntfile.js +++ b/testing/wicket-js-tests/Gruntfile.js @@ -8,7 +8,8 @@ * 3) run: npm install (This will use package.json and install grunt and all dependencies) * 4.1) grunt jshint - checks all JavaScript files with JSHint * 4.2) grunt jshint:core - checks only the files in wicket-core - * 4.3) grunt - starts the registered tasks: starting a web server and running all tests (Ajax, non-Ajax and AMD) + * 4.3) grunt - starts the registered tasks: starting a web server and running all tests (Ajax, non-Ajax, XML + * replacement and AMD) */ /*global module: true */ @@ -18,11 +19,10 @@ module.exports = function(grunt) { var coreJs = [ - '../../wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery-debug.js', '../../wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery.js', + '../../wicket-core/src/main/java/org/apache/wicket/ajax/res/js/xml-replacement-method.js', "../../wicket-core/src/main/java/org/apache/wicket/markup/html/form/CheckSelector.js", "../../wicket-core/src/main/java/org/apache/wicket/markup/html/form/upload/MultiFileUploadField.js", - "../../wicket-core/src/main/java/org/apache/wicket/ajax/form/AjaxFormChoiceComponentUpdatingBehavior.js", "../../wicket-core/src/main/java/org/apache/wicket/markup/html/pages/wicket-browser-info.js" ], extensionsJs = [ @@ -30,7 +30,6 @@ module.exports = function(grunt) { "../../wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/wicket-ajaxdownload.js", "../../wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/form/palette/palette.js", "../../wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/autocomplete/wicket-autocomplete.js", - "../../wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/res/modal.js", "../../wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/data/table/filter/wicket-filterform.js" ], nativeWebSocketJs = [ @@ -44,7 +43,8 @@ module.exports = function(grunt) { "../../wicket-core/src/test/js/channels.js", "../../wicket-core/src/test/js/event.js", "../../wicket-core/src/test/js/timer.js", - "../../wicket-core/src/test/js/amd.js" + "../../wicket-core/src/test/js/amd.js", + "../../wicket-core/src/test/js/xml-replacement.js" ], gymTestsJs = [ "../../wicket-examples/src/main/webapp/js-test/tests/ajax/form.js", @@ -121,6 +121,17 @@ module.exports = function(grunt) { 'http://localhost:38887/test/js/amd.html?3.7.1' ] } + }, + + /** + * Run XML reaplcement method tests + */ + "xml-replacement": { + options: { + urls: [ + 'http://localhost:38887/test/js/xml-replacement.html?3.7.1' + ] + } } }, diff --git a/testing/wicket-js-tests/src/test/java/org/apache/wicket/testing/jstest/StartJavaScriptTests.java b/testing/wicket-js-tests/src/test/java/org/apache/wicket/testing/jstest/StartJavaScriptTests.java index 3979abeba0d..e5ce9482f9a 100644 --- a/testing/wicket-js-tests/src/test/java/org/apache/wicket/testing/jstest/StartJavaScriptTests.java +++ b/testing/wicket-js-tests/src/test/java/org/apache/wicket/testing/jstest/StartJavaScriptTests.java @@ -21,7 +21,6 @@ import java.net.URI; import javax.management.MBeanServer; - import org.eclipse.jetty.jmx.MBeanContainer; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; @@ -114,9 +113,9 @@ public static void main(String[] args) try { server.start(); - + browse(); - + server.join(); } catch (Exception e) @@ -130,7 +129,7 @@ private static void browse() { try { - Desktop.getDesktop().browse(new URI("http://localhost:8080/ajax-tests/test/js/all.html?3.6.4")); + Desktop.getDesktop().browse(new URI("http://localhost:8080/ajax-tests/test/js/all.html?3.7.1")); } catch (Exception e) { diff --git a/wicket-core-tests/src/test/java/org/apache/wicket/core/request/handler/AbstractPartialPageRequestHandlerTest.java b/wicket-core-tests/src/test/java/org/apache/wicket/core/request/handler/AbstractPartialPageRequestHandlerTest.java new file mode 100644 index 00000000000..6e29073b390 --- /dev/null +++ b/wicket-core-tests/src/test/java/org/apache/wicket/core/request/handler/AbstractPartialPageRequestHandlerTest.java @@ -0,0 +1,154 @@ +/* + * 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.core.request.handler; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.Collection; +import java.util.List; + +import org.apache.wicket.Component; +import org.apache.wicket.MarkupContainer; +import org.apache.wicket.Page; +import org.apache.wicket.markup.Markup; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.page.PartialPageUpdate; +import org.apache.wicket.page.XmlPartialPageUpdate; +import org.apache.wicket.request.IRequestCycle; +import org.apache.wicket.util.tester.WicketTestCase; +import org.junit.jupiter.api.Test; + +class AbstractPartialPageRequestHandlerTest extends WicketTestCase +{ + @Test + void passesNoReplacementMethodToUpdateForComponentWithOverriddenMarkupIdRegisteredWithout() + { + Label label = tester.startComponentInPage(new Label("some ID")); + ReplacementMethodsExposingUpdate update = new ReplacementMethodsExposingUpdate(label.getPage()); + AbstractPartialPageRequestHandler handler = new TestPartialPageRequestHandler(label.getPage(), update); + + handler.add(label, "the ID"); + + assertNull(update.getReplacementMethodPublic(label.getMarkupId())); + } + + @Test + void passesNoReplacementMethodToUpdateForComponentRegisteredWithout() + { + Component label = tester.startComponentInPage(new Label("the ID").setOutputMarkupId(true)); + ReplacementMethodsExposingUpdate update = new ReplacementMethodsExposingUpdate(label.getPage()); + AbstractPartialPageRequestHandler handler = new TestPartialPageRequestHandler(label.getPage(), update); + + handler.add(label); + + assertNull(update.getReplacementMethodPublic(label.getMarkupId())); + } + + @Test + void passesNoReplacementMethodToUpdateForChildrenRegisteredWithout() + { + Component label = new Label("the ID").setOutputMarkupId(true).setMarkup(Markup.of("")); + MarkupContainer container = new WebMarkupContainer("some ID").add(label); + tester.startComponentInPage(container.setMarkup(Markup.of(""))); + ReplacementMethodsExposingUpdate update = new ReplacementMethodsExposingUpdate(container.getPage()); + AbstractPartialPageRequestHandler handler = new TestPartialPageRequestHandler(container.getPage(), update); + + handler.addChildren(container, Label.class); + + assertNull(update.getReplacementMethodPublic(label.getMarkupId())); + } + + @Test + void passesReplacementMethodToUpdateForComponentWithOverriddenMarkupIdRegisteredWithOne() + { + Label label = tester.startComponentInPage(new Label("some ID")); + ReplacementMethodsExposingUpdate update = new ReplacementMethodsExposingUpdate(label.getPage()); + AbstractPartialPageRequestHandler handler = new TestPartialPageRequestHandler(label.getPage(), update); + + handler.add("theReplacementMethod", label, "the ID"); + + assertEquals("theReplacementMethod", update.getReplacementMethodPublic(label.getMarkupId())); + } + + @Test + void passesReplacementMethodToUpdateForComponentRegisteredWithOne() + { + Component label = tester.startComponentInPage(new Label("the ID").setOutputMarkupId(true)); + ReplacementMethodsExposingUpdate update = new ReplacementMethodsExposingUpdate(label.getPage()); + AbstractPartialPageRequestHandler handler = new TestPartialPageRequestHandler(label.getPage(), update); + + handler.add("theReplacementMethod", label); + + assertEquals("theReplacementMethod", update.getReplacementMethodPublic(label.getMarkupId())); + } + + @Test + void passesReplacementMethodToUpdateForChildrenRegisteredWithOne() + { + Component label = new Label("the ID").setOutputMarkupId(true).setMarkup(Markup.of("")); + MarkupContainer container = new WebMarkupContainer("some ID").add(label); + tester.startComponentInPage(container.setMarkup(Markup.of(""))); + ReplacementMethodsExposingUpdate update = new ReplacementMethodsExposingUpdate(container.getPage()); + AbstractPartialPageRequestHandler handler = new TestPartialPageRequestHandler(container.getPage(), update); + + handler.addChildren("theReplacementMethod", container, Label.class); + + assertEquals("theReplacementMethod", update.getReplacementMethodPublic(label.getMarkupId())); + } + + private static class ReplacementMethodsExposingUpdate extends XmlPartialPageUpdate + { + public ReplacementMethodsExposingUpdate(Page page) + { + super(page); + } + + public String getReplacementMethodPublic(String markupId) { + return getReplacementMethod(markupId); + } + } + + private static class TestPartialPageRequestHandler extends AbstractPartialPageRequestHandler + { + private final ReplacementMethodsExposingUpdate update; + + public TestPartialPageRequestHandler(Page page, ReplacementMethodsExposingUpdate update) + { + super(page); + this.update = update; + } + + @Override + protected PartialPageUpdate getUpdate() + { + return update; + } + + @Override + public Collection getComponents() + { + return List.of(); + } + + @Override + public void respond(IRequestCycle requestCycle) + { + } + } +} diff --git a/wicket-core-tests/src/test/java/org/apache/wicket/core/request/handler/MathmlSubexpressionPanel.java b/wicket-core-tests/src/test/java/org/apache/wicket/core/request/handler/MathmlSubexpressionPanel.java new file mode 100644 index 00000000000..b60eed36149 --- /dev/null +++ b/wicket-core-tests/src/test/java/org/apache/wicket/core/request/handler/MathmlSubexpressionPanel.java @@ -0,0 +1,34 @@ +/* + * 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.core.request.handler; + +import org.apache.wicket.markup.MarkupType; +import org.apache.wicket.markup.html.panel.Panel; + +public class MathmlSubexpressionPanel extends Panel +{ + public MathmlSubexpressionPanel(String id) + { + super(id); + } + + @Override + public MarkupType getMarkupType() + { + return new MarkupType("xml", "application/mathml+xml"); + } +} diff --git a/wicket-core-tests/src/test/java/org/apache/wicket/core/request/handler/MathmlSubexpressionPanel.xml b/wicket-core-tests/src/test/java/org/apache/wicket/core/request/handler/MathmlSubexpressionPanel.xml new file mode 100644 index 00000000000..30fd1ac9b99 --- /dev/null +++ b/wicket-core-tests/src/test/java/org/apache/wicket/core/request/handler/MathmlSubexpressionPanel.xml @@ -0,0 +1,18 @@ + + +2 diff --git a/wicket-core-tests/src/test/java/org/apache/wicket/core/request/handler/XmlReplacementEnablingBehaviorTest.java b/wicket-core-tests/src/test/java/org/apache/wicket/core/request/handler/XmlReplacementEnablingBehaviorTest.java new file mode 100644 index 00000000000..a9d932fa818 --- /dev/null +++ b/wicket-core-tests/src/test/java/org/apache/wicket/core/request/handler/XmlReplacementEnablingBehaviorTest.java @@ -0,0 +1,138 @@ +/* + * 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.core.request.handler; + +import static org.apache.wicket.markup.parser.XmlTag.TagType.CLOSE; +import static org.apache.wicket.markup.parser.XmlTag.TagType.OPEN; +import static org.apache.wicket.markup.parser.XmlTag.TagType.OPEN_CLOSE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.apache.wicket.MarkupContainer; +import org.apache.wicket.markup.ComponentTag; +import org.apache.wicket.markup.IMarkupResourceStreamProvider; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.WebPage; +import org.apache.wicket.util.resource.IResourceStream; +import org.apache.wicket.util.resource.StringResourceStream; +import org.apache.wicket.util.tester.WicketTestCase; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class XmlReplacementEnablingBehaviorTest extends WicketTestCase +{ + private static final String SOME_ELEMENT = "someelement"; + private static final String SOME_ID = "some-id"; + private static final String SOME_NAMESPACE_URI = "some-namespace-uri"; + + @Test + void doesNotAcceptNullNamespaceUri() + { + Assertions.assertThrows(IllegalArgumentException.class, () -> new XmlReplacementEnablingBehavior(null)); + } + + @Test + void addsJavaScriptsNeededForXmlReplacement() + { + tester.startPage(XmlReplacementEnablingBehaviorTest.TestPage.class); + + tester.assertContains(""); + } + + @Test + void doesNotAllowBindingToMultipleComponents() + { + var behavior = new XmlReplacementEnablingBehavior(SOME_NAMESPACE_URI); + behavior.bind(new WebMarkupContainer(SOME_ID)); + + Assertions.assertThrows(IllegalStateException.class, () -> behavior.bind(new WebMarkupContainer(SOME_ID))); + } + + @Test + void enablesOutputOfMarkupIdOnBindingToComponent() + { + var behavior = new XmlReplacementEnablingBehavior(SOME_NAMESPACE_URI); + var component = new WebMarkupContainer(SOME_ID); + behavior.bind(component); + + Assertions.assertTrue(component.getOutputMarkupId()); + } + + @Test + void setsNamespaceUriOnComponentOpenTag() + { + var behavior = new XmlReplacementEnablingBehavior("http://example.com/the-namespace-uri"); + var tag = new ComponentTag(SOME_ELEMENT, OPEN); + + behavior.onComponentTag(new WebMarkupContainer(SOME_ID), tag); + + assertEquals("http://example.com/the-namespace-uri", tag.getAttribute("xmlns")); + } + + @Test + void setsNamespaceUriOnComponentOpenCloseTag() + { + var behavior = new XmlReplacementEnablingBehavior("http://example.com/the-namespace-uri"); + var tag = new ComponentTag(SOME_ELEMENT, OPEN_CLOSE); + + behavior.onComponentTag(new WebMarkupContainer(SOME_ID), tag); + + assertEquals("http://example.com/the-namespace-uri", tag.getAttribute("xmlns")); + } + + @Test + void doesNotSetNamespaceUriOnComponentCloseTag() + { + var behavior = new XmlReplacementEnablingBehavior("http://example.com/the-namespace-uri"); + var tag = new ComponentTag(SOME_ELEMENT, CLOSE); + + behavior.onComponentTag(new WebMarkupContainer(SOME_ID), tag); + + assertNull(tag.getAttribute("xmlns")); + } + + @Test + void disablesRenderingOfWicketTags() + { + tester.startPage(XmlReplacementEnablingBehaviorTest.TestPage.class); + + tester.assertContainsNot(""); + } + + public static class TestPage extends WebPage implements IMarkupResourceStreamProvider + { + /** */ + private static final long serialVersionUID = 1L; + + /** + * Construct. + */ + public TestPage() + { + add(new MathmlSubexpressionPanel("component") + .add(new XmlReplacementEnablingBehavior(XmlReplacementEnablingBehavior.MATHML_NAMESPACE_URI))); + } + + @Override + public IResourceStream getMarkupResourceStream(MarkupContainer container, + Class containerClass) + { + return new StringResourceStream( + ""); + } + } +} diff --git a/wicket-core-tests/src/test/java/org/apache/wicket/core/util/license/ApacheLicenceHeaderTest.java b/wicket-core-tests/src/test/java/org/apache/wicket/core/util/license/ApacheLicenceHeaderTest.java index 5426729976f..baba95da99e 100644 --- a/wicket-core-tests/src/test/java/org/apache/wicket/core/util/license/ApacheLicenceHeaderTest.java +++ b/wicket-core-tests/src/test/java/org/apache/wicket/core/util/license/ApacheLicenceHeaderTest.java @@ -45,6 +45,7 @@ class ApacheLicenceHeaderTest extends ApacheLicenseHeaderTestCase // the licence header breaks the tests in IE htmlIgnore.add("src/test/js/all.html"); htmlIgnore.add("src/test/js/amd.html"); + htmlIgnore.add("src/test/js/xml-replacement.html"); /* * See NOTICE.txt @@ -53,6 +54,7 @@ class ApacheLicenceHeaderTest extends ApacheLicenseHeaderTestCase // the xml prolog breaks the tests in IE xmlPrologIgnore.add("src/test/js/all.html"); xmlPrologIgnore.add("src/test/js/amd.html"); + xmlPrologIgnore.add("src/test/js/xml-replacement.html"); /* * .css in test is very test specific and a license header would confuse and make it unclear diff --git a/wicket-core-tests/src/test/java/org/apache/wicket/page/PageForPartialUpdate.html b/wicket-core-tests/src/test/java/org/apache/wicket/page/PageForPartialUpdate.html index c6a2f30ed87..3ccd564cf34 100644 --- a/wicket-core-tests/src/test/java/org/apache/wicket/page/PageForPartialUpdate.html +++ b/wicket-core-tests/src/test/java/org/apache/wicket/page/PageForPartialUpdate.html @@ -15,5 +15,6 @@ two brackets: ]] greater than: > CDATA end: ]]> + test diff --git a/wicket-core-tests/src/test/java/org/apache/wicket/page/PageForPartialUpdate.java b/wicket-core-tests/src/test/java/org/apache/wicket/page/PageForPartialUpdate.java index 06867563f8b..488fe653cc2 100644 --- a/wicket-core-tests/src/test/java/org/apache/wicket/page/PageForPartialUpdate.java +++ b/wicket-core-tests/src/test/java/org/apache/wicket/page/PageForPartialUpdate.java @@ -29,6 +29,7 @@ public class PageForPartialUpdate extends WebPage private static final long serialVersionUID = 1L; public WebMarkupContainer container; + public WebMarkupContainer alternativeReplacement; /** * Construct. @@ -43,5 +44,8 @@ public void renderHead(IHeaderResponse response) { }; container.setOutputMarkupId(true); add(container); + + alternativeReplacement = new WebMarkupContainer("alternativeReplacement"); + add(alternativeReplacement); } } diff --git a/wicket-core-tests/src/test/java/org/apache/wicket/page/PartialPageUpdateTest.java b/wicket-core-tests/src/test/java/org/apache/wicket/page/PartialPageUpdateTest.java new file mode 100644 index 00000000000..9f9d011b847 --- /dev/null +++ b/wicket-core-tests/src/test/java/org/apache/wicket/page/PartialPageUpdateTest.java @@ -0,0 +1,107 @@ +/* + * 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.page; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.apache.wicket.request.Response; +import org.apache.wicket.request.http.WebResponse; +import org.apache.wicket.util.tester.WicketTestCase; +import org.junit.jupiter.api.Test; + +class PartialPageUpdateTest extends WicketTestCase +{ + @Test + void returnsNoReplacementMethodIfNotSpecified() + { + var page = new PageForPartialUpdate(); + tester.startPage(page); + var update = new TestPartialPageUpdate(page); + + update.add(page.alternativeReplacement, "theMarkupId"); + + assertNull(update.getReplacementMethod("theMarkupId")); + } + + @Test + void returnsNoReplacementMethodIfNull() + { + var page = new PageForPartialUpdate(); + tester.startPage(page); + var update = new TestPartialPageUpdate(page); + + update.add(null, page.alternativeReplacement, "theMarkupId"); + + assertNull(update.getReplacementMethod("theMarkupId")); + } + + @Test + void returnsReplacementMethodIfSpecified() + { + var page = new PageForPartialUpdate(); + tester.startPage(page); + var update = new TestPartialPageUpdate(page); + + update.add("theReplacementMethod", page.alternativeReplacement, "theMarkupId"); + + assertEquals("theReplacementMethod", update.getReplacementMethod("theMarkupId")); + } + + private static class TestPartialPageUpdate extends PartialPageUpdate + { + public TestPartialPageUpdate(PageForPartialUpdate page) + { + super(page); + } + + @Override + protected void writeFooter(Response response, String encoding) + { + } + + @Override + protected void writeHeader(Response response, String encoding) + { + } + + @Override + protected void writeComponent(Response response, String markupId, CharSequence contents) + { + } + + @Override + protected void writePriorityEvaluation(Response response, CharSequence contents) + { + } + + @Override + protected void writeHeaderContribution(Response response, CharSequence contents) + { + } + + @Override + protected void writeEvaluation(Response response, CharSequence contents) + { + } + + @Override + public void setContentType(WebResponse response, String encoding) + { + } + } +} diff --git a/wicket-core-tests/src/test/java/org/apache/wicket/page/XmlPartialPageUpdateTest.java b/wicket-core-tests/src/test/java/org/apache/wicket/page/XmlPartialPageUpdateTest.java index ab82a41742c..2e1204e6790 100644 --- a/wicket-core-tests/src/test/java/org/apache/wicket/page/XmlPartialPageUpdateTest.java +++ b/wicket-core-tests/src/test/java/org/apache/wicket/page/XmlPartialPageUpdateTest.java @@ -39,15 +39,15 @@ class XmlPartialPageUpdateTest extends WicketTestCase void encodeCdataEnd() { PageForPartialUpdate page = new PageForPartialUpdate(); - + XmlPartialPageUpdate update = new XmlPartialPageUpdate(page); - + update.add(page.container, page.container.getMarkupId()); - + MockWebResponse response = new MockWebResponse(); - + update.writeTo(response, "UTF-8"); - + String expected = " two brackets: ]] greater than: > CDATA end: ]]]]> ]]> - diff --git a/wicket-core/src/test/js/data/ajax/alternativeReplacementMethod.xml b/wicket-core/src/test/js/data/ajax/alternativeReplacementMethod.xml new file mode 100644 index 00000000000..ccf682aba25 --- /dev/null +++ b/wicket-core/src/test/js/data/ajax/alternativeReplacementMethod.xml @@ -0,0 +1,18 @@ + + +new body]]> diff --git a/wicket-core/src/test/js/data/ajax/complexComponentIdXml.xml b/wicket-core/src/test/js/data/ajax/complexComponentIdXml.xml new file mode 100644 index 00000000000..f3e9cbe451f --- /dev/null +++ b/wicket-core/src/test/js/data/ajax/complexComponentIdXml.xml @@ -0,0 +1,28 @@ + + + + +2 + +3 + += +6 + +]]> diff --git a/wicket-core/src/test/js/data/ajax/componentIdXml.xml b/wicket-core/src/test/js/data/ajax/componentIdXml.xml new file mode 100644 index 00000000000..c6098c50f2a --- /dev/null +++ b/wicket-core/src/test/js/data/ajax/componentIdXml.xml @@ -0,0 +1,18 @@ + + +2]]> diff --git a/wicket-core/src/test/js/dom.js b/wicket-core/src/test/js/dom.js index 405bfcaaac3..38d4ab6d071 100644 --- a/wicket-core/src/test/js/dom.js +++ b/wicket-core/src/test/js/dom.js @@ -190,6 +190,41 @@ jQuery(document).ready(function() { assert.equal( Wicket.DOM.serializeNode(Wicket.$(toBeReplacedByDivWithChildrenId)).toLowerCase(), complexElMarkup.toLowerCase(), "Wicket.DOM.replace should replace the span with a complex element." ); }); + test("replace() use alternative replacement method if given", assert => { + assert.expect(2); + + var oldReplacementMethods = Wicket.DOM.replacementMethods; + + Wicket.DOM.registerReplacementMethod('alternativeReplacement', function(element, text) { + assert.equal(element, Wicket.$(existingId), 'Element passed to alternative replacement method must be correct.'); + assert.equal(text, '', 'Text passed to alternative replacement method must be new markup.'); + + // restore the original replacement methods + Wicket.DOM.replacementMethods = oldReplacementMethods; + }); + + var el = Wicket.$(existingId); + var someMarkup = ''; + Wicket.DOM.replace(el, someMarkup, 'alternativeReplacement'); + }); + + test("replace() log error if replacement method unknown", assert => { + assert.expect(1); + + var oldWicketLogError = Wicket.Log.error; + + Wicket.Log.error = function() { + assert.equal(arguments[0], "No replacement registerd for type: unknownMethod", "Unknown replacement type is logged."); + + // restore the original method + Wicket.Log.error = oldWicketLogError; + }; + + var el = Wicket.$(existingId); + var someMarkup = ''; + Wicket.DOM.replace(el, someMarkup, 'unknownMethod'); + }); + test("replace - test event notifications", assert => { Wicket.Event.subscribe('/dom/node/removing', function(jqEvent, elementToBeRemoved) { diff --git a/wicket-core/src/test/js/xml-replacement.html b/wicket-core/src/test/js/xml-replacement.html new file mode 100644 index 00000000000..67c956b4b5e --- /dev/null +++ b/wicket-core/src/test/js/xml-replacement.html @@ -0,0 +1,68 @@ + + + + + XML replacement method tests + + + + + +
+ +
+ +
+ + + 1 + + +
+ +
+ + + + 1 + + + 2 + + = + 3 + + +
+ + + + + +
+ + + + + + + + + + + + diff --git a/wicket-core/src/test/js/xml-replacement.js b/wicket-core/src/test/js/xml-replacement.js new file mode 100644 index 00000000000..6c5d29e04f2 --- /dev/null +++ b/wicket-core/src/test/js/xml-replacement.js @@ -0,0 +1,128 @@ +/* + * 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. + */ + +/* + Note: these tests run only through Web Server. + Here is a possible setup for Apache HTTPD: + + Alias /ajax-tests "/path/to/wicket/wicket-core/src" + + + + Options Indexes + AllowOverride None AuthConfig + + Order allow,deny + Allow from all + + + + Or start StartJavaScriptTests.java in project wicket-js-tests. + */ + +/*global ok: true, start: true, test: true, equal: true, deepEqual: true, + QUnit: true, expect: true, console: true */ +jQuery(document).ready(function() { + "use strict"; + + const {module, test} = QUnit; + + var execute = function (attributes, assert, done) { + const done2 = done || assert.async(); + Wicket.testDone = done2; + + var defaults = { + fh: [ + function () { + done2(); + assert.ok(false, 'Failure handler should not be executed!'); + } + ], + ch: '0|s', + sh: [ + function () { + assert.ok(true, 'Success handler is executed'); + } + ] + }; + var attrs = jQuery.extend({}, defaults, attributes); + var call = new Wicket.Ajax.Call(); + call.ajax(attrs); + + }; + + // Ajax tests are executed only when run with Web Server + if (!QUnit.isLocal) { + + module('Wicket.Ajax', { + beforeEach: function () { + // unsubscribe all global listeners + Wicket.Event.unsubscribe(); + } + }); + + test('processComponent(), XML, simple case.', assert => { + const done = assert.async(); + assert.expect(3); + + assert.equal(jQuery('#simpleXml').text(), '1', 'The component is existing and has the old innerHTML'); + + var attrs = { + u: 'data/ajax/componentIdXml.xml', + c: 'componentIdXml', + sh: [ + function () { + done(); + var simpleXml = jQuery('#simpleXml'); + assert.equal(simpleXml.text(), '2', 'The component must be replaced'); + assert.equal(simpleXml[0].namespaceURI, 'http://www.w3.org/1998/Math/MathML', + 'The namespace is set correctly'); + } + ] + }; + execute(attrs, assert, done); + }); + + test('processComponent(), XML, complex case.', assert => { + const done = assert.async(); + Wicket.testDone = done; + Wicket.assert = assert; + assert.expect(2); + + var attrs = { + u: 'data/ajax/complexComponentIdXml.xml', + c: 'complexComponentIdXml', + sh: [ + function () { + done(); + var complexXml = jQuery('#complexXml'); + assert.equal(complexXml.html().trim(), '\n' + + '2\n' + + '\n' + + '3\n' + + '\n' + + '=\n' + + '6', 'The component must be replaced'); + assert.equal(complexXml[0].namespaceURI, 'http://www.w3.org/1998/Math/MathML', + 'The namespace is set correctly'); + } + ] + }; + execute(attrs, assert, done); + }); + } +}); diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/Index.html b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/Index.html index 92a7049ab4f..9aa2712fb3a 100644 --- a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/Index.html +++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/Index.html @@ -37,6 +37,8 @@ World Clock Example: demonstrates a single component with AjaxTimerBehavior updating multiple components

Ajax Download: download initiated via Ajax +

+XML: update and interact with SVG and MathML - \ No newline at end of file + diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/SvgAndMathmlPage.html b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/SvgAndMathmlPage.html new file mode 100644 index 00000000000..c75a5eb379f --- /dev/null +++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/SvgAndMathmlPage.html @@ -0,0 +1,391 @@ + + + + + Wicket Examples - Ajax - XML (SVG and MathML) + + + + +

SVG

+

Click the squares to place your piece, or use the buttons below. Click 'reset' to start over.

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ + + +

+

+ + + +

+

+ + + +

+

+ +

+
+ +

MathML

+

+ Click on the numbers and the operator, or select them from the drop-down menus below. +

+

+ + + 1 + + + 1 + = + 2 + + +

+
+

+ First number: +

+

+ Operator: +

+

+ Second number: +

+
+
+ + diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/SvgAndMathmlPage.java b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/SvgAndMathmlPage.java new file mode 100644 index 00000000000..fc57e56ce56 --- /dev/null +++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/SvgAndMathmlPage.java @@ -0,0 +1,379 @@ +/* + * 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.examples.ajax.builtin; + +import static org.apache.wicket.core.request.handler.XmlReplacementEnablingBehavior.MATHML_NAMESPACE_URI; +import static org.apache.wicket.core.request.handler.XmlReplacementEnablingBehavior.SVG_NAMESPACE_URI; + +import java.util.Arrays; +import java.util.List; + +import org.apache.wicket.ajax.AjaxEventBehavior; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; +import org.apache.wicket.ajax.markup.html.AjaxLink; +import org.apache.wicket.ajax.markup.html.form.AjaxButton; +import org.apache.wicket.behavior.AttributeAppender; +import org.apache.wicket.core.request.handler.XmlReplacementEnablingBehavior; +import org.apache.wicket.markup.head.CssHeaderItem; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.DropDownChoice; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.LambdaModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.request.resource.CssResourceReference; + +/** + * Demo page for XML, showing update of and interaction with SVG and MathML. + */ +public class SvgAndMathmlPage extends BasePage +{ + private static final List ONE_TO_NINE = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9); + + @Override + protected void onInitialize() + { + super.onInitialize(); + + addSvgForm(); + addMathmlForm(); + } + + private void addSvgForm() + { + var ticTacToe = new WebMarkupContainer("ticTacToe"); + ticTacToe.add(new XmlReplacementEnablingBehavior(SVG_NAMESPACE_URI)); + add(ticTacToe); + + var boardStateModel = Model.of(newInitialTicTacToeState()); + var nextTurnModel = Model.of(SquareState.CROSS); + + var svgButtonsForm = new Form<>("svgForm"); + svgButtonsForm.setOutputMarkupId(true); + add(svgButtonsForm); + + var squareLinks = new AjaxLink[9]; + var buttons = new AjaxButton[9]; + for (var column = 'A'; column < 'D'; column += 1) + { + for (var row = 1; row < 4; row += 1) + { + var squareId = String.valueOf(column) + row; + var index = (column - 'A') * 3 + row - 1; + var squareStateModel = LambdaModel.of(boardStateModel, + boardState -> boardState[index], + (boardState, squareState) -> boardState[index] = squareState); + var squareLink = new AjaxLink<>("square" + squareId, squareStateModel) + { + @Override + protected void onConfigure() + { + super.onConfigure(); + + setEnabled(getModelObject() == SquareState.EMPTY); + } + + @Override + public void onClick(AjaxRequestTarget target) + { + setModelObject(nextTurnModel.getObject()); + nextTurnModel.setObject(nextTurnModel.getObject() == SquareState.CROSS ? SquareState.CIRCLE : SquareState.CROSS); + + target.add(XmlReplacementEnablingBehavior.XML, this); + target.add(buttons[index]); + } + }; + squareLink.add(new XmlReplacementEnablingBehavior(SVG_NAMESPACE_URI)); + ticTacToe.add(squareLink); + squareLinks[index] = squareLink; + + var circle = new WebMarkupContainer("circle" + squareId); + circle.add(AttributeAppender.replace("class", boardStateModel.map(boardState -> boardState[index] == SquareState.CIRCLE ? "circle" : "hidden"))); + squareLink.add(circle); + + var cross = new WebMarkupContainer("cross" + squareId); + cross.add(AttributeAppender.replace("class", boardStateModel.map(boardState -> boardState[index] == SquareState.CROSS ? "cross" : "hidden"))); + squareLink.add(cross); + + var button = new AjaxButton("button" + squareId) + { + @Override + protected void onConfigure() + { + super.onConfigure(); + + setEnabled(boardStateModel.getObject()[index] == SquareState.EMPTY); + } + + @Override + protected void onSubmit(AjaxRequestTarget target) + { + boardStateModel.getObject()[index] = nextTurnModel.getObject(); + nextTurnModel.setObject(nextTurnModel.getObject() == SquareState.CROSS ? SquareState.CIRCLE : SquareState.CROSS); + + target.add(XmlReplacementEnablingBehavior.XML, squareLinks[index]); + target.add(this); + } + }; + button.setOutputMarkupId(true); + svgButtonsForm.add(button); + buttons[index] = button; + } + } + + var resetButton = new AjaxButton("reset") + { + @Override + protected void onSubmit(AjaxRequestTarget target) + { + boardStateModel.setObject(newInitialTicTacToeState()); + nextTurnModel.setObject(SquareState.CROSS); + + target.add(XmlReplacementEnablingBehavior.XML, ticTacToe); + target.add(svgButtonsForm); + } + }; + svgButtonsForm.add(resetButton); + } + + private void addMathmlForm() + { + var firstNumberModel = Model.of(1); + var operatorModel = Model.of(Operator.ADD); + var secondNumberModel = Model.of(1); + var outcomeModel = new IModel<>() + { + @Override + public Integer getObject() + { + return operatorModel.getObject().compute( + firstNumberModel.getObject(), + secondNumberModel.getObject()); + } + + @Override + public void detach() + { + firstNumberModel.detach(); + operatorModel.detach(); + secondNumberModel.detach(); + } + }; + + var firstNumberDropDown = new DropDownChoice<>("firstNumberDropDown", firstNumberModel, ONE_TO_NINE); + var operatorDropDown = new DropDownChoice<>("operatorDropDown", operatorModel, OPERATORS); + var secondNumberDropDown = new DropDownChoice<>("secondNumberDropDown", secondNumberModel, ONE_TO_NINE); + + var outcome = new Label("outcome", outcomeModel) + .add(new XmlReplacementEnablingBehavior(MATHML_NAMESPACE_URI)); + var firstNumber = new Label("firstNumber", firstNumberModel) + { + @Override + protected void onInitialize() + { + super.onInitialize(); + + add(new XmlReplacementEnablingBehavior(MATHML_NAMESPACE_URI)); + var labelThis = this; + add(new AjaxEventBehavior("click") + { + @Override + protected void onEvent(AjaxRequestTarget target) + { + var currentValue = firstNumberModel.getObject(); + var newValue = currentValue == 9 ? 1 : currentValue + 1; + firstNumberModel.setObject(newValue); + target.add(XmlReplacementEnablingBehavior.XML, labelThis, outcome); + target.add(firstNumberDropDown); + } + }); + } + }; + var operator = new Label("operator", operatorModel) + { + @Override + protected void onInitialize() + { + super.onInitialize(); + + add(new XmlReplacementEnablingBehavior(MATHML_NAMESPACE_URI)); + var labelThis = this; + add(new AjaxEventBehavior("click") + { + @Override + protected void onEvent(AjaxRequestTarget target) + { + var currentOrdinal = operatorModel.getObject().ordinal(); + var newOrdinal = currentOrdinal == NUMBER_OF_OPERATORS - 1 ? 0 : currentOrdinal + 1; + operatorModel.setObject(Operator.values()[newOrdinal]); + target.add(XmlReplacementEnablingBehavior.XML, labelThis, outcome); + target.add(operatorDropDown); + } + }); + } + }; + var secondNumber = new Label("secondNumber", secondNumberModel) + { + @Override + protected void onInitialize() + { + super.onInitialize(); + + add(new XmlReplacementEnablingBehavior(MATHML_NAMESPACE_URI)); + var labelThis = this; + add(new AjaxEventBehavior("click") + { + @Override + protected void onEvent(AjaxRequestTarget target) + { + var currentValue = secondNumberModel.getObject(); + var newValue = currentValue == 9 ? 1 : currentValue + 1; + secondNumberModel.setObject(newValue); + target.add(XmlReplacementEnablingBehavior.XML, labelThis, outcome); + target.add(secondNumberDropDown); + } + }); + } + }; + add(firstNumber, operator, secondNumber, outcome); + + var form = new Form<>("mathmlForm"); + firstNumberDropDown.setOutputMarkupId(true); + firstNumberDropDown.add(new AjaxFormComponentUpdatingBehavior("change") + { + @Override + protected void onUpdate(AjaxRequestTarget target) + { + target.add(XmlReplacementEnablingBehavior.XML, firstNumber, outcome); + } + }); + operatorDropDown.setOutputMarkupId(true); + operatorDropDown.add(new AjaxFormComponentUpdatingBehavior("change") + { + @Override + protected void onUpdate(AjaxRequestTarget target) + { + target.add(XmlReplacementEnablingBehavior.XML, operator, outcome); + } + }); + secondNumberDropDown.setOutputMarkupId(true); + secondNumberDropDown.add(new AjaxFormComponentUpdatingBehavior("change") + { + @Override + protected void onUpdate(AjaxRequestTarget target) + { + target.add(XmlReplacementEnablingBehavior.XML, secondNumber, outcome); + } + }); + + add( + form.add( + firstNumberDropDown, + operatorDropDown, + secondNumberDropDown + ) + ); + } + + @Override + public void renderHead(IHeaderResponse response) + { + super.renderHead(response); + + response.render(CssHeaderItem.forReference(new CssResourceReference(SvgAndMathmlPage.class, "tic-tac-toe.css"))); + } + + private static SquareState[] newInitialTicTacToeState() + { + return new SquareState[] + { + SquareState.EMPTY, + SquareState.EMPTY, + SquareState.EMPTY, + SquareState.EMPTY, + SquareState.EMPTY, + SquareState.EMPTY, + SquareState.EMPTY, + SquareState.EMPTY, + SquareState.EMPTY + }; + } + + private enum SquareState + { + EMPTY, + CIRCLE, + CROSS + } + + private enum Operator + { + ADD("+") + { + @Override + int compute(int firstNumber, int secondNubmer) + { + return firstNumber + secondNubmer; + } + }, + SUBTRACT("-") + { + @Override + int compute(int firstNumber, int secondNubmer) + { + return firstNumber - secondNubmer; + } + }, + MULTIPLY("×") + { + @Override + int compute(int firstNumber, int secondNubmer) + { + return firstNumber * secondNubmer; + } + }, + DIVIDE("÷") + { + @Override + int compute(int firstNumber, int secondNubmer) + { + return firstNumber / secondNubmer; + } + }; + + private final String text; + + Operator(String text) + { + this.text = text; + } + + abstract int compute(int firstNumber, int secondNubmer); + + @Override + public String toString() + { + return text; + } + } + + private static final List OPERATORS = Arrays.asList(Operator.values()); + private static final int NUMBER_OF_OPERATORS = OPERATORS.size(); +} diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/tic-tac-toe.css b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/tic-tac-toe.css new file mode 100644 index 00000000000..0fc5e8156d5 --- /dev/null +++ b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/tic-tac-toe.css @@ -0,0 +1,38 @@ +/* + * 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. + */ +rect.grid { + fill: #ff9925; +} + +rect.square { + fill: #ffffff; + fill-opacity: 0; +} + +circle.circle { + fill: none; + stroke: #303284; + stroke-width: 3.96875; +} + +g.cross { + fill: #d22128; +} + +circle.hidden, g.hidden { + display: none; +} diff --git a/wicket-user-guide/src/main/asciidoc/ajax/ajax_1.adoc b/wicket-user-guide/src/main/asciidoc/ajax/ajax_1.adoc index cbf096e41b8..853c9f6411e 100644 --- a/wicket-user-guide/src/main/asciidoc/ajax/ajax_1.adoc +++ b/wicket-user-guide/src/main/asciidoc/ajax/ajax_1.adoc @@ -99,3 +99,17 @@ NOTE: During request handling _AjaxRequestHandler_ sends an event to its applica The payload of the event is the _AjaxRequestHandler_ itself. +=== Alternative markup replacement methods + +The replacement of markup can be extended with alternative methods. This is useful for when the jQuery markup replacement does not work properly, is too slow, and/or is missing features. For example: partially updating SVG markup, or updating large pieces of markup of which only a few things have changed. + +Using another replacement method consists of 2 steps: + +* Register the replacement method with Wicket AJAX. This is usually handled by a behavior added to the component for which to use the alternative method. If the method has not been registered when the AJAX response is processed (and Wicket AJAX debug mode is enabled), an error will be logged. +* When adding the component(s) to the _AjaxRequestTarget_, specify the replacement method identifier. The identifier to use usually is a constant of the behavior registering the replacement method. + +*Warning*: not all replacement methods may support all features of or be fully compatible with the default replacement method that uses jQuery. A replacement method may also have additional markup requirements. Check the documentation of the replacement method, and test it to see if it works as desired for your situation. + +Wicket includes the following alternative replacement methods: + +* XML: for replacing markup of SVG and MathML. See _XmlReplacementEnablingBehavior_.