Skip to content

Commit

Permalink
Support @meta annotation (#4378)
Browse files Browse the repository at this point in the history
  • Loading branch information
ZheSun88 authored and Denis committed Jul 10, 2018
1 parent f6ebcd0 commit e5b5ccd
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 16 deletions.
68 changes: 68 additions & 0 deletions flow-server/src/main/java/com/vaadin/flow/component/page/Meta.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2000-2018 Vaadin Ltd.
*
* Licensed 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 com.vaadin.flow.component.page;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Defines a meta tag with customized name and content that will be added to the
* HTML of the host page of a UI class.
*
* @author Vaadin Ltd
* @since 1.1
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Repeatable(Meta.Container.class)
public @interface Meta {
/**
* Gets the custom tag name.
*
* @return the custom tag name
*/
String name();

/**
* Gets the custom tag content.
*
* @return the custom tag content
*/
String content();

/**
* Internal annotation to enable use of multiple {@link Meta} annotations.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Container {
/**
* Internally used to enable use of multiple {@link Meta} annotations.
*
* @return an array of the Meta annotations
*/
Meta[] value();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,10 @@ private static void setupMetaAndTitle(Element head,
.attr(CONTENT_ATTRIBUTE, BootstrapUtils
.getViewportContent(context).orElse(Viewport.DEFAULT));

if (!BootstrapUtils.getMetaTargets(context).isEmpty()) {
BootstrapUtils.getMetaTargets(context).forEach((name,content)->head.appendElement(META_TAG)
.attr("name",name).attr(CONTENT_ATTRIBUTE,content));
}
resolvePageTitle(context).ifPresent(title -> {
if (!title.isEmpty()) {
head.appendElement("title").appendText(title);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand All @@ -38,6 +39,7 @@
import com.vaadin.flow.component.dependency.HtmlImport;
import com.vaadin.flow.component.page.BodySize;
import com.vaadin.flow.component.page.Inline;
import com.vaadin.flow.component.page.Meta;
import com.vaadin.flow.component.page.Viewport;
import com.vaadin.flow.internal.ReflectTools;
import com.vaadin.flow.router.AfterNavigationEvent;
Expand Down Expand Up @@ -112,6 +114,37 @@ static Optional<String> getViewportContent(
.map(Viewport::value);
}

/**
* Returns the map which contains name and content of the customized meta
* tag for the target route chain that was navigated to, specified with
* {@link Meta} on the {@link Route} class or the {@link ParentLayout} of
* the route.
*
* @param context
* the bootstrap context
* @return the map contains name and content value string for the customized
* meta tag
*/
static Map<String, String> getMetaTargets(
BootstrapHandler.BootstrapContext context) {
List<Meta> metaAnnotations = context.getPageConfigurationAnnotations(Meta.class);
boolean illegalValue = false;
Map<String, String> map = new HashMap<>();
for (Meta meta : metaAnnotations) {
if (!meta.name().isEmpty() && !meta.content().isEmpty()) {
map.put(meta.name(), meta.content());
} else {
illegalValue = true;
break;
}
}
if (illegalValue) {
throw new IllegalStateException(
"Meta tags added via Meta annotation contain null value on name or content attribute.");
}
return map;
}

/**
* Get initial page settings if a {@link PageConfigurator} is found for the
* current component tree after navigation has resolved.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.vaadin.flow.server;

import javax.servlet.http.HttpServletRequest;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
Expand All @@ -13,6 +14,17 @@
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.io.IOUtils;
import org.hamcrest.CoreMatchers;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Html;
import com.vaadin.flow.component.Tag;
Expand All @@ -23,6 +35,7 @@
import com.vaadin.flow.component.dependency.StyleSheet;
import com.vaadin.flow.component.page.BodySize;
import com.vaadin.flow.component.page.Inline;
import com.vaadin.flow.component.page.Meta;
import com.vaadin.flow.component.page.TargetElement;
import com.vaadin.flow.component.page.Viewport;
import com.vaadin.flow.internal.UsageStatistics;
Expand All @@ -44,18 +57,9 @@
import com.vaadin.flow.theme.AbstractTheme;
import com.vaadin.flow.theme.Theme;
import com.vaadin.tests.util.MockDeploymentConfiguration;
import org.apache.commons.io.IOUtils;
import org.hamcrest.CoreMatchers;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

import static org.hamcrest.Matchers.is;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
Expand Down Expand Up @@ -1480,6 +1484,82 @@ public void uiInitialization_changingListenersOnEventWorks() {
secondInit.getUI(), uiReference.get());
}

@Route("")
@Tag(Tag.DIV)
@Meta(name = "apple-mobile-web-app-capable", content = "yes")
@Meta(name = "apple-mobile-web-app-status-bar-style", content = "black")
public static class MetaAnnotations extends Component {
}

@Test
public void addMultiMetaTagViaMetaAnnotation_MetaSizeCorrect_ContentCorrect()
throws InvalidRouteConfigurationException {
initUI(testUI, createVaadinRequest(),
Collections.singleton(MetaAnnotations.class));

Document page = BootstrapHandler.getBootstrapPage(
new BootstrapContext(request, null, session, testUI));

Element head = page.head();
Elements metas = head.getElementsByTag("meta");

Assert.assertEquals(5, metas.size());
Element meta = metas.get(0);
assertEquals("Content-Type", meta.attr("http-equiv"));
assertEquals("text/html; charset=utf-8", meta.attr("content"));

meta = metas.get(1);
assertEquals("X-UA-Compatible", meta.attr("http-equiv"));
assertEquals("IE=edge", meta.attr("content"));

meta = metas.get(2);
assertEquals(BootstrapHandler.VIEWPORT, meta.attr("name"));
assertEquals(Viewport.DEFAULT,
meta.attr(BootstrapHandler.CONTENT_ATTRIBUTE));

meta = metas.get(3);
assertEquals("apple-mobile-web-app-status-bar-style",
meta.attr("name"));
assertEquals("black",
meta.attr(BootstrapHandler.CONTENT_ATTRIBUTE));

meta = metas.get(4);
assertEquals("apple-mobile-web-app-capable", meta.attr("name"));
assertEquals("yes",
meta.attr(BootstrapHandler.CONTENT_ATTRIBUTE));
}

@Route("")
@Tag(Tag.DIV)
@Meta(name = "", content = "yes")
public static class MetaAnnotationsContainsNull extends Component {
}

@Test(expected = IllegalStateException.class)
public void AnnotationContainsNullValue_ExceptionThrown()
throws InvalidRouteConfigurationException {
initUI(testUI, createVaadinRequest(),
Collections.singleton(MetaAnnotationsContainsNull.class));

Document page = BootstrapHandler.getBootstrapPage(
new BootstrapContext(request, null, session, testUI));
}

@Tag(Tag.DIV)
@Meta(name = "apple-mobile-web-app-capable", content = "yes")
public static class MetaAnnotationsWithoutRoute extends Component {
}

@Test(expected = InvalidRouteConfigurationException.class)
public void AnnotationsWithoutRoute_ExceptionThrown()
throws InvalidRouteConfigurationException {
initUI(testUI, createVaadinRequest(),
Collections.singleton(MetaAnnotationsWithoutRoute.class));

Document page = BootstrapHandler.getBootstrapPage(
new BootstrapContext(request, null, session, testUI));
}

private void assertStringEquals(String message, String expected,
String actual) {
Assert.assertThat(message,
Expand Down Expand Up @@ -1573,18 +1653,25 @@ public void viewportAnnotationOverridesDefault() throws Exception {

@Test
public void testUIConfiguration_usingPageSettings() throws Exception {
Assert.assertTrue("By default loading indicator is themed", testUI.getLoadingIndicatorConfiguration().isApplyDefaultTheme());
Assert.assertTrue("By default loading indicator is themed", testUI
.getLoadingIndicatorConfiguration().isApplyDefaultTheme());

initUI(testUI, createVaadinRequest(), Collections.singleton(InitialPageConfiguratorRoute.class));
initUI(testUI, createVaadinRequest(),
Collections.singleton(InitialPageConfiguratorRoute.class));
Document page = BootstrapHandler.getBootstrapPage(
new BootstrapContext(request, null, session, testUI));

Assert.assertFalse("Default indicator theme is not themed anymore", testUI.getLoadingIndicatorConfiguration().isApplyDefaultTheme());
Assert.assertFalse("Default indicator theme is not themed anymore",
testUI.getLoadingIndicatorConfiguration()
.isApplyDefaultTheme());

Assert.assertEquals(InitialPageConfiguratorRoute.SECOND_DELAY, testUI.getLoadingIndicatorConfiguration().getSecondDelay());
Assert.assertEquals(InitialPageConfiguratorRoute.SECOND_DELAY,
testUI.getLoadingIndicatorConfiguration().getSecondDelay());

Assert.assertEquals(PushMode.MANUAL, testUI.getPushConfiguration().getPushMode());
Assert.assertEquals(PushMode.MANUAL,
testUI.getPushConfiguration().getPushMode());

Assert.assertTrue(testUI.getReconnectDialogConfiguration().isDialogModal());
Assert.assertTrue(
testUI.getReconnectDialogConfiguration().isDialogModal());
}
}

0 comments on commit e5b5ccd

Please sign in to comment.