0

A click on a commandButton should trigger an action in a ManagedBean: to add a new "outputText" component to the current page.

The overall idea is to have the page changed dynamically with user action, with server side action because new elements added to the page need data from a db to be laid out.

-> How do I add a component to the page from a managed bean in jsf / primefaces? Let's say that the elements should be added in an existing div like:

<div id="placeHolder">
</div>

(this div could be changed to a jsf panel if needs be)

Note: if alternative methods are better to achieve the same effect I'd be glad to learn about them.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
seinecle
  • 10,118
  • 14
  • 61
  • 120
  • Yes, use primefaces panel (p:panel). Start looking at some of the basic tutorials. – Makky Jan 05 '14 at 09:29
  • You can put all elements with rendered=false. When the action is performing, change the bool element to true. – StarsSky Jan 05 '14 at 10:21
  • What @StarsSky says is the easiest way by far. Make the component not to be rendered at the beginning. When user clicks button, load any info from db if you need. After update some element which contains the outputText itself and set it as rendered. You can just use a condition: `rendered="#{not bean.buttonClicked}"`. – Aritz Jan 05 '14 at 10:52
  • Thanks but that wouldn't work. I need an non determinate number of components to be rendered - could be a lot, and I don't know how many in advance. Seems that a programmatic solution is what I need. @Makky, I could not find basic tutorials on how to add components programmatically on a page. – seinecle Jan 05 '14 at 11:16
  • So your question is how to add components dynamically ? – Makky Jan 05 '14 at 11:20
  • @seinecle why not add an `ui:repeat` which renders multiple outputText based in an array of `String`? Then wrap it with an `h:outputPanel` which will be rendered only when the array is not empty. – Aritz Jan 05 '14 at 12:33
  • Thanks for the suggestion, but I am after the general way of adding components dynamically (not looking for outputText specifically). – seinecle Jan 05 '14 at 12:49
  • with a bit of luck @BalusC will have a look at the question! :-) – seinecle Jan 05 '14 at 12:50
  • 1
    There is no general way of adding components. For example , InputText extends to UIInput where as OutputLabel extends to UIOutput. You can't have a list of generic components. I can't think of any other solutions than specified by @XtremeBiker – Makky Jan 05 '14 at 13:20
  • ok... that's quite a disappointment. I might go with a pure javascript solution then. – seinecle Jan 05 '14 at 14:07
  • Check [this answer](http://stackoverflow.com/questions/3510614/how-to-create-dynamic-jsf-1-2-form-fields/3522489#3522489) it may help. – Vasil Lukach Jan 05 '14 at 15:55
  • @Makky, about component extensions what you stated might be true, but just point that all of them extend from `UIComponent` parent. – Aritz Jan 07 '14 at 08:09

2 Answers2

2

I'll provide you another solution apart from the one you posted. Basically it has a List of given outputs, which is increased everytime the button is pushed. That should render exactly the same DOM tree as the solution you stated:

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
    <title>Tiles</title>
    <h:outputStylesheet name="css/320andup_cle.css" />
</h:head>
<h:body>
    <h:form>
        <h:commandButton actionListener="#{bean.createNewTile}" title="new"
            value="new" />
    </h:form>

    <h:panelGroup layout="block" id="tiles">
        <ui:repeat var="str" value="#{bean.strings}">
            <h:panelGroup>
                <h:outputText styleClass="tile" value="#{str}" />
            </h:panelGroup>
        </ui:repeat>
    </h:panelGroup>
</h:body>
</html>
@ManagedBean
@SessionScoped
public class Bean {

    List<String> strings = new ArrayList<String>();

    public List<String> getStrings() {
        return strings;
    }

    public void createNewTile() {
        strings.add("output");
    }
}

Apart from being much simpler IMHO, it has a main advantage: it doesn't couple your server side code to JSF implicit API. You can change the @ManagedBean annotation for @Named if you want it to be a CDI managed bean.

Aritz
  • 30,971
  • 16
  • 136
  • 217
  • Thanks, more elegant indeed! But it works for a list of strings: I am not sure it can be extended to components that need more elaborated customization (I got my solution to work for a dynamic inserting of a component made of an outputText + a form + a commandButton etc...) – seinecle Jan 07 '14 at 09:16
  • 1
    The list of strings is only used to provide an iteration model. You can play with `ui:repeat` and adapt it to the model you've. Commonly, if you want to create more than one form, you'll know at least what in the form should be displayed (based in the Entity you've got). Using `ui:repeat` tags and `rendered` attributes allow you to render your view based in your own model and let JSF auto generate the components, instead of creating them yourself manually. – Aritz Jan 07 '14 at 09:41
1

The solution:

This is a jsf page with a button creating a new div each time it is clicked:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <title>Tiles</title>
        <h:outputStylesheet name="css/320andup_cle.css" />
    </h:head>
    <h:body>
        <h:form>
            <h:commandButton actionListener="#{bean.createNewTile()}" title="new" value="new"/>
        </h:form>

        <h:panelGroup layout="block" id="tiles">
        </h:panelGroup>    
    </h:body>
</html>

The Managed Bean:

@Named
@SessionScoped
public class Bean implements Serializable {

    private UIComponent found;

    public void createNewTile() {

        HtmlPanelGroup div = new HtmlPanelGroup();
        div.setLayout("block");


        HtmlOutputText tile = new HtmlOutputText();
        tile.setValue("heeeeeRRRRRRRRRRRRRR         ");
        tile.setStyleClass("tile");
        div.getChildren().add(tile);

        doFind(FacesContext.getCurrentInstance(), "tiles");
        found.getChildren().add(div);

    }

    private void doFind(FacesContext context, String clientId) {
        FacesContext.getCurrentInstance().getViewRoot().invokeOnComponent(context, clientId, new ContextCallback() {
            @Override
            public void invokeContextCallback(FacesContext context,
                    UIComponent component) {
                found = component;
            }
        });
    }
}

See this app built with this logic of dynamically generated components: https://github.com/seinecle/Tiles

seinecle
  • 10,118
  • 14
  • 61
  • 120