Is it possible to have multiple values for a composite component. here is the example of what I want to be able to do:
<foo:zipcode id="zipcode" value="#{bean.zipcode}" city="#{bean.city}" >
<f:ajax eventt="changeEvent" update="city">
</foo:zipcode>
<h:inputText id="city" value="#{bean.city} />
Where the composite component has an input for the zipcode. Once entered I do a lookup/validation of the zipcode and update the city.
The composite component looks like:
<cc:interface componentType="component.zipCode">
<cc:attribute name="value" required="true" />
<cc:attribute name="city" />
</cc:interface>
<cc:implementation>
<p:inputText id="zipCode" value="#{cc.attrs.value}" >
<p:ajax partialSubmit="true" process="#this" update="#this" listener="#{cc.lookupZip}"/>
</p:inputText>
</cc:implementation>
Then for the backing component I have:
#FacesComponent(value = "component.zipCode")
public class UIZipCode extends UIInput implements NamingContainer {
public void lookupZip(AjaxBehaviorEvent event) {
// Code to lookup zipcode and get city.
// What goes HERE to update the city passed as attribute?
}
}
Am I missing something obvious. I've dug through tons of BalusC's posts and tried something like:
ValueExpression city = (ValueExpression) getAttributes().get("city");
city.setValue(FacesContext.getCurrentInstance().getELContext(), cityString);
I have most of this working, but can't figure out how to set the value of city in the backing bean from the lookupZip method. The value of city is always
null. Is this possible?
Related
My Composite Component contains the following form:
<cc:interface componentType="answerCompositeComponent">
<cc:attribute name="AnswerType" type="code.elephant.domainmodel.AnswerType" required="true" />
<cc:attribute name="ItemSource" type="code.elephant.domainmodel.Answer" required="true" />
<cc:attribute name="QuestionId" type="java.lang.Long" required="true" />
</cc:interface>
<cc:implementation>
<input jsf:id="sc#{cc.attrs.ItemSource.answerId}" />
</cc:implementation>
How can I access the value of the <input jsf:id="sc#{cc.attrs.ItemSource.answerId}" /> in my Backing Component? I tried the following in my backing bean in the overriden processUpdates method.
Answer ItemSource = (Answer) getValueExpression("ItemSource").getValue(context.getELContext());
String formid = String.format("sc%d", ItemSource.getAnswerId());
String get = context.getExternalContext().getRequestParameterMap().get(formid);
String get is always null. Is there a way to get the value of the input?
PS: I know that using plain html in jsf is not the purpose of it. I'm just interessted how my plan is achievable.
I never used plain html with jsf attributes, so I don't know if it's applicable.
Generally, this is a common way to access nested components in a composite:
<cc:interface componentType="answerCompositeComponent">
<cc:attribute name="AnswerType" type="code.elephant.domainmodel.AnswerType" required="true" />
<cc:attribute name="ItemSource" type="code.elephant.domainmodel.Answer" required="true" />
<cc:attribute name="QuestionId" type="java.lang.Long" required="true" />
</cc:interface>
<cc:implementation>
<h:inputText id="questionInput" binding="#{cc.input}" />
<!-- maybe something like this might work
<input jsf:id="questionInput" jsf:binding="#{cc.input}" />
-->
</cc:implementation>
where
#FacesComponent("answerCompositeComponent")
public class AnswerCompositeComponent extends UINamingContainer
{
private UIInput input;
#Override
public void processUpdates(FacesContext context)
{
super.processUpdates(context);
Object value = input.getValue();
Object localValue = input.getLocalValue();
Object submittedValue = input.getSubmittedValue();
// do your things with values
}
public UIInput getInput()
{
return input;
}
public void setInput(UIInput input)
{
this.input = input;
}
}
Note that a composite backing component is a NamingContainer, so prefer static (or none at all) nested component IDs. Avoid dynamic IDs, unless you really need them and you know exactly what you're doing.
I'm trying to create my own dataTable like the primefaces one. The problem is that cc.attrs.var when used throws a IllegalArgumentException. So I'm wondering how I can have the var attribute like Primefaces.
<cc:interface>
<cc:attribute name="value"/>
<cc:attribute name="var"/>
<cc:attribute name="styleClass"/>
</cc:interface>
<cc:implementation>
<div>Previous</div>
<div>Next</div>
<h:dataTable value="#{cc.attrs.value}" var="#{cc.attrs.var}" styleClass="#{cc.attrs.styleClass}">
<ui:insert/>
</h:dataTable>
</cc:implementation>
As per the UIData#setValueExpression() javadoc, it's not allowed to have an EL expression in var attribute.
Throws:
IllegalArgumentException - if name is one of id, parent, var, or rowIndex
Your best bet is to create a backing component wherein you manually evaluate and set the var attribute of the UIData component bound to <h:dataTable> during the postAddToView event.
<cc:interface componentType="yourTableComposite">
<cc:attribute name="value" />
<cc:attribute name="var" />
</cc:interface>
<cc:implementation>
<f:event type="postAddToView" listener="#{cc.init}" />
<h:dataTable binding="#{cc.table}" value="#{cc.attrs.value}">
<cc:insertChildren />
</h:dataTable>
</cc:implementation>
#FacesComponent("yourTableComposite")
public class YourTableComposite extends UINamingContainer {
private UIData table;
public void init() {
table.setVar((String) getAttributes().get("var"));
}
public UIData getTable() {
return table;
}
public void setTable(UIData table) {
this.table = table;
}
}
Note that I fixed the <ui:insert> to be <cc:insertChildren>. The <ui:insert> can only be used in <ui:composition>/<ui:decorate>.
See also:
Initialize a composite component based on the provided attributes
How does the 'binding' attribute work in JSF? When and how should it be used?
I created a very simple example based on my project in order to illustrate my doubt. Just a way to register a person with a list of telephone numbers.
MainController.java
private String name;
private List<Phone> phoneList;
// Getters and Setters
#PostConstruct
private void init() {
phoneList = new ArrayList<>();
}
public static class Phone implements Serializable {
private String number;
// Getters and Setters
#Override
public String toString() {
return number != null ? number : "null";
}
}
public void add() {
phoneList.add(new Phone());
}
public void save() {
System.out.println("Name: " + name + "; " + phoneList.toString());
}
index.xhtml
<h:form>
<h:inputText value="#{mainController.name}" required="true" />
<ui:repeat var="phone" value="#{mainController.phoneList}" varStatus="status">
<h:inputText value="#{phone.number}" required="true" />
</ui:repeat>
<h:commandButton action="#{mainController.add()}" value="Add Phone" immediate="true" />
<h:commandButton action="#{mainController.save()}" value="Save" />
</h:form>
In my example, note that all phone fields that are added MUST be filled in (required = true).
The problem is: when I type name and click add (to add a phone) the value of the field is maintained. But when I type a first phone and click add, the phone's value is not maintained. This occurs for all fields within the component ui:repeat.
Is there a way to preserve the input values within a after an immediate request, as with the name field?
Extra note: Other strange behavior I noticed is when add at least two phone fields, let the first blank and fills the second, and saves the form. After a failed validation (due to phone blank), click add will make all fields are filled with the value of the second phone.
Wildfly 9.0.2, JSF Api (Jboss) 2.2.12
Thanks to #BalusC comment. The OmniFaces library has two taghandlers that can be used in this case. In both cases input values will be preserved in case of validation failure. Note that h:commandButton should be with <h:commandButton immediate="false" />.
ignoreValidationFailed
In this case all validation failures will be ignored (including converter failures). Note that the h:form have to be changed to o:form. Also, the failures messages will still be displayed, which can be solved putting a proper condition in the rendered attribute. The files will look like this:
index.xhtml
<o:form>
<h:inputText value="#{mainController.name}" required="true" />
<ui:repeat var="phone" value="#{mainController.phoneList}" varStatus="status">
<h:inputText value="#{phone.number}" required="true" />
</ui:repeat>
<h:commandButton action="#{mainController.add()}" value="Add Phone">
<o:ignoreValidationFailed />
</h:commandButton>
<h:commandButton action="#{mainController.save()}" value="Save" />
</o:form>
<h:messages rendered="#{facesContext.validationFailed}" />
skipValidators
In this case only the validation failures will be ignored (the converters will still run). The failures messages will not be displayed, except for the converters. Note that this taghandler is only available since the 2.3 version. The files will look like this:
index.xhtml
<h:form>
<h:inputText value="#{mainController.name}" required="true" />
<ui:repeat var="phone" value="#{mainController.phoneList}" varStatus="status">
<h:inputText value="#{phone.number}" required="true" />
</ui:repeat>
<h:commandButton action="#{mainController.add()}" value="Add Phone">
<o:skipValidators />
</h:commandButton>
<h:commandButton action="#{mainController.save()}" value="Save" />
</h:form>
The solution that I use to this problem is to create an external field to the loop, which stores a JSON containing the values that should be saved. This field, to be outside the loop, properly saves values after each try and restore the missing values when necessary. I use two functions JavaScript and JQuery library.
So the files would look like this:
index.xhtml
<h:outputScript library="jquery" name="jquery.min.js" />
<h:outputScript library="all" name="all.js" />
<h:form>
<h:inputText value="#{mainController.name}" required="true" />
<ui:repeat var="phone" value="#{mainController.phoneList}" varStatus="status">
<h:inputText styleClass="savePhoneNumber" value="#{phone.number}" required="true" onchange="saveUiRepeatInput('#{allPhoneNumber.clientId}', 'savePhoneNumber')" />
</ui:repeat>
<h:inputHidden id="allPhoneNumber" binding="#{allPhoneNumber}" />
<h:outputScript>loadUiRepeatInput('#{allPhoneNumber.clientId}', 'savePhoneNumber')</h:outputScript>
<h:commandButton action="#{mainController.add()}" value="Add Phone" immediate="true" />
<h:commandButton action="#{mainController.save()}" value="Save" />
</h:form>
all.js
function saveUiRepeatInput(inputAll, inputClass) {
document.getElementById(inputAll).value = JSON.stringify($('.' + inputClass).map(function() { return this.value; }).get());
}
function loadUiRepeatInput(inputAll, inputClass) {
var jsonAll = document.getElementById(inputAll).value;
if (jsonAll) {
var array = JSON.parse(jsonAll);
$('.' + inputClass).each(function(i) { if (i < array.length) this.value = array[i]; });
}
}
Although work perfectly (including via ajax, with some minor changes), it looks like a hack, not an ideal solution. So if anyone can help with any solution strictly based on JSF, I will be grateful. Thanks.
I am currently trying to design a reusable dialog for creating a new entity instance.
Problem situation:
In my view there is a dynamic list of entities, for which I have a selectOne-Dropdown to select an existing entity an also a button, that one may click to open a create-dialog for a creating a new instance of this entity class, if the correct one is missing.
Attemp for solution:
I tried to create a composite component for this issues which looks like this:
<composite:interface>
<composite:attribute name="myBean" required="true"
type="interfaces.INewEntity" />
<composite:attribute name="target" required="true"
type="entity.SsoUser" />
</composite:interface>
<composite:implementation>
<p:selectOneMenu value="#{cc.attributes.target}" >
// selectItems
// ...
</p:selectOneMenu>
<p:commandButton type="buton" value="new" onclick="PF('a').show()" />
<p:dialog id="test" widgetVar="a">
<h:panelGrid columns="2" id="newPanelGrid">
<h:outputText value="Set some properties.." />
<p:inputText value="#{cc.attributes.myBean.newEntity.foo}" />
</h:panelGrid>
<p:commandButton value="Create new Entity" process="test" update="#form"
oncomplete="PF('a').hide()"
actionListener="#{cc.attributes.myBean.save()}">
</p:commandButton>
</p:dialog>
</composite:implementation>
After persisting the new entity the dropdown now contains the new entity instance.
Problem: I want to somehow set the value from the dropdown menu to the new created entity. How could this be achieved?
A static example:
#ManagedBean(name = "myBean")
#ViewScoped
public class MyBean implements Serializable, INewEntity {
// comes from the interfaces
private Entity newEntity;
// some persistent fields
private Wrapper wrap;
// ...
}
public class Wrapper {
Entity a,b,c;
}
And I am using my CC this way:
<stg:mycc target="#{myBean.wrap.a}" userBean="#{myBean}" />
<stg:mycc target="#{myBean.wrap.b}" userBean="#{myBean}" />
<stg:mycc target="#{myBean.wrap.c}" userBean="#{myBean}" />
I want to change the inputTexts' values when I choose another Skin from my selectOneMenu.
Everything is doing well, my Converter gives back the right Object from the menu, but the inputTexts are not updated.
<h:form>
<h:selectOneMenu id="dropdownSkin"
value="#{helloBean.currentSkin}" defaultLabel="Select a skin.."
valueChangeListener="#{helloBean.skinValueChanged}" immediate="true"
onchange="this.form.submit()" converter="SkinConverter" >
<f:selectItems value="#{helloBean.mySkinsSI}" var="c"
itemValue="#{c.value}" />
</h:selectOneMenu>
<br />
<h:inputText id="name" value="#{helloBean.currentSkin.title}"></h:inputText>
<br />
<h:inputText id="tcolor" value="#{helloBean.currentSkin.titleBar.textColor}"></h:inputText>
<br />
<h:inputText id="bcolor" value="#{helloBean.currentSkin.titleBar.backgroundColorStart}"></h:inputText>
</h:form>
Here is what my Bean looks like. I debugged it and the Object currentSkin is set correctly. Now i need to know how to update the textfields content.
#ManagedBean
#SessionScoped
public class HelloBean implements Serializable {
private static final long serialVersionUID = 1L;
private List<ExtendedSkin> mySkins;
private List<SelectItem> mySkinsSI;
private ExtendedSkin currentSkin;
public void skinValueChanged(ValueChangeEvent e) {
currentSkin = (ExtendedSkin) e.getNewValue();
FacesContext.getCurrentInstance().renderResponse();
}
public List<ExtendedSkin> getMySkins() {
mySkins = XMLParser.readExtendedSkins();
return mySkins;
}
public List<SelectItem> getMySkinsSI() {
mySkinsSI = new LinkedList<SelectItem>();
for (ExtendedSkin s : getMySkins()) {
mySkinsSI.add(new SelectItem(s, s.getTitle()));
}
return mySkinsSI;
}
public void setMySkinsSI(List<SelectItem> myItems) {
this.mySkinsSI = myItems;
}
public ExtendedSkin getCurrentSkin() {
if (currentSkin == null) {
currentSkin = getMySkins().get(0);
}
return currentSkin;
}
public void setCurrentSkin(ExtendedSkin currentSkin) {
this.currentSkin = currentSkin;
}
}
The problem here is that the converter is doing its work filling the helloBean.currentSkin object, but the values in the <h:inputText> that are bounded to this helloBean.currentSkin: title, textColor and backgroundColorStart will be send to the server and replace the actual values that were loaded by the converter. In other words:
The converter is executed and builds the helloBean.currentSkin based on the selected value.
The <h:inputText id="name"> empty value is sent to server and will be injected in helloBean.currentSkin.title. Same behavior for the other 2 <h:inputText>s.
The view will be loaded using the selected helloBean.currentSkin and it will load the helloBean.currentSkin.title with the empty value. Same behavior for the other 2 <h:inputText>s.
There are two possible solutions to this problem:
Move the <h:inputText>s outside the form, so the empty values won't be send to the server. When loading the view, it will maintain the values loaded in the converter.
<h:form>
<h:selectOneMenu id="dropdownSkin"
value="#{helloBean.currentSkin}" defaultLabel="Select a skin.."
valueChangeListener="#{helloBean.skinValueChanged}" immediate="true"
onchange="this.form.submit()" converter="SkinConverter" >
<f:selectItems value="#{helloBean.mySkinsSI}" var="c"
itemValue="#{c.value}" />
</h:selectOneMenu>
</h:form>
<br />
<h:inputText id="name" value="#{helloBean.currentSkin.title}"></h:inputText>
<!-- rest of Facelets code... -->
Since you're loading the helloBean.currentSkin while changing the selected value on your dropdownlist, you can add ajax behavior using <f:ajax> tag component inside the <h:selectOneMenu> and update the fields in a cleaner way. I would opt for this solution.
<h:form>
<!-- Note that there's no need of the onchange JavaScript function -->
<h:selectOneMenu id="dropdownSkin"
value="#{helloBean.currentSkin}" defaultLabel="Select a skin.."
valueChangeListener="#{helloBean.skinValueChanged}" immediate="true"
converter="SkinConverter" >
<f:selectItems value="#{helloBean.mySkinsSI}" var="c"
itemValue="#{c.value}" />
<f:ajax process="#this" render="name tcolor bcolor" />
</h:selectOneMenu>
<br />
<h:inputText id="name" value="#{helloBean.currentSkin.title}" />
<h:inputText id="tcolor" value="#{helloBean.currentSkin.titleBar.textColor}" />
<br />
<h:inputText id="bcolor"
value="#{helloBean.currentSkin.titleBar.backgroundColorStart}" />
</h:form>
You can learn more about <f:ajax> in online tutorial like this one.
Since you're going to use an ajax call in your page, you should change your managed bean scope from #SessionScoped to #ViewScoped. More info about this here: Communication in JSF 2