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

你离开我真会死。 提交于 2019-11-28 00:23:06

Here is an entire example app, built in Vaadin 8.5.1. We present a UUID as text in a TextField, with a button that opens a second window showing a web page with HTML generated by our Vaadin app without using Vaadin widgets or layouts. The id from that field is passed to the new window, which in a real app could be used for a database lookup.

As shown on that page in the manual, you do need to use a BrowserWindowOpener (or a Link). This must be configured ahead of time, before the user clicks the button because of security restrictions common to browsers. So instead of writing code in a button’s click listener, we must earlier configure the BrowserWindowOpener object, and associate with a button.

Define the button to be clicked by the user to generate the report.

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

Define the destination for the new window to be opened, what URL it should use as its web address. We want to call back into our Vaadin app. So get the URL of this web app at runtime. The technical term for our web app in Java Servlet terminology is “context”. So we ask the current context for its URL (path).

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

We need to narrow that URL to our report, detailing a single Person object to be loaded from the database. So we invent person.html as the resource being requested in our URL.

We want to ask for a dynamically generated HTML page without invoking Vaadin widgets, so we use the 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.

With that Resource object in hand, we are ready to define the BrowserWindowOpener.

BrowserWindowOpener webPageOpener = new BrowserWindowOpener( resource );

Let’s configure some of its attributes, such as the title of the window to be opened.

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

And we want to pass the ID of the “person” row to be retrieved from the database and then displayed in our generated web page.

One way to pass information such as this as a parameter in a 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 as our database identifier. We give this value an arbitrary key name such as person_id .

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

We can set the size of the new window to be opened. We will make it the same size as the user’s current window at runtime. And we will make the window resizable, so the user can stretch it larger or smaller. We want to end up with window features described in a string such as width=800,height=600,resizable. We will plug in that width and height at runtime.

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".

We are done configuring the new window to be opened. Since window-opening cannot be called as result of the button-click by user in an event listener as might normally do for other behaviors, we must associate the opener with the button ahead of time.

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

For fun, we can get a preview of the URL to be invoked by the new window. In real work, use a logging framework here such as SLF4J and LogBack. For this demo, we dump to console.

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

Good, we now have button with an opener set to ask for a HTML-based report to be generated. Next we must generate that report. To do that, tell our Vaadin app to expect an incoming URL with the person.html URL we specified above. We do that by implementing the RequestHandler interface. See the manual.

In our RequestHandler we do four things:

  1. Retrieve the hex-string of the UUID passed as a parameter in the query string of the URL opened in the new window.
  2. Reconstitute a UUID object from that hex-string.
  3. Pass that UUID object to a routine generating the HTML to be displayed in this new window.
  4. Display that HTML in the new window by passing it to the VaadinResponse object which gets passed along back to the user’s web browser via Java Servlet technology.

And we must instantiate our RequestHandler implementation, and register the instance with the user’s session, a VaadinSession object.

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
            }
        } );

Fill in that method to generate the 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;
}

Resulting HTML source code looks something like this:

<!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>

For your convenience, here is that entire Vaadin 8 app, the contents of the MyUI.java file first generated by the simplest Maven archetype provided by the Vaadin Ltd company.

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 {
    }
}

You can also do this by passing the html string as a parameter to the UI class for your BrowserWindowOpener:

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));
    }
}

Edit: Using my previous method I encountered a problem where the popup page was not displayed due to a HTTP 400 error. This was due to the http header size was too large (yes I generated a large html page).

My solution to this was to directly generate the StreamResource for the BrowserWindowOpener:

StreamResource streamResource = new StreamResource((StreamResource.StreamSource) () -> 
    new ByteArrayInputStream(htmlString.getBytes()), "report.html");
BrowserWindowOpener opener = new BrowserWindowOpener(streamResource);
opener.extend(myButtonForLaunchingNewWindow);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!