I have an inputtextarea which calls a completeMethod after a query is entered... that is working fine and the suggestions are displaying, but I woul'd need to catch the ajax event in order to know which suggestion the user has picked. Is it possible?
You are trying to use the ajax itemSelect event like so:
<h:form>
<p:inputTextarea widgetVar="textarea" completeMethod="#{myBean.complete}">
<p:ajax event="itemSelect" listener="#{myBean.onSelect}" />
</p:inputTextarea>
</h:form>
Managed bean methods:
public List<String> complete(String filter) {
List<String> result = new ArrayList<String>();
for (int i = 0; i < 10; i++)
result.add(filter + i);
return result;
}
public void onSelect(SelectEvent<String> e) {
System.out.println(e.getObject());
}
This does not work in Primefaces 7.0 (and probably earlier) because the renderer of the inputTextarea fails to add the clientBehavior configuration which is fixed as of Version 7.1.
In the browser javascript console input:
PF('textarea').cfg.behaviors
> undefined
The result undefined indicates missing client behavior configuration.
You can work around this by overriding the InputTextareaRenderer.encodeScript() method:
package my.package;
import java.io.IOException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import org.primefaces.component.inputtextarea.InputTextarea;
import org.primefaces.component.inputtextarea.InputTextareaRenderer;
import org.primefaces.expression.SearchExpressionFacade;
import org.primefaces.util.WidgetBuilder;
public class CustomInputTextareaRenderer extends InputTextareaRenderer {
#Override
protected void encodeScript(FacesContext context, InputTextarea inputTextarea) throws IOException {
String clientId = inputTextarea.getClientId(context);
boolean autoResize = inputTextarea.isAutoResize();
String counter = inputTextarea.getCounter();
WidgetBuilder wb = getWidgetBuilder(context);
wb.init("InputTextarea", inputTextarea.resolveWidgetVar(), clientId).attr("autoResize", autoResize)
.attr("maxlength", inputTextarea.getMaxlength(), Integer.MAX_VALUE);
if (counter != null) {
UIComponent counterComponent = SearchExpressionFacade.resolveComponent(context, inputTextarea, counter);
wb.attr("counter", counterComponent.getClientId(context)).attr("counterTemplate",
inputTextarea.getCounterTemplate(), null);
}
if (inputTextarea.getCompleteMethod() != null) {
wb.attr("autoComplete", true).attr("minQueryLength", inputTextarea.getMinQueryLength())
.attr("queryDelay", inputTextarea.getQueryDelay())
.attr("scrollHeight", inputTextarea.getScrollHeight(), Integer.MAX_VALUE);
}
// additional line enabling AJAX 'itemSelect' event handling.
encodeClientBehaviors(context, inputTextarea);
wb.finish();
}
}
In faces config, add the renderer:
<faces-config>
...
<render-kit>
<renderer>
<component-family>org.primefaces.component</component-family>
<renderer-type>org.primefaces.component.InputTextareaRenderer</renderer-type>
<renderer-class>my.package.CustomInputTextareaRenderer</renderer-class>
</renderer>
</render-kit>
</faces-config>
Quick indication that it works in javascript console:
PF('textarea').cfg.behaviors
> Object { itemSelect: itemSelect() }
Related
I have primefaces steps using tag <p:steps> like below :
<p:steps activeIndex="3" styleClass="custom" readonly="false" style="padding: 20px;">
<p:menuitem value="step 1." actionListener="#{masterController.menuSales(preferencesController)}" update="mainPanel"/>
<p:menuitem value="step 2." actionListener="#{masterController.menuCustomer(preferencesController)}" update="mainPanel"/>
<p:menuitem value="step 3." actionListener="#{masterController.menuItem(preferencesController)}" update="mainPanel"/>
<p:menuitem value="step 4"/>
</p:steps>
And the result is like this :
I can click step 1 but not step 3 and 4. How can I enable click for all steps?
Wow, that's a nice question!
I've tried many things with the current API to accomplish it, but seems like it's not possible with our current options.
To solve this I wrote a custom renderer for the Steps component:
Most of the code below is the same from the PrimeFaces's GitHub. I just changed a few things to solve this specific problem.
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.primefaces.component.api.AjaxSource;
import org.primefaces.component.api.UIOutcomeTarget;
import org.primefaces.component.steps.Steps;
import org.primefaces.component.steps.StepsRenderer;
import org.primefaces.model.menu.MenuItem;
import org.primefaces.util.ComponentTraversalUtils;
public class CustomStepsRenderer extends StepsRenderer {
#Override
protected void encodeItem(FacesContext context, Steps steps, MenuItem item, int activeIndex, int index) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String itemClass;
if (steps.isReadonly()) {
itemClass = (index == activeIndex) ? Steps.ACTIVE_ITEM_CLASS : Steps.INACTIVE_ITEM_CLASS;
} else {
if (index == activeIndex) {
itemClass = Steps.ACTIVE_ITEM_CLASS;
}
else {
itemClass = Steps.VISITED_ITEM_CLASS;
}
}
String containerStyle = item.getContainerStyle();
String containerStyleClass = item.getContainerStyleClass();
if (containerStyleClass != null) {
itemClass = itemClass + " " + containerStyleClass;
}
//header container
writer.startElement("li", null);
writer.writeAttribute("class", itemClass, null);
writer.writeAttribute("role", "tab", null);
if (containerStyle != null) {
writer.writeAttribute("style", containerStyle, null);
}
encodeMenuItem(context, steps, item, activeIndex, index);
writer.endElement("li");
}
#Override
protected void encodeMenuItem(FacesContext context, Steps steps, MenuItem menuitem, int activeIndex, int index) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String title = menuitem.getTitle();
String style = menuitem.getStyle();
String styleClass = this.getLinkStyleClass(menuitem);
writer.startElement("a", null);
writer.writeAttribute("tabindex", "-1", null);
if (shouldRenderId(menuitem)) {
writer.writeAttribute("id", menuitem.getClientId(), null);
}
if (title != null) {
writer.writeAttribute("title", title, null);
}
writer.writeAttribute("class", styleClass, null);
if (style != null) {
writer.writeAttribute("style", style, null);
}
if (steps.isReadonly() || menuitem.isDisabled()) {
writer.writeAttribute("href", "#", null);
writer.writeAttribute("onclick", "return false;", null);
} else {
String onclick = menuitem.getOnclick();
//GET
if (menuitem.getUrl() != null || menuitem.getOutcome() != null) {
String targetURL = getTargetURL(context, (UIOutcomeTarget) menuitem);
writer.writeAttribute("href", targetURL, null);
if (menuitem.getTarget() != null) {
writer.writeAttribute("target", menuitem.getTarget(), null);
}
} //POST
else {
writer.writeAttribute("href", "#", null);
UIComponent form = ComponentTraversalUtils.closestForm(context, steps);
if (form == null) {
throw new FacesException("MenuItem must be inside a form element");
}
String command;
if (menuitem.isDynamic()) {
String menuClientId = steps.getClientId(context);
Map<String, List<String>> params = menuitem.getParams();
if (params == null) {
params = new LinkedHashMap<String, List<String>>();
}
List<String> idParams = new ArrayList<String>();
idParams.add(menuitem.getId());
params.put(menuClientId + "_menuid", idParams);
command = menuitem.isAjax()
? buildAjaxRequest(context, steps, (AjaxSource) menuitem, form, params)
: buildNonAjaxRequest(context, steps, form, menuClientId, params, true);
} else {
command = menuitem.isAjax()
? buildAjaxRequest(context, (AjaxSource) menuitem, form)
: buildNonAjaxRequest(context, ((UIComponent) menuitem), form, ((UIComponent) menuitem).getClientId(context), true);
}
onclick = (onclick == null) ? command : onclick + ";" + command;
}
if (onclick != null) {
writer.writeAttribute("onclick", onclick, null);
}
}
writer.startElement("span", steps);
writer.writeAttribute("class", Steps.STEP_NUMBER_CLASS, null);
writer.writeText((index + 1), null);
writer.endElement("span");
Object value = menuitem.getValue();
if (value != null) {
writer.startElement("span", steps);
writer.writeAttribute("class", Steps.STEP_TITLE_CLASS, null);
writer.writeText(value, null);
writer.endElement("span");
}
writer.endElement("a");
}
Then, register this new renderer in your faces-config.xml file:
<render-kit>
<renderer>
<component-family>org.primefaces.component</component-family>
<renderer-type>org.primefaces.component.StepsRenderer</renderer-type>
<renderer-class>YOUR_PACKAGE.CustomStepsRenderer</renderer-class>
</renderer>
</render-kit>
Don't forget to change YOUR_PACKAGE to your CustomStepsRenderer package location.
After that, just build/re-deploy your application and everything should work fine:
Well, p:steps and p:wizard are the components in PrimeFaces component suite that represent or indicate the step(s) in a workflow to manage multiple steps of single form (step by step) for process simplication and can be used interchangably if you understand the usage properly (depending on the requirement).
For using p:steps component, you should ensure that the next step(s) will only be displayed when the current step is completely processed and required data is gathered.
Assume the process of online shopping, where payment processing is the last step and that will appear if and only if, you have any item in your cart and have provided the other information (if any).
The above scenario can also be implemented using p:wizard component. Where only current step is processed partially and next step is displayed if current step passes validations. However, p:wizard component has feasibility to override it's default behavior by controlling the wizard flow, rendering of custom previous & next buttons with custom action handlers and skipping of validation to view next steps.
menuform:I may answer your question a bit late, but I will post it so if other persona have the same problem, may it work for them.
I use JavaScript for the solution, so may it not the solution that you need:
// That is your code. I added ids to capture them with the DOM.
<p:steps activeIndex="3" styleClass="custom" readonly="false" style="padding: 20px;">
<p:menuitem value="step 1." actionListener="#{masterController.menuSales(preferencesController)}" update="mainPanel" id="step1"/>
<p:menuitem value="step 2." actionListener="#{masterController.menuCustomer(preferencesController)}" update="mainPanel" id="step2"/>
<p:menuitem value="step 3." actionListener="#{masterController.menuItem(preferencesController)}" update="mainPanel" id="step3"/>
<p:menuitem value="step 4" id="step4"/>
</p:steps>
// Now we can make the script
<script>
// First of all, we will capture all the steps with the DOM (you can also work with jQuery, but I will post the solution with DOM in case you do not have your code prepared to jQuery)
var step1 = document.getElementById("menuform:step1");
var step2 = document.getElementById("menuform:step2");
var step3 = document.getElementById("menuform:step3");
var step4 = document.getElementById("menuform:step4");
// Then, we are going to set the attributes href and onclick, and give them some style to make the elements look like proper links
step1.setAttribute("href", "[url]");
step1.setAttribute("onclick", true);
step1.style.cursor = "pointer";
step2.setAttribute("href", "[url]");
step2.setAttribute("onclick", true);
step2.style.cursor = "pointer";
step3.setAttribute("href", "[url]");
step3.setAttribute("onclick", true);
step4.style.cursor = "pointer";
step4.setAttribute("href", "[url]");
step4.setAttribute("onclick", true);
step4.style.cursor = "pointer";
</script>
Is important to change href and onclick (click event), because the element 'steps' change both of them, thats like them looks like when you inspect the code with the console:
- href="#"
- onclick="return false;"
I have been following this tutorial
http://www.primefaces.org/showcase-ext/sections/dynaform/basicUsage.jsf
I have been able to create tree Dynaform objects and send it to the page. But I am having a hard time obtaining the values that the user entered once they clicked submit. I want to be able to get these values in the backbean.
Here is submit button
<p:commandButton value="Submit" action="#{dynaFormController.submitForm}"
process="dynaForm" update=":mainForm:dynaFormGroup :mainForm:inputValues"
oncomplete="handleComplete(xhr, status, args)"/>
<p:commandButton type="reset" value="Reset" style="margin-left: 5px;"/>
I know the submit calls this function
<h:outputScript id="dynaFormScript" target="body">
/* <![CDATA[ */
function handleComplete(xhr, status, args) {
if(args && args.isValid) {
PF('inputValuesWidget').show();
} else {
PF('inputValuesWidget').hide();
}
}
/* ]]> */
</h:outputScript>
Then in the bean we have:
public String submitForm() {
FacesMessage.Severity sev = FacesContext.getCurrentInstance().getMaximumSeverity();
boolean hasErrors = (sev != null && (FacesMessage.SEVERITY_ERROR.compareTo(sev) >= 0));
RequestContext requestContext = RequestContext.getCurrentInstance();
requestContext.addCallbackParam("isValid", !hasErrors);
return null;
}
How would I be able to get either the fields values from the submitted form?
I have 3 dynaforms that I would like to submit them and be able to get the values in the back bean. Can anyone explain? I tried looking up some tutorials but I didn't find any explaining this.
Thanks.
It's the same as plain JSF.
You need a variable in your bean, its getters and setters.
Then, you compare it to the DynaFormControl.
#ManagedBean
#SessionScoped
public class DynaFormController implements Serializable {
private static final long serialVersionUID = 1L;
private DynaFormModel model;
private BookProperty bookProperty;
public String getBookProperty() {
return bookProperty;
}
public void setBookProperty(BookProperty bookProperty) {
this.bookProperty = bookProperty;
}
public String submitForm() {
//your code
List<DynaFormControl> controls = model.getControls();
for (DynaFormControl control : controls) {
if(control.getData() instanceof BookProperty) {
BookProperty bp = (BookProperty) c.getData();
//use the object
}
}
return null;
}
}
I have a question regarding the lifecycle of session scoped CDI beans.
As far as I understand, a session scoped CDI bean is constructed by the container when the session starts and destroyed when the session ends. Before the bean is destroyed the #PreDestroy Method is invoked as described here https://docs.oracle.com/javaee/6/tutorial/doc/gmgkd.html. It also says to release resources in this method.
In a JSF application I build I experience Memory Leak because the bean doesn't seem to be destroyed and hence the #PreDestroy Method is not invoked to free some references for the garbage collector. So I built a simple Application to test the behavior. My experience is that the session bean doesn't get destroyed when the session is over and furthermore it doesn't even get destroyed when the memory space is needed. I cannot believe I am the first to encounter this, but I don't find any information about this behavior..
So my question is: Shouldn't a CDI bean be destroyed - and hence the #PreDestroy Method be invoked - immediately after its context expired? And if not shouldn't it be at least destroyed when the space is needed?
My test Application:
I am not allowed to post a picture, but the outline is the very basic jsf webapp generated by eclipse. I also have the beans.xml file.
Test.java:
package com.test;
import java.io.Serializable;
import java.util.ArrayList;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
#SessionScoped
#Named
public class Test implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private String test;
private ArrayList<ComplexType> cps;
private ArrayList<ComplexType> cps_2;
#PostConstruct
public void init() {
System.out.println("test postconstruct..");
test = "Cdi Test";
}
#PreDestroy
public void cleanUp() {
cps = null;
cps_2 = null;
System.out.println("test cleanUp....");
}
public void data_1() {
cps = new ArrayList<ComplexType>();
for(int i = 0; i < 800; i++) {
String[] s = new String[100000];
ComplexType cp = new ComplexType(i, s);
cps.add(cp);
System.out.println(i);
}
System.out.println("data_1");
}
public void free_1() {
cps = null;
System.out.println("free_1");
}
public void data_2() {
cps_2 = new ArrayList<ComplexType>();
for(int i = 0; i < 800; i++) {
String[] s = new String[100000];
ComplexType cp = new ComplexType(i, s);
cps_2.add(cp);
System.out.println(i);
}
System.out.println("data_1");
}
public void free_2() {
cps_2 = null;
System.out.println("free_1");
}
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
ComplexType.java:
package com.test;
public class ComplexType {
private int id;
private String[] name;
public ComplexType(int id, String[] name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String[] getName() {
return name;
}
public void setName(String[] name) {
this.name = name;
}
}
index.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
>
<h:head>
<title>Cdi test </title>
</h:head>
<h:body>
<h:outputText value="#{test.test}"></h:outputText>
<h:form>
<h:commandButton value="cp_1 data" actionListener="#{test.data_1}">
<f:ajax></f:ajax>
</h:commandButton>
<h:commandButton value="cp_1 Free" actionListener="#{test.free_1}">
<f:ajax></f:ajax>
</h:commandButton>
<br></br>
<h:commandButton value="cp_2 data" actionListener="#{test.data_2}">
<f:ajax></f:ajax>
</h:commandButton>
<h:commandButton value="cp_2 Free" actionListener="#{test.free_2}">
<f:ajax></f:ajax>
</h:commandButton>
</h:form>
</h:body>
</html>
I open the index.xhtml page and the #PostConstruct Method gets invoked as expected. The heap space is exceeded when I invoke data_1 and data_2 both without freeing in between. When I free one of the resources in between or I invoke one method twice in a row the heap space is enough, as the garbage collector frees the memory. This works as I would expect it to work.
But when I invoke one data function, close the browser and hence the session, open a new browser and invoke one of the data functions again, then the application stops working as (I guess) the memory space is exceeded. The point is: the first session bean doesn't get destroyed and its #PreDestroy Method not invoked and therefore the ArrayList is still in the memory.
Can someone please explain to me what is going on here? Shouldn't a CDI bean be destroyed by the container as soon its context expires so that references can be set to null and the garbage collector can free resources?
I am using JBoss AS 7.1.1 and its default implementation JSF Mojarra 2.1.
Session beans (regardless CDI or JSF managed) stay alive until some session timeout exceeds (usually 30 minutes by default, dependent on application server), which you can specify in web.xml. Just closing the browser doesn't invalidate session and it wait to be destroyed by servlet container after timeout expiration. So, my assumption, such behaviour is just fine, #PreDestroy method will be invoked later.
The answer of #olexd basically explains what I was getting wrong in my mind, thank you very much! But invalidating the session after a determined period is not an option, so I had to use the comment of #geert3 as well, thank you for that! I am answering my own question to show how I have solved my particular problem in detail here.
What I was wrong about: I thought the session expires as soon as the browser is closed. This is wrong and it makes sense. One may want to close the browser and open it again to work in the same session as before.
For me this behaviour is not appropriate because I want to release resources as soon as the browser gets closed. So the answer is to manually invalidate the session like this:
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
As soon as this method is called, the #PreDestroy Method is called, exactly as I want it. Now I had to determine when to call this function. I searched for a way to listen to something like a browserclose event. There are the onbeforeunload and onunload events. onunload doesn't seem to work for me in Chrome, but the onbeforeunload does. See also this answer:
https://stackoverflow.com/a/16677225/1566562
So I wrote a hidden button that gets clicked by javascript on beforeunload and invokes an appropriate backingbean method. This works as I would expect it to work. I tested it on Chrome 43.0.2357.65 and IE 11, for now I am content with it. However it doesn't work with onunload, but this is not of concern for me right now.
So my final code likes this:
index.xhtml
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Cdi test</title>
<h:outputScript library="default" name="js/jquery-1.11.3.min.js"
target="head"></h:outputScript>
</h:head>
<h:body>
<h:outputText value="#{test.test}"></h:outputText>
<h:form id="overall">
<h:commandButton value="cp_1 data" actionListener="#{test.data_1}">
<f:ajax></f:ajax>
</h:commandButton>
<h:commandButton value="cp_1 Free" actionListener="#{test.free_1}">
<f:ajax></f:ajax>
</h:commandButton>
<br></br>
<h:commandButton value="cp_2 data" actionListener="#{test.data_2}">
<f:ajax></f:ajax>
</h:commandButton>
<h:commandButton value="cp_2 Free" actionListener="#{test.free_2}">
<f:ajax></f:ajax>
</h:commandButton>
<br></br>
<h:commandButton id="b" style="display:none"
actionListener="#{test.invalidate}"></h:commandButton>
</h:form>
<script type="text/javascript">
$(window).on('beforeunload', function() {
$('#overall\\:b').click();
});
</script>
</h:body>
</html>
Test.java
package com.test;
import java.io.Serializable;
import java.util.ArrayList;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.SessionScoped;
import javax.faces.context.FacesContext;
import javax.inject.Named;
#SessionScoped
#Named
public class Test implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private String test;
private ArrayList<ComplexType> cps;
private ArrayList<ComplexType> cps_2;
#PostConstruct
public void init() {
System.out.println("test postconstruct..");
test = "Cdi Test";
}
#PreDestroy
public void cleanUp() {
cps = null;
cps_2 = null;
System.out.println("test cleanUp....");
}
public void data_1() {
cps = new ArrayList<ComplexType>();
for (int i = 0; i < 800; i++) {
String[] s = new String[100000];
ComplexType cp = new ComplexType(i, s);
cps.add(cp);
System.out.println(i);
}
System.out.println("data_1");
}
public void free_1() {
cps = null;
System.out.println("free_1");
}
public void data_2() {
cps_2 = new ArrayList<ComplexType>();
for (int i = 0; i < 800; i++) {
String[] s = new String[100000];
ComplexType cp = new ComplexType(i, s);
cps_2.add(cp);
System.out.println(i);
}
System.out.println("data_2");
}
public void free_2() {
cps_2 = null;
System.out.println("free_2");
}
public void invalidate() {
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
System.out.println("invalidate");
}
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
Note that I have used JQuery. This works with JBoss AS 7.1.1 and the default Weld implementation as well.
One thing to add: one doesn't have to set all the referenes manually to null. This makes sense as well, as it would be tedious..
Is it possible to override renderer used by <h:selectOneRadio>? I tried to find the class from jsf-impl package of JSF 2.2 but didn't find it. The reason I want to do this is to get rid of the table it generates.
Is it possible to override renderer used by h:selectOneRadio?
Yes, surely it is. Otherwise, UI component libraries like PrimeFaces couldn't exist.
I tried to find the class from jsf-impl package but didn't find it.
The exact class depends on the JSF implementation you're using. If it's Mojarra, then it's the com.sun.faces.renderkit.html_basic.RadioRenderer class. If it's MyFaces, then it's the org.apache.myfaces.renderkit.html.HtmlRadioRenderer class.
In order to properly override it, just extend the class and override methods where necessary and register it as follows in your faces-config.xml:
<render-kit>
<renderer>
<component-family>javax.faces.SelectOne</component-family>
<renderer-type>javax.faces.Radio</renderer-type>
<renderer-class>com.example.MyRadioRenderer</renderer-class>
</renderer>
</render-kit>
Keep in mind that you're this way tight-coupling the renderer to the specific JSF impl/version. Such an extended renderer is not compatible with a different JSF implementation (i.e. your app wouldn't deploy when you ever replace Mojarra by MyFaces) and may possibly break when the current JSF implementation has been updated to a newer version. If you worry about this, consider writing the renderer entirely from scratch, like PrimeFaces et.al. do.
The reason I want to do this is to get rid of the table it generates.
Consider looking at Tomahawk or PrimeFaces instead of reinventing the wheel. They have respectively a <t:selectOneRadio layout="spread"><t:radio> and <p:selectOneRadio layout="custom"><p:radioButton> which allows you positioning those things everywhere you want.
See also:
<h:selectOneRadio> renders table element, how to avoid this?
I added
<render-kit>
<renderer>
<component-family>javax.faces.SelectOne</component-family>
<renderer-type>javax.faces.Radio</renderer-type>
<renderer-class>com.sial.ecommerce.configurator.ui.model.RadioRendererWithoutDataTable</renderer-class>
</renderer>
</render-kit>
to faces-config.xml.
And created a class which extends com.sun.faces.renderkit.html_basic.RadioRenderer And I did override the method encodeEnd then commented out the code which adding table elements.
public class RadioRendererWithoutDataTable extends com.sun.faces.renderkit.html_basic.RadioRenderer {
#Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
rendererParamsNotNull(context, component);
if (!shouldEncode(component)) {
return;
}
ResponseWriter writer = context.getResponseWriter();
assert (writer != null);
String alignStr;
Object borderObj;
boolean alignVertical = false;
int border = 0;
if (null != (alignStr = (String) component.getAttributes().get("layout"))) {
alignVertical = alignStr.equalsIgnoreCase("pageDirection");
}
if (null != (borderObj = component.getAttributes().get("border"))) {
border = (Integer) borderObj;
}
Converter converter = null;
if (component instanceof ValueHolder) {
converter = ((ValueHolder) component).getConverter();
}
// renderBeginText(component, border, alignVertical, context, true);
Iterator<SelectItem> items = RenderKitUtils.getSelectItems(context, component);
Object currentSelections = getCurrentSelectedValues(component);
Object[] submittedValues = getSubmittedSelectedValues(component);
Map<String, Object> attributes = component.getAttributes();
OptionComponentInfo optionInfo = new OptionComponentInfo((String) attributes.get("disabledClass"),
(String) attributes.get("enabledClass"), (String) attributes.get("unselectedClass"),
(String) attributes.get("selectedClass"), Util.componentIsDisabled(component), isHideNoSelection(component));
int idx = -1;
while (items.hasNext()) {
SelectItem curItem = items.next();
idx++;
// If we come across a group of options, render them as a nested
// table.
if (curItem instanceof SelectItemGroup) {
// write out the label for the group.
if (curItem.getLabel() != null) {
// if (alignVertical) {
// writer.startElement("tr", component);
// }
//writer.startElement("td", component);
writer.writeText(curItem.getLabel(), component, "label");
// writer.endElement("td");
// if (alignVertical) {
// writer.endElement("tr");
// }
}
// if (alignVertical) {
// writer.startElement("tr", component);
// }
// writer.startElement("td", component);
// writer.writeText("\n", component, null);
// renderBeginText(component, 0, alignVertical, context, false);
// render options of this group.
SelectItem[] itemsArray = ((SelectItemGroup) curItem).getSelectItems();
for (int i = 0; i < itemsArray.length; ++i) {
renderOption(context, component, converter, itemsArray[i], currentSelections, submittedValues, alignVertical, i,
optionInfo);
}
// renderEndText(component, alignVertical, context);
// writer.endElement("td");
// if (alignVertical) {
// writer.endElement("tr");
// writer.writeText("\n", component, null);
// }
} else {
renderOption(context, component, converter, curItem, currentSelections, submittedValues, alignVertical, idx, optionInfo);
}
}
//renderEndText(component, alignVertical, context);
}
Then it worked for me.
When I given
<h:selectOneRadio >
<f:selectItem itemValue="1" itemLabel="Item 1" />
<f:selectItem itemValue="2" itemLabel="Item 2" />
</h:selectOneRadio>
in my jsf page.
It converted to
<input type="radio" name="bulkForm:j_idt224" id="bulkForm:j_idt224:0" value="1"><label for="bulkForm:j_idt224:0"> Item 1</label>
<input type="radio" name="bulkForm:j_idt224" id="bulkForm:j_idt224:1" value="2"><label for="bulkForm:j_idt224:1"> Item 2</label>
which was what I need.
I'm trying to get a composite component working with it's own backing bean,
using the example on p375 from the Core JSF 3 book, but just get an NPE. The problem seems to be at the start of encodeBegin(), Date date = (Date) getValue() returns null.
If I'm honest I don't really understand where the value of the component is supposed to
be getting stored, I specify it as a java.util.Date using cc:attribute type=, but I
don't really understand how this: public Object getSubmittedValue() { return this; } -
which is going to return an instance of an InputDateBean class - results in a Date. I am generally good and confused by how this is supposed to work.
Unlike the book example I am trying to the use backing component for temporary storage,
so when the day is input I try to store it in #{cc.day}, in the book they use an application scoped bean for some reason.
Thanks for any help. I am using Mojarra 2.1.
inputDate.xhtml
<cc:interface componentType="uk.co.myco.jsfbeans.sqcc.InputDateBean">
<cc:attribute name="value" type="java.util.Date"/>
</cc:interface>
<cc:implementation>
<h:panelGrid columns="3">
<h:inputText id="day" value="#{cc.day}"
converter="javax.faces.Integer"/>
<h:inputText id="month" value="#{cc.month}"
converter="javax.faces.Integer"/>
<h:inputText id="year" value="#{cc.year}"
converter="javax.faces.Integer"/>
</h:panelGrid>
</cc:implementation>
InputDateBean.java
package uk.co.myco.jsfbeans.sqcc;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import javax.faces.component.FacesComponent;
import java.util.GregorianCalendar;
import javax.faces.application.FacesMessage;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.convert.ConverterException;
import uk.co.myco.general.SQLog;
import uk.co.myco.jsfbeans.helper.Messages;
#FacesComponent(value = "uk.co.myco.jsfbeans.sqcc.InputDateBean")
public class InputDateBean extends UIInput implements NamingContainer {
private int day = 0, month = 0, year = 0;
public InputDateBean() {
}
#Override
public String getFamily() {
return "javax.faces.NamingContainer";
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
Date date = (Date) getValue();
Calendar cal = new GregorianCalendar();
cal.setTime(date);
UIInput dayComponent = (UIInput) findComponent("day");
UIInput monthComponent = (UIInput) findComponent("month");
UIInput yearComponent = (UIInput) findComponent("year");
dayComponent.setValue(cal.get(Calendar.DATE));
monthComponent.setValue(cal.get(Calendar.MONTH) + 1);
yearComponent.setValue(cal.get(Calendar.YEAR));
super.encodeBegin(context);
}
#Override
public Object getSubmittedValue() {
return this;
}
#Override
protected Object getConvertedValue(FacesContext context, Object newSubmittedValue)
throws ConverterException {
UIInput dayComponent = (UIInput) findComponent("day");
UIInput monthComponent = (UIInput) findComponent("month");
UIInput yearComponent = (UIInput) findComponent("year");
int lday = (Integer) dayComponent.getValue();
int lmonth = (Integer) monthComponent.getValue();
int lyear = (Integer) yearComponent.getValue();
if (isValidDate(lday, lmonth, lyear)) {
return new GregorianCalendar(lyear, lmonth - 1, lday).getTime();
} else {
FacesMessage message = Messages.getMessage("util.messages", "invalidDate", null);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ConverterException(message);
}
}
// getters & setters & isValidDate() removed
}
I now see my mistake. The problem was that the composite component has to be
called with a Date object, i.e. <cclib:inputDate value="#{bean.date}"/>. As the
code stands the date needs to be instantiated, but it wasn't. The more robust
way of doing this is to do a new Date() in encodeBegin() in the event that
getValue() is null. This then works the same a h:inputText/f:convertDateTime
which does not require that the value is instantiated.