Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Programmatically creating facelets with @View doesn't work #444

Open
simasch opened this issue May 28, 2024 · 8 comments
Open

Programmatically creating facelets with @View doesn't work #444

simasch opened this issue May 28, 2024 · 8 comments
Assignees
Labels
bug Something isn't working

Comments

@simasch
Copy link

simasch commented May 28, 2024

I tried to add a programmatic view with the @View annotation, but when I access http://localhost:8080/facelet.xhml it returns 404

@View("/facelet.xhtml")
@ApplicationScoped
public class FaceletView extends Facelet {

    @Override
    public void apply(FacesContext facesContext, UIComponent parent) {
        if (!facesContext.getAttributes().containsKey(IS_BUILDING_INITIAL_STATE)) {
            return;
        }

        var components = new ComponentBuilder(facesContext);
        var rootChildren = parent.getChildren();

        var doctype = new UIOutput();
        doctype.setValue("<!DOCTYPE html>");
        rootChildren.add(doctype);

        var htmlTag = new UIOutput();
        htmlTag.setValue("<html xmlns=\"http://www.w3.org/1999/xhtml\">");
        rootChildren.add(htmlTag);

        HtmlBody body = components.create(HtmlBody.COMPONENT_TYPE);
        rootChildren.add(body);

        HtmlForm form = components.create(HtmlForm.COMPONENT_TYPE);
        form.setId("form");
        body.getChildren().add(form);

        HtmlOutputText message = components.create(HtmlOutputText.COMPONENT_TYPE);
        message.setId("message");

        HtmlCommandButton actionButton = components.create(HtmlCommandButton.COMPONENT_TYPE);
        actionButton.setId("button");
        actionButton.addActionListener(
                e -> message.setValue("Hello, World! Welcome to Faces 4.0 on Jakarta EE 10"));
        actionButton.setValue("Greet");

        form.getChildren().add(actionButton);

        parent.getChildren().add(message);

        htmlTag = new UIOutput();
        htmlTag.setValue("</html>");
        rootChildren.add(htmlTag);
    }

    private static class ComponentBuilder {
        FacesContext facesContext;

        ComponentBuilder(FacesContext facesContext) {
            this.facesContext = facesContext;
        }

        @SuppressWarnings("unchecked")
        <T> T create(String componentType) {
            return (T) facesContext.getApplication().createComponent(facesContext, componentType, null);
        }
    }
}

@melloware
Copy link
Owner

I wonder if nobody has tried this yet and the Quarkus MyFaces extension is not scanning for @View. I will investigate.

@melloware melloware added the bug Something isn't working label May 28, 2024
@melloware melloware self-assigned this May 28, 2024
@melloware
Copy link
Owner

Standalone Jetty runnner of the example above just so i could make sure it works outside of Quarkus.
quarkus-444.zip

mvn clean jetty:run and navigate to http://localhost:8080/facelet.xhtml

@melloware
Copy link
Owner

I opened a MyFaces Quarkus Extension ticket: https://issues.apache.org/jira/browse/MYFACES-4668

@melloware
Copy link
Owner

Also opened this: https://issues.apache.org/jira/browse/MYFACES-4669

@melloware
Copy link
Owner

I have a fix for Quarkus but waiting to hear back on the escaping issue with the UIOutput

@melloware
Copy link
Owner

ok for MyFaces I had to update your example to be this using DocType and removing the <html> tag.

package org.primefaces.test;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.faces.annotation.View;
import jakarta.faces.application.StateManager;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.html.HtmlBody;
import jakarta.faces.component.html.HtmlCommandButton;
import jakarta.faces.component.html.HtmlDoctype;
import jakarta.faces.component.html.HtmlForm;
import jakarta.faces.component.html.HtmlOutputText;
import jakarta.faces.context.FacesContext;
import jakarta.faces.view.facelets.Facelet;

@View("/facelet.xhtml")
@ApplicationScoped
public class FaceletView extends Facelet {

    @Override
    public void apply(FacesContext facesContext, UIComponent parent) {
        if (!facesContext.getAttributes().containsKey(StateManager.IS_BUILDING_INITIAL_STATE)) {
            return;
        }

        var components = new ComponentBuilder(facesContext);
        var rootChildren = parent.getChildren();

        var htmlDoctype = new HtmlDoctype();
        htmlDoctype.setRootElement("html");
        rootChildren.add(htmlDoctype);

        HtmlBody body = components.create(HtmlBody.COMPONENT_TYPE);
        rootChildren.add(body);

        HtmlForm form = components.create(HtmlForm.COMPONENT_TYPE);
        form.setId("form");
        body.getChildren().add(form);

        HtmlOutputText message = components.create(HtmlOutputText.COMPONENT_TYPE);
        message.setId("message");

        HtmlCommandButton actionButton = components.create(HtmlCommandButton.COMPONENT_TYPE);
        actionButton.setId("button");
        actionButton.addActionListener(
                e -> message.setValue("Hello, World! Welcome to Faces 4.0 on Jakarta EE 10"));
        actionButton.setValue("Greet");

        form.getChildren().add(actionButton);

        body.getChildren().add(message);
    }

    private static class ComponentBuilder {
        FacesContext facesContext;

        ComponentBuilder(FacesContext facesContext) {
            this.facesContext = facesContext;
        }

        @SuppressWarnings("unchecked")
        <T> T create(String componentType) {
            try {
                return (T) facesContext.getApplication().createComponent(componentType);
            } catch (ClassCastException e) {
                throw new IllegalArgumentException("Component type " + componentType + " is not valid.", e);
            }
        }
    }
}

@melloware
Copy link
Owner

I also asked on Mojarra which is the correct behavior eclipse-ee4j/mojarra#5452

@melloware
Copy link
Owner

Got to the bottom of it. Mojarra is wrong and Myfaces is correct: jakartaee/faces#1796

To do it correctly escape must be set on the UI output like this.

package org.primefaces.test;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.faces.annotation.View;
import jakarta.faces.application.StateManager;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.UIOutput;
import jakarta.faces.component.html.HtmlBody;
import jakarta.faces.component.html.HtmlCommandButton;
import jakarta.faces.component.html.HtmlDoctype;
import jakarta.faces.component.html.HtmlForm;
import jakarta.faces.component.html.HtmlOutputText;
import jakarta.faces.context.FacesContext;
import jakarta.faces.view.facelets.Facelet;

@View("/facelet.xhtml")
@ApplicationScoped
public class FaceletView extends Facelet {

    @Override
    public void apply(FacesContext facesContext, UIComponent parent) {
        if (!facesContext.getAttributes().containsKey(StateManager.IS_BUILDING_INITIAL_STATE)) {
            return;
        }

        var components = new ComponentBuilder(facesContext);
        var rootChildren = parent.getChildren();

        var htmlDoctype = new HtmlDoctype();
        htmlDoctype.setRootElement("html");
        rootChildren.add(htmlDoctype);

        UIOutput output = new UIOutput();
        output.setValue("<html xmlns=\"http://www.w3.org/1999/xhtml\">");
        output.getAttributes().put("escape", false);
        rootChildren.add(output);

        HtmlBody body = components.create(HtmlBody.COMPONENT_TYPE);
        rootChildren.add(body);

        HtmlForm form = components.create(HtmlForm.COMPONENT_TYPE);
        form.setId("form");
        body.getChildren().add(form);

        HtmlOutputText message = components.create(HtmlOutputText.COMPONENT_TYPE);
        message.setId("message");

        HtmlCommandButton actionButton = components.create(HtmlCommandButton.COMPONENT_TYPE);
        actionButton.setId("button");
        actionButton.addActionListener(
                e -> message.setValue("Hello, World! Welcome to Faces 4.0 on Jakarta EE 10"));
        actionButton.setValue("Greet");

        form.getChildren().add(actionButton);

        body.getChildren().add(message);

        output = new UIOutput();
        output.setValue("</html>");
        output.getAttributes().put("escape", false);
        rootChildren.add(output);
    }

    private static class ComponentBuilder {
        FacesContext facesContext;

        ComponentBuilder(FacesContext facesContext) {
            this.facesContext = facesContext;
        }

        @SuppressWarnings("unchecked")
        <T> T create(String componentType) {
            try {
                return (T) facesContext.getApplication().createComponent(componentType);
            } catch (ClassCastException e) {
                throw new IllegalArgumentException("Component type " + componentType + " is not valid.", e);
            }
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants