I'm trying to build a custom composite component using JSF 2. I have a dialog inside this component that I want to show when I submit the form, however the dialog is never shown when i use appendToBody="false" and not working when i use appendToBody="true".
My search component looks like this.
When user fills in some value to the inputText and presses the seach button, the dialog like this should pop up:
Now the only way I can make this work is having two buttons in my component instead of one. One for submitting the value and one for showing the dialog. This is when I use the dialog with appendToBody="false" property. So my testing component looks like this:
When I use dialog with appendToBody="true" I am able to submit and show the dialog at once, however the dialog has then other issues, like I'm not able to close the dialog. Data is not updated, etc.
Here is my code (lov.xhtml):
<ui:component xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface componentType="lov">
<composite:attribute name="value" required="true" />
<composite:attribute name="definitionFile" required="true" />
<composite:attribute name="definitionName" required="true" />
</composite:interface>
<composite:implementation>
<h:outputStylesheet library="css" name="styles.css" />
<p:inputText id="lovInputText" styleClass="lovInputText"
value="#{cc.selectedValue}" binding="#{cc.lovInputText}" />
<p:commandButton styleClass="lovButton" icon="ui-icon-search"
onclick="searchAndSelect.show();" actionListener="#{cc.updateDialog}"
update="@form" />
<p:commandButton value="Open" icon="ui-icon-search"
onclick="searchAndSelect.show();" />
<h:outputLabel id="outputLabel" value="#{cc.selectedValue}" />
<p:dialog id="searchAndSelectDialog" header="Search and Select"
appendToBody="false" closable="false" resizable="false"
widgetVar="searchAndSelect" showEffect="fade" hideEffect="fade"
binding="#{cc.searchAndSelectDialog}">
<p:panelGrid>
<p:row>
<p:column>
<h:outputLabel value="Value: " />
</p:column>
<p:column>
<h:form id="sasInputTextForm" prependId="true">
<p:inputText id="sasInputText" value="#{cc.selectedValue}"
label="Value" binding="#{cc.sasInputText}" />
</h:form>
</p:column>
</p:row>
<p:row>
<p:column colspan="2" styleClass="searchAndResetColumn">
<h:form>
<p:commandButton value="Search" icon="ui-icon-search"
actionListener="#{cc.search}" />
<p:commandButton type="button" value="Reset"
icon="ui-icon-arrowrefresh-1-e" />
</h:form>
</p:column>
</p:row>
<p:row>
<p:column colspan="2">
<h:form>
<p:dataTable var="item" value="#{cc.data}">
<p:columns value="#{cc.columns}" var="column"
columnIndexVar="colIndex" sortBy="#{item[column.property]}"
filterBy="#{item[column.property]}">
<f:facet name="header">#{column.header}</f:facet>
#{item[column.property]}
</p:columns>
</p:dataTable>
</h:form>
</p:column>
</p:row>
</p:panelGrid>
<f:facet name="footer">
<p:commandButton type="button" value="OK" icon="ui-icon-check" />
<p:commandButton type="button" value="Cancel" icon="ui-icon-cancel"
onclick="searchAndSelect.hide();" />
</f:facet>
</p:dialog>
</composite:implementation>
</ui:component>
Here's the component backing bean (Lov.java):
@FacesComponent("lov")
public class Lov extends UIInput implements NamingContainer {
private List<ColumnModel> columns = new ArrayList<ColumnModel>();
private String definitionFile;
private String definitionName;
private String bean;
private String attribute;
private List<String> displayAttributes;
private UIInput lovInputText;
private UIInput sasInputText;
private UIComponent searchAndSelectDialog;
// Fields
// -------------------------------------------------------------------------------------
// Actions
// ------------------------------------------------------------------------------------
/**
* Returns the component family of {@link UINamingContainer}. (that's just
* required by composite component)
*/
@Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
/**
* Set the selected and available values of the day, month and year fields
* based on the model.
*/
@Override
public void encodeBegin(FacesContext context) throws IOException {
System.out.println("Called encodeBeing method...");
System.out.println("getValue: "+ getValue().toString());
setSelectedValue(getValue().toString());
definitionFile = getAttributeValue("definitionFile", null);
definitionName = getAttributeValue("definitionName", null);
try {
parseXml();
queryData();
} catch (Exception e) {
e.printStackTrace();
}
createDynamicColumns();
super.encodeBegin(context);
}
/**
* Returns the submitted value in dd-MM-yyyy format.
*/
@Override
public Object getSubmittedValue() {
System.out.println("====================================================");
System.out.println("getSubmittedValue method called...");
System.out.println("submittedValue: " + lovInputText.getSubmittedValue());
System.out.println("localValue: " +lovInputText.getLocalValue());
return lovInputText.getSubmittedValue();
}
/**
* Converts the submitted value to concrete {@link Date} instance.
*/
@Override
protected Object getConvertedValue(FacesContext context,
Object submittedValue) {
return super.getConvertedValue(context, submittedValue);
}
public void search(ActionEvent actionEvent) {
System.out.println("Search method called...");
try {
queryData();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Update the available days based on the selected month and year, if necessary.
*/
public void updateDialog(ActionEvent actionEvent) {
System.out.println("==========================================================");
System.out.println("updateDialog method called...");
System.out.println("getValue: "+getValue().toString());
setSelectedValue(getValue().toString());
/*FacesContext context = FacesContext.getCurrentInstance(); // Update dialog
context.getPartialViewContext().getRenderIds().add(searchAndSelectDialog.getClientId(context));*/
}
// Helpers
// ------------------------------------------------------------------------------------
/**
* Return specified attribute value or otherwise the specified default if
* it's null.
*/
@SuppressWarnings("unchecked")
private <T> T getAttributeValue(String key, T defaultValue) {
T value = (T) getAttributes().get(key);
return (value != null) ? value : defaultValue;
}
/**
* Create an integer array with values from specified begin to specified
* end, inclusive.
*/
private static Integer[] createIntegerArray(int begin, int end) {
int direction = (begin < end) ? 1 : (begin > end) ? -1 : 0;
int size = Math.abs(end - begin) + 1;
Integer[] array = new Integer[size];
for (int i = 0; i < size; i++) {
array[i] = begin + (i * direction);
}
return array;
}
protected void parseXml() throws ParserConfigurationException,
SAXException, IOException, XPathExpressionException,
URISyntaxException {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
dbFactory.setNamespaceAware(true);
DocumentBuilder builder = dbFactory.newDocumentBuilder();
ServletContext servletContext = (ServletContext) FacesContext
.getCurrentInstance().getExternalContext().getContext();
InputStream is = servletContext.getResourceAsStream(definitionFile);
Document doc = builder.parse(is);
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
XPathExpression expr = xpath.compile(String.format(
"/definitions/definition[@name='%s']/bean/@name",
definitionName));
Object result = expr.evaluate(doc, XPathConstants.STRING);
bean = result.toString();
setBean(bean);
System.out.println("bean: " + bean);
expr = xpath
.compile(String
.format("/definitions/definition[@name='%s']/attributes/attribute/@name",
definitionName));
result = expr.evaluate(doc, XPathConstants.STRING);
attribute = result.toString();
setAttribute(attribute);
System.out.println("attribute: " + attribute);
expr = xpath
.compile(String
.format("/definitions/definition[@name='%s']/displayAttributes/attribute",
definitionName));
result = expr.evaluate(doc, XPathConstants.NODESET);
displayAttributes = new ArrayList<String>();
NodeList nodes = (NodeList) result;
for (int i = 0; i < nodes.getLength(); i++) {
NamedNodeMap attrs = nodes.item(i).getAttributes();
for (int j = 0; j < attrs.getLength(); j++) {
String nodeName = attrs.item(j).getNodeName();
if (nodeName.equalsIgnoreCase("name")) {
displayAttributes.add(attrs.item(j).getNodeValue());
}
}
}
setDisplayAttributes(displayAttributes);
System.out.println("displayAttributes: " + displayAttributes.toString());
}
protected void queryData() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException {
FilterableBean bean = (FilterableBean) findBean(getBean());
if (lovInputText != null) {
System.out.println("lovInputText exists");
}
System.out.println("queryData value: "+getValue().toString());
setData(bean.get(getSelectedValue()));
System.out.println("data: "+ getData().toString());
}
@SuppressWarnings("unchecked")
public static <T> T findBean(String beanName) {
FacesContext context = FacesContext.getCurrentInstance();
return (T) context.getApplication().evaluateExpressionGet(context, "#{" + beanName + "}", Object.class);
}
protected void createDynamicColumns() {
if (displayAttributes != null) {
columns.clear();
for (String displayAttribute : displayAttributes) {
String key = displayAttribute.trim();
columns.add(new ColumnModel(key.toUpperCase(), key));
}
}
}
// Getters/setters
// ----------------------------------------------------------------------------
@SuppressWarnings("rawtypes")
public List getData() {
return (List) getStateHelper().get("data");
}
@SuppressWarnings("rawtypes")
public void setData(List data) {
getStateHelper().put("data", data);
}
private String hello = "hello";
public String getHello() {
return hello;
}
public void setHello(String hello) {
this.hello = hello;
}
public List<ColumnModel> getColumns() {
return columns;
}
public String getSelectedValue() {
return (String) getStateHelper().get("selectedValue");
}
public void setSelectedValue(String selectedValue) {
getStateHelper().put("selectedValue", selectedValue);
}
public UIInput getLovInputText() {
return lovInputText;
}
public void setLovInputText(UIInput lovInputText) {
this.lovInputText = lovInputText;
}
public UIInput getSasInputText() {
return sasInputText;
}
public void setSasInputText(UIInput sasInputText) {
this.sasInputText = sasInputText;
}
public UIComponent getSearchAndSelectDialog() {
return searchAndSelectDialog;
}
public void setSearchAndSelectDialog(UIComponent searchAndSelectDialog) {
this.searchAndSelectDialog = searchAndSelectDialog;
}
public String getBean() {
return (String) getStateHelper().get("bean");
}
public void setBean(String bean) {
getStateHelper().put("bean", bean);
}
public String getAttribute() {
return (String) getStateHelper().get("attribute");
}
public void setAttribute(String attribute) {
getStateHelper().put("attribute", attribute);
}
@SuppressWarnings("unchecked")
public List<String> getDisplayAttributes() {
return (List<String>) getStateHelper().get("displayAttributes");
}
public void setDisplayAttributes(List<String> displayAttributes) {
getStateHelper().put("displayAttributes", displayAttributes);
}
static public class ColumnModel implements Serializable {
private String header;
private String property;
public ColumnModel(String header, String property) {
this.header = header;
this.property = property;
}
public String getHeader() {
return header;
}
public String getProperty() {
return property;
}
}
}
Here is how I use the component:
<h:form>
<my:lov value="#{testBean.region}" definitionFile="/resources/xml/lov/definitions/country.xml" definitionName="LOV_Region"/>
<p:messages />
</h:form>
I was following BalusC's article to create this component. Here's the link:
How can I use a dialog in a custom composite component, that will be updated with the values and shown at once?