从 Vaadin 8 应用生成 HTML 页面,并在新的 window 中打开

Generate an HTML page, and open in a new window, from a Vaadin 8 app

在我的 Vaadin 8 网络应用程序中,我希望用户能够通过单击按钮在另一个 window 中打开报告。内容将由 Vaadin 应用程序使用普通 HTML5 而不是使用 Vaadin 小部件生成。

Vaadin 8 手册有一页 Handling Browser Windows。它显示了使用 BrowserWindowOpener 对象打开一个新的 window。但是 window 包含一个 Vaadin UI 子类,而我想生成我自己的 HTML 内容。

传递数据库标识符值等信息的奖励积分。

这是在 Vaadin 8.5.1 中构建的完整示例应用程序。我们在 TextField 中将 UUID 作为文本显示,并使用一个按钮打开第二个 window,显示由我们的 Vaadin 应用程序生成的带有 HTML 的网页,而无需使用 Vaadin 小部件或布局。来自那个字段的 id 被传递给新的 window,它在真实的应用程序中可以用于数据库查找。

如手册中该页所示,您确实需要使用 BrowserWindowOpener (or a Link)。这必须提前配置, 用户单击按钮之前,因为浏览器通用的安全限制。因此,与其在按钮的点击侦听器中编写代码,不如我们必须提前配置 BrowserWindowOpener object,并与按钮相关联。

定义用户单击以生成报告的按钮。

Button webPageButton = new Button( "Generate Person report" );

定义要打开的新 window 的目的地,URL 它应该用作其网址。我们想回调我们的 Vaadin 应用程序。因此在运行时获取此 Web 应用程序的 URL。 Java Servlet 术语中我们的 Web 应用程序的技术术语是“上下文”。所以我们向当前上下文询问其 URL (路径)。

String servletPath = VaadinServlet.getCurrent().getServletContext().getContextPath(); // URL for this web app at runtime.

我们需要将 URL 缩小到我们的报告,详细说明要从数据库加载的单个 Person object。所以我们发明 person.html 作为我们 URL 中请求的资源。

我们想要在不调用 Vaadin 小部件的情况下请求动态生成的 HTML 页面,因此我们使用 ExternalResource class.

Resource resource = new ExternalResource( servletPath + "/person.html" );  // Defining an external resource as a URL that is not really so external -- will call back into this same web app.

有了 Resource object,我们就可以定义 BrowserWindowOpener.

BrowserWindowOpener webPageOpener = new BrowserWindowOpener( resource );

我们来配置它的一些属性,比如要打开的window的标题

webPageOpener.setWindowName( "Person ID: " + personUuid.getValue() );  // Set title of the new window to be opened.

并且我们要传递要从数据库中检索的“人员”行的 ID,然后显示在我们生成的网页中。

一种将诸如此类的信息作为参数传递到 query string on the URL. So the last part of our URL will look something like person.html?person_id= f0e32ddc-18ed-432c-950b-eda3f3e4a80d. This value must be textual, so we use the canonical 36-character hex string representing the 128-bits of a UUID 作为我们的数据库标识符的方法。我们给这个值一个任意的键名,例如 person_id .

String param = "person_id";
webPageOpener.setParameter( param , personUuid.getValue() );

我们可以设置新window打开的大小。我们将在运行时使其与用户当前的 window 大小相同。我们将使 window 可调整大小,以便用户可以将其拉伸得更大或更小。我们希望以 width=800,height=600,resizable 等字符串中描述的 window 特征结束。我们将在运行时插入该宽度和高度。

String windowFeaturesString = String.format( "width=%d,height=%d,resizable" , Page.getCurrent().getBrowserWindowWidth() , Page.getCurrent().getBrowserWindowHeight() ) ; // Same size as original window.
webPageOpener.setFeatures( windowFeaturesString );  // Example: "width=800,height=600,resizable".

我们已完成配置要打开的新 window。由于 window-opening 不能像通常对其他行为那样由用户在事件侦听器中作为 button-click 的结果调用,因此我们必须提前将开启器与按钮相关联。

webPageOpener.extend( webPageButton ); // Associate opener with button.

为了好玩,我们可以预览 URL 将被新的 window 调用。在实际工作中,这里使用日志框架,例如SLF4J and LogBack。对于这个演示,我们转储到控制台。

System.out.println( "TRACE BrowserWindowOpener URL: " + webPageOpener.getUrl() );

很好,我们现在有一个带有开场白的按钮,要求生成 HTML-based 报告。接下来我们必须生成该报告。为此,请告诉我们的 Vaadin 应用程序期望传入的 URL 带有我们在上面指定的 person.html URL。我们通过实施 RequestHandler interface. See the manual.

来做到这一点

在我们的 RequestHandler 中,我们做了四件事:

  1. 检索在新 window 打开的 URL 的查询字符串中作为参数传递的 UUID 的 hex-string。
  2. 从 hex-string.
  3. 重构 UUID object
  4. UUID object 传递给生成要在此新 window 中显示的 HTML 的例程。
  5. 通过将 HTML 传递给 VaadinResponse object 在新 window 中显示 HTML,后者通过 [=117= 传递回用户的 Web 浏览器] Servlet技术。

而且我们必须实例化我们的RequestHandler实现,并用用户的session注册实例,一个VaadinSessionobject.

VaadinSession.getCurrent().addRequestHandler(
        new RequestHandler() {
            @Override
            public boolean handleRequest ( VaadinSession session ,
                                           VaadinRequest request ,
                                           VaadinResponse response )
                    throws IOException {
                if ( "/panel.html".equals( request.getPathInfo() ) ) {
                    // Retrieve the hex-string of the UUID from the URL’s query string parameter.
                    String uuidString = request.getParameter( "person_id" );  // In real-work, validate the results here.
                    UUID uuid = UUID.fromString( uuidString ); // Reconstitute a `UUID` object from that hex-string. In real-work, validate the results here.
                    System.out.println( "UUID object reconstituted from string passed as parameter in query string of URL opened in new window: " + uuid );
                    // Build HTML.
                    String html = renderHtml( uuid );
                    // Send out the generated text as HTML, in UTF-8 character encoding.
                    response.setContentType( "text/html; charset=utf-8" );
                    response.getWriter().append( html );
                    return true; // We wrote a response
                } else
                    return false; // No response was written
            }
        } );

填写生成HTML的方法。

// Generate the HTML to report on the details of a `person` from the database, given the UUID of that database row.
private String renderHtml ( UUID uuid ) {
    String eol = "\n"; // End-of-line character(s) to use in the HTML.
    StringBuilder html = new StringBuilder();
    html.append( "<!DOCTYPE html>" ).append( eol );
    html.append( "<html>" ).append( eol );
    html.append( "<head>" ).append( eol );
    html.append( "<title>Person</title>" ).append( eol );
    html.append( "</head>" ).append( eol );
    html.append( "<body style='color:DarkSlateGray' >" ).append( eol );
    html.append( "<h1>Demo</h1>" ).append( eol );
    html.append( "<p>This is a drill. This is only a drill.</p>" ).append( eol );
    html.append( "<p>If this had been a real application, you would have seen some data.</p>" ).append( eol );
    html.append( "<p>Person ID: " ).append( uuid.toString() ).append( ".</p>" ).append( eol );
    html.append( "<p style='color:DimGray ; font-family: Pragmata Hack Menlo monospaced' >Report generated " ).append( Instant.now() ).append( ".</p>" ).append( eol );
    html.append( "</body>" ).append( eol );
    html.append( "</html>" ).append( eol );
    String s = html.toString();
    return s;
}

生成的 HTML 源代码如下所示:

<!DOCTYPE html>
<html>
<head>
<title>Person</title>
</head>
<body style='color:DarkSlateGray' >
<h1>Demo</h1>
<p>This is a drill. This is only a drill.</p>
<p>If this had been a real application, you would have seen some data.</p>
<p>Person ID: cc5e975b-2632-4c92-a1cb-b25085c60e60.</p>
<p style='color:DimGray ; font-family: Pragmata , Hack , Menlo , monospace' >Report generated 2018-08-05T02:33:13.028594Z.</p>
</body>
</html>

为方便起见,这里是整个 Vaadin 8 应用程序,MyUI.java 文件的内容首先由 Vaadin Ltd 公司提供的最简单的 Maven 原型生成。

package com.basilbourque.example;

import javax.servlet.annotation.WebServlet;

import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.server.*;
import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;

import java.io.IOException;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.UUID;

/**
 * This UI is the application entry point. A UI may either represent a browser window
 * (or tab) or some part of an HTML page where a Vaadin application is embedded.
 * <p>
 * The UI is initialized using {@link #init(VaadinRequest)}. This method is intended to be
 * overridden to add component to the user interface and initialize non-component functionality.
 */
@Theme ( "mytheme" )
public class MyUI extends UI {

    @Override
    protected void init ( VaadinRequest vaadinRequest ) {
        final VerticalLayout layout = new VerticalLayout();

        TextField personUuid = new TextField( "UUID of Person:" );
        personUuid.setWidth( 22 , Unit.EM );
        personUuid.setValue( UUID.randomUUID().toString() );
        personUuid.setReadOnly( true );

        Button webPageButton = new Button( "Generate Person report" );
        webPageButton.setWidthUndefined();
        webPageButton.addClickListener( e -> {
            System.out.println( "Button clicked. " + ZonedDateTime.now() );
        } );

        // Configure web page opener object. Must be done *before* user clicks on button, not after.
        String servletPath = VaadinServlet.getCurrent().getServletContext().getContextPath(); // URL for this web app at runtime.
        Resource resource = new ExternalResource( servletPath + "/person.html" );  // Defining an external resource as a URL that is not really so external -- will call back into this same web app.
        BrowserWindowOpener webPageOpener = new BrowserWindowOpener( resource );
        webPageOpener.setWindowName( "Person ID: " + personUuid.getValue() );  // Set title of the new window to be opened.
        String param = "person_id";
        webPageOpener.setParameter( param , personUuid.getValue() );
        String windowFeaturesString = String.format( "width=%d,height=%d,resizable" , Page.getCurrent().getBrowserWindowWidth() , Page.getCurrent().getBrowserWindowHeight() ); // Same size as original window.
        webPageOpener.setFeatures( windowFeaturesString );  // Example: "width=800,height=600,resizable".
        webPageOpener.extend( webPageButton ); // Connect opener with button.
        System.out.println( "TRACE BrowserWindowOpener URL: " + webPageOpener.getUrl() );

        layout.addComponents( personUuid , webPageButton );
        setContent( layout );

        // A request handler for generating some content
        VaadinSession.getCurrent().addRequestHandler(
                new RequestHandler() {
                    @Override
                    public boolean handleRequest ( VaadinSession session ,
                                                   VaadinRequest request ,
                                                   VaadinResponse response )
                            throws IOException {
                        if ( "/person.html".equals( request.getPathInfo() ) ) {
                            // Retrieve the hex-string of the UUID from the URL’s query string parameter.
                            String uuidString = request.getParameter( "person_id" );  // In real-work, validate the results here.
                            UUID uuid = UUID.fromString( uuidString ); // Reconstitute a `UUID` object from that hex-string. In real-work, validate the results here.
                            System.out.println( "UUID object reconstituted from string passed as parameter in query string of URL opened in new window: " + uuid );
                            // Build HTML.
                            String html = renderHtml( uuid );
                            // Send out the generated text as HTML, in UTF-8 character encoding.
                            response.setContentType( "text/html; charset=utf-8" );
                            response.getWriter().append( html );
                            return true; // We wrote a response
                        } else
                            return false; // No response was written
                    }
                } );
    }

    // Generate the HTML to report on the details of a `person` from the database, given the UUID of that database row.
    private String renderHtml ( UUID uuid ) {
        String eol = "\n"; // End-of-line character(s) to use in the HTML.
        StringBuilder html = new StringBuilder();
        html.append( "<!DOCTYPE html>" ).append( eol );
        html.append( "<html>" ).append( eol );
        html.append( "<head>" ).append( eol );
        html.append( "<title>Person</title>" ).append( eol );
        html.append( "</head>" ).append( eol );
        html.append( "<body style='color:DarkSlateGray' >" ).append( eol );
        html.append( "<h1>Demo</h1>" ).append( eol );
        html.append( "<p>This is a drill. This is only a drill.</p>" ).append( eol );
        html.append( "<p>If this had been a real application, you would have seen some data.</p>" ).append( eol );
        html.append( "<p>Person ID: " ).append( uuid.toString() ).append( ".</p>" ).append( eol );
        html.append( "<p style='color:DimGray ; font-family: Pragmata , Hack , Menlo , monospace' >Report generated " ).append( Instant.now() ).append( ".</p>" ).append( eol );
        html.append( "</body>" ).append( eol );
        html.append( "</html>" ).append( eol );
        String s = html.toString();
        System.out.println( "\n\n" + s + "\n\n" );
        return s;
    }

    @WebServlet ( urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true )
    @VaadinServletConfiguration ( ui = MyUI.class, productionMode = false )
    public static class MyUIServlet extends VaadinServlet {
    }
}

您也可以通过将 html 字符串作为参数传递给 BrowserWindowOpener 的 UI class 来执行此操作:

BrowserWindowOpener opener = new BrowserWindowOpener(MyUI.class);
opener.extend(myButtonForLaunchingNewWindow);
opener.setParameter("text",myHtmlStringWhichIJustGenerated);

public static class MyUI extends UI {
    @Override
    protected void init(VaadinRequest request) {

        String text = request.getParameter("text");
        // Have some content to print
        setContent(new Label(
                text,
                ContentMode.HTML));
    }
}

编辑:使用我以前的方法时,我遇到了一个问题,即由于 HTTP 400 错误而无法显示弹出页面。这是因为 http header 大小太大(是的,我生成了一个大 html 页面)。

我的解决方案是直接为 BrowserWindowOpener 生成 StreamResource:

StreamResource streamResource = new StreamResource((StreamResource.StreamSource) () -> 
    new ByteArrayInputStream(htmlString.getBytes()), "report.html");
BrowserWindowOpener opener = new BrowserWindowOpener(streamResource);
opener.extend(myButtonForLaunchingNewWindow);