I have a data object called DeliveryPeriod which is a container for a start and a end date (saved as String like dd.MM.yyy, comes from the database) an the id of another object called PlanningPeriod. This delivery period should be displayed in its own custom component in JSF like
<myc:deliveryPeriodComponent value="#{backendBean.deliveryPeriod}" />
I implement a class DeliveryPeriodComponent which extends UIInput and a DeliveryPeriodComponentRenderer which extendes javax.faces.renderer. The rendering works well, i see two calender elements and a SelectOneMenu to choose the planning period. But render the data is not all, I need to change the data as well. And here comes the problem, i have no idea to get the data inside my component to the backend bean. The decode() method did not know the new values and the other methods are never called. I didn't know the trick, how to connect the JSF page to the bean, from the tutorial (http://jsfatwork.irian.at, i bought the book) i had these methods like getValue() and getConverter().
Here is the code from the component:
public class DeliveryPeriodComponent extends UIInput {
public static final String COMPONENT_TYPE = "de.hacon.tps.integrator.web.component.deliveryperiod.DeliveryPeriodComponent";
enum PropertyKeys {
begin, end, planningPeriod
}
public DeliveryPeriodComponent() {
setRendererType("de.hacon.tps.integrator.web.component.deliveryperiod.DeliveryPeriodComponent");
}
public String getBegin() {
return (String) getStateHelper().eval(PropertyKeys.begin, "01.01.2012");
}
public void setBegin(String begin) {
getStateHelper().put(PropertyKeys.begin, begin);
}
public String getEnd() {
return (String) getStateHelper().eval(PropertyKeys.end, "31.12.2012");
}
public void setEnd(String end) {
getStateHelper().put(PropertyKeys.end, end);
}
public int getPlanningPeriod() {
return (Integer) getStateHelper().eval(PropertyKeys.planningPeriod, 0);
}
public void setPlanningPeriod(int planningPeriod) {
getStateHelper().put(PropertyKeys.planningPeriod, planningPeriod);
}}
And here is the renderer:
public class DeliveryPeriodComponentRenderer extends Renderer {
SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy");
#Override
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
DeliveryPeriodComponent comp = (DeliveryPeriodComponent) component;
String clientId = comp.getId();
try {
encodeInput(context, comp, clientId);
} catch (ParseException e) {
e.printStackTrace();
}
}
private void encodeInput(FacesContext context, DeliveryPeriodComponent comp, String clientId) throws ParseException {
comp.getChildren().clear();
DeliveryPeriod value = (DeliveryPeriod) comp.getAttributes().get("value");
List<PlanningPeriodSubset> pp = (List<PlanningPeriodSubset>) comp.getAttributes().get("periods");
HtmlPanelGrid pGrid = new HtmlPanelGrid();
pGrid.setColumns(4);
Calendar cBegin = new Calendar();
cBegin.setShowOn("button");
cBegin.setValue(sdf.parse(value.getStartDate()));
cBegin.setPattern("dd.MM.yyyy");
pGrid.getChildren().add(cBegin);
Calendar cEnd = new Calendar();
cEnd.setShowOn("button");
cEnd.setValue(sdf.parse(value.getEndDate()));
cEnd.setPattern("dd.MM.yyyy");
pGrid.getChildren().add(cEnd);
HtmlSelectOneMenu sPlanningPeriod = new HtmlSelectOneMenu();
Collection<UISelectItem> items = new ArrayList<UISelectItem>();
for (PlanningPeriodSubset op : pp) {
UISelectItem item = new UISelectItem();
item.setItemLabel(op.getName());
item.setItemValue(op.getId());
items.add(item);
}
sPlanningPeriod.getChildren().addAll(items);
sPlanningPeriod.setValue(value.getPlanningPeriodId());
pGrid.getChildren().add(sPlanningPeriod);
HtmlPanelGrid buttonPanel = new HtmlPanelGrid();
buttonPanel.setColumns(2);
Button bDelete = new Button();
bDelete.setValue(" - ");
buttonPanel.getChildren().add(bDelete);
Button bInfo = new Button();
bInfo.setValue(" i ");
buttonPanel.getChildren().add(bInfo);
pGrid.getChildren().add(buttonPanel);
comp.getChildren().add(pGrid);
}
#Override
public void decode(FacesContext context, UIComponent component) {
DeliveryPeriodComponent deliveryComponent = (DeliveryPeriodComponent) component;
DeliveryPeriod deliveryPeriod = new DeliveryPeriod();
deliveryPeriod.setStartDate(deliveryComponent.getBegin());
deliveryPeriod.setEndDate(deliveryComponent.getEnd());
deliveryPeriod.setPlanningPeriodId(deliveryComponent.getPlanningPeriod());
// Map<String, String> params = context.getExternalContext().getRequestParameterMap();
// String clientId = component.getClientId();
// String value = params.get(clientId);
// ((UIInput) component).setSubmittedValue(value);
}
#Override
public Object getConvertedValue(FacesContext context, UIComponent component, Object submittedValue)
throws ConverterException {
Converter converter = getConverter(context, (DeliveryPeriodComponent) component);
if (converter != null) {
return converter.getAsObject(context, component, (String) submittedValue);
} else {
return submittedValue;
}
}
private Object getValue(FacesContext context, DeliveryPeriodComponent comp) {
Object submittedValue = comp.getSubmittedValue();
if (submittedValue != null) {
return submittedValue;
}
Object begin = comp.getBegin();
Object end = comp.getEnd();
Object planningPeriod = comp.getPlanningPeriod();
DeliveryPeriod period = new DeliveryPeriod();
period.setStartDate((String) begin);
period.setEndDate((String) end);
period.setPlanningPeriodId((Integer) planningPeriod);
Converter converter = this.getConverter(context, comp);
if (converter != null) {
return converter.getAsString(context, comp, period);
} else if (period != null) {
return period.toString();
} else {
return "";
}
}
private Converter getConverter(FacesContext context, DeliveryPeriodComponent comp) {
Converter conv = ((UIInput) comp).getConverter();
if (conv != null) {
ValueExpression exp = comp.getValueExpression("value");
if (exp == null) {
return null;
} else {
Class valueType = exp.getType(context.getELContext());
if (valueType == null) {
return null;
} else {
return context.getApplication().createConverter(valueType);
}
}
}
return null;
}}
Maybe this is a trivial problem, I am not a pro at JSF and still learning. And it is very hard to write your own components when you almost confused about the basics :-( Still learning new things every day. Thank you for your help!
(I found a lot on custom components with examples like a custum inputfields, these components works well and transfer their data. Unfortunatly I found nothing on custom components which contains more then one input field or did something different from the existing JSF elements)
In Xhtml Page Call your listner with
<h:commandButton id="add" value="Add More" styleClass="input_right_cor" type="button">
<f:ajax execute="#this" listener="#{templateSearchHandler.AddRow}" />
</h:commandButton>
Related
Im trying to write validation in Vaadin but I don't understand how to check if date field is empty
I wrote something like this
#Override
public void setConfiguration(EditorConfiguration editorConfiguration) {
boolean required = ((DateFieldConfiguration) editorConfiguration).isRequired();
if (required == true) {
setRequiredIndicatorVisible(true);
addValueChangeListener(event -> validate(event.getSource().getDefaultValidator(), event.getValue()));
}
}
private void validate(Validator<LocalDate> defaultValidator, LocalDate localDate) {
binder.forField(this).withValidator(validator).asRequired("Mandatory").bind(s -> getValue(),
(b, v) -> setValue(v));
}
I have achived a validation with a text field:
String Validator code
public class VaadinStringEditor extends TextField implements HasValueComponent<String> {
/**
*
*/
private static final long serialVersionUID = 6271513226609012483L;
private Binder<String> binder;
#PostConstruct
public void init() {
setWidth("100%");
binder = new Binder<>();
}
#Override
public void initDefaults() {
setValue("");
binder.validate();
}
#Override
public void setConfiguration(EditorConfiguration editorConfiguration) {
Validator<String> validator = ((TextFieldConfiguration) editorConfiguration).getValidator();
if (validator != null) {
binder.forField(this).withValidator(validator).asRequired("Mandatory").bind(s -> getValue(),
(b, v) -> setValue(v));
}
and I valid it here:
question.setEditorConfiguration(new TextFieldConfiguration(textRequiredValidator()));
Validator:
private Validator<String> textRequiredValidator() {
return Validator.from(v -> v != null && StringUtils.trimAllWhitespace((String) v).length() != 0,
, "Not empty");
}
You should use com.vaadin.ui.DateField for LocalDate values. Have a look at the following example.
Example bean:
public class MyBean {
private LocalDate created;
public LocalDate getCreated() {
return created;
}
public void setCreated(LocalDate created) {
this.created = created;
}
}
Editor
DateField dateField = new DateField("Date selector");
binder.forField(dateField)
.bind(MyBean::getCreated, MyBean::setCreated);
If for some reason you would like to have com.vaadin.ui.TextField for editing date, then you need to set converter like this:
Binder<MyBean> binder = new Binder<>();
TextField textDateField = new TextField("Date here:");
binder.forField(textDateField)
.withNullRepresentation("")
.withConverter(new StringToLocalDateConverter())
.bind(MyBean::getCreated, MyBean::setCreated);
Converter implementation:
public class StringToLocalDateConverter implements Converter<String, LocalDate> {
#Override
public Result<LocalDate> convertToModel(String userInput, ValueContext valueContext) {
try {
return Result.ok(LocalDate.parse(userInput));
} catch (RuntimeException e) {
return Result.error("Invalid value");
}
}
#Override
public String convertToPresentation(LocalDate value, ValueContext valueContext) {
return Objects.toString(value, "");
}
}
Note that this converter does not utilise ValueContext object that contains information that should be taken into account in more complex cases. For example, user locale should be handled.
I'm new to android development and I have been struggling to parse more than one tag at a time and display it in a ListView.
I'm using SAX parser, here is my RssParseHandler code.
public class RssParseHandler extends DefaultHandler {
private List<RssItem> rssItems;
private RssItem currentMessage;
//private StringBuilder builder;
private boolean parseLink;
private boolean parseTitle;
private boolean parseDate;
private boolean parseDes;
public RssParseHandler() {
rssItems = new ArrayList();
}
public List<RssItem> getItems() {
return this.rssItems;
}
#Override
public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException {
super.startElement(uri, localName, name, attributes);
if (localName.equalsIgnoreCase("item")) {
this.currentMessage = new RssItem();
} else if (localName.equalsIgnoreCase("title")) {
//currentMessage.setTitle(builder.toString());
parseTitle = true;
} else if (localName.equalsIgnoreCase("link")) {
//currentMessage.setLink(builder.toString());
parseLink = true;
} else if (localName.equalsIgnoreCase("description")) {
//currentMessage.setDescription(builder.toString());
parseDes = true;
} else if (localName.equalsIgnoreCase("pubDate")) {
//currentMessage.setDate(builder.toString());
parseDate = true;
}
//parsing enclosure tag
else if ("enclosure".equals(localName)) {
// Get tags attributes number
int attrsLength = attributes.getLength();
for (int i = 0; i < attrsLength; i++) {
String attrName = attributes.getQName(i); // attribute name
if ("url".equals(attrName)) // This tag has only one attribute but it is better to check it name is correct
currentMessage.getLink();
}
}
}
#Override
public void endElement(String uri, String localName, String name)
throws SAXException {
super.endElement(uri, localName, name);
if (this.currentMessage != null) {
if (localName.equalsIgnoreCase("item")) {
rssItems.add(currentMessage);
//currentMessage = null;
} else if (localName.equalsIgnoreCase("link")) {
//currentMessage.setLink(builder.toString());
//parseLink = false;
} else if (localName.equalsIgnoreCase("description")) {
//currentMessage.setDescription(builder.toString());
//parseDes = false;
} else if (localName.equalsIgnoreCase("pubDate")){
//currentMessage.setDate(builder.toString());
parseDate = false;
} else if (localName.equalsIgnoreCase("title")) {
//currentMessage.setTitle(builder.toString());
parseTitle = false;
}
//builder.setLength(0);
}
}
#Override
public void characters(char[] ch, int start, int length)
throws SAXException {
super.characters(ch, start, length);
//builder.append(ch, start, length);
if (parseTitle) {
if (currentMessage != null)
currentMessage.setTitle(new String(ch, start, length));
} else if (parseLink) {
if (currentMessage != null) {
currentMessage.setLink(new String(ch, start, length));
//parseLink = false;
}
} else if (parseDes) {
if (currentMessage != null)
currentMessage.setDescription(new String(ch, start, length));
//parseLink = false;
} else if (parseDate) {
if (currentMessage != null) {
currentMessage.setDate(new String(ch, start, length));
//currentMessage.setDate(new String(ch, start, length));
//parseDesc = false;
}
}
}
}
Here is the code for the Listview:
public class ReaderAppActivity extends Fragment {
private ReaderAppActivity local;
private ListView mList;
/**
* This method creates main application view
*/
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//super.onCreate(savedInstanceState);
// Set view
//setContentView(R.layout.fragment_rss);
local = this;
//int position = getArguments().getInt("position");
// String url = getArguments().getString("url");
// List of rivers
String[] menus = getResources().getStringArray(R.array.menus);
// Creating view corresponding to the fragment
View v = inflater.inflate(R.layout.fragment_rss, container, false);
// Set reference to this activity
//local = this;
GetRSSDataTask task = new GetRSSDataTask();
// Start download RSS task
task.execute("http://thechurchofwhatshappeningnow.libsyn.com/rss");
//task.execute(url);
// Debug the thread name
Log.d("ITCRssReader", Thread.currentThread().getName());
//mList = (ListView) findViewById(R.id.rssListMainView);
return v;
}
private class GetRSSDataTask extends AsyncTask<String, Void, List<RssItem> > {
#Override
protected List<RssItem> doInBackground(String... urls) {
// Debug the task thread name
Log.d("ITCRssReader", Thread.currentThread().getName());
try {
// Create RSS reader
RssReader rssReader = new RssReader(urls[0]);
// Parse RSS, get items
return rssReader.getItems();
} catch (Exception e) {
Log.e("ITCRssReader", e.getMessage());
}
return null;
}
#Override
protected void onPostExecute(List<RssItem> result) {
// Get a ListView from main view
ListView mList = (ListView) getView().findViewById(R.id.rssListMainView);
// Create a list adapter
ArrayAdapter<RssItem> adapter = new ArrayAdapter<RssItem>(getActivity(),R.layout.rss_text, result);
//ArrayAdapter<RssItem> adapter = new ArrayAdapter<RssItem>(getActivity(),R.layout.fragment_rss, result);
// Set list adapter for the ListView
mList.setAdapter(adapter);
// Set list view item click listener
mList.setOnItemClickListener(new ListListener(result, getActivity()));
}
}
}
What am I doing wrong? I can't figure it out. I would like to parse, the link, description, pubDate, and pass them into the ListView. Ideally I would only display the title and episode number in the listview, and pass the other tags into String, so I can display them when I click an item in the listView.
I've created another class called SingleMenuItem to be called when I click an item in the ListView, it's just filler code right now, it does not display anything because the items aren't parsed.
Any help would be appreciated. Here is a RSS link to the feed:
public class SingleMenuItem extends Activity {
// XML node keys
static final String KEY_NAME = "name";
static final String KEY_DATE = "pubdate";
static final String KEY_DESC = "description";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.single_list_item);
// getting intent data
Intent in = getIntent();
// Get XML values from previous intent
String name = in.getStringExtra(KEY_NAME);
String date = in.getStringExtra(KEY_DATE);
String description = in.getStringExtra(KEY_DESC);
// Displaying all values on the screen
TextView lblName = (TextView) findViewById(R.id.name_label);
TextView lblDate = (TextView) findViewById(R.id.date_label);
TextView lblDesc = (TextView) findViewById(R.id.description_label);
lblName.setText(name);
lblDate.setText(date);
lblDesc.setText(description);
}
}
Here is the code for my ReaderAppActivty that puts the results of the parsing into the ListView:
public class ReaderAppActivity extends Fragment {
private ReaderAppActivity local;
private ListView mList;
/**
* This method creates main application view
*/
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//super.onCreate(savedInstanceState);
// Set view
//setContentView(R.layout.fragment_rss);
local = this;
//int position = getArguments().getInt("position");
// String url = getArguments().getString("url");
// List of rivers
String[] menus = getResources().getStringArray(R.array.menus);
// Creating view corresponding to the fragment
View v = inflater.inflate(R.layout.fragment_rss, container, false);
// Set reference to this activity
//local = this;
GetRSSDataTask task = new GetRSSDataTask();
// Start download RSS task
task.execute("http://thechurchofwhatshappeningnow.libsyn.com/rss");
//task.execute(url);
// Debug the thread name
Log.d("ITCRssReader", Thread.currentThread().getName());
//mList = (ListView) findViewById(R.id.rssListMainView);
return v;
}
private class GetRSSDataTask extends AsyncTask<String, Void, List<RssItem> > {
#Override
protected List<RssItem> doInBackground(String... urls) {
// Debug the task thread name
Log.d("ITCRssReader", Thread.currentThread().getName());
try {
// Create RSS reader
RssReader rssReader = new RssReader(urls[0]);
// Parse RSS, get items
return rssReader.getItems();
} catch (Exception e) {
Log.e("ITCRssReader", e.getMessage());
}
return null;
}
#Override
protected void onPostExecute(List<RssItem> result) {
// Get a ListView from main view
ListView mList = (ListView) getView().findViewById(R.id.rssListMainView);
// Create a list adapter
ArrayAdapter<RssItem> adapter = new ArrayAdapter<RssItem>(getActivity(),R.layout.rss_text, result);
//ArrayAdapter<RssItem> adapter = new ArrayAdapter<RssItem>(getActivity(),R.layout.fragment_rss, result);
// Set list adapter for the ListView
mList.setAdapter(adapter);
// Set list view item click listener
mList.setOnItemClickListener(new ListListener(result, getActivity()));
}
}
}
Based on the amount of commented-out code in your RssParseHandler, you've clearly been struggling with this for a bit, and some early attempts were closer to right than what you've got now.
The issue with your current code appears to be that you're not consistently resetting the booleans that drive which part of the item you're setting. Debugging through it, I saw it setting a date into the link field at some point.
But you're actually doing some of that setting in the wrong method, as the characters method doesn't necessarily give you the full contents of the tag. You need to use a Stringbuilder, and I can see from commented-out code that you tried that at some point.
If you collect the text in a stringbuilder and do all the setting in the endElement method, you don't really need the booleans at all, as the endElement method has knowledge of which tag you're ending.
Here's a working version that's perhaps not too far from something you had at some point but which gets rid of all those flag fields.
public class RssParseHandler extends DefaultHandler {
private List<RssItem> rssItems;
private RssItem currentMessage;
private StringBuilder builder;
public RssParseHandler() {
rssItems = new ArrayList<>();
builder = new StringBuilder();
}
public List<RssItem> getItems() {
return this.rssItems;
}
#Override
public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException {
super.startElement(uri, localName, name, attributes);
builder.setLength(0);
if (localName.equalsIgnoreCase("item")) {
this.currentMessage = new RssItem();
}
}
#Override
public void endElement(String uri, String localName, String name)
throws SAXException {
super.endElement(uri, localName, name);
if (this.currentMessage != null) {
if (localName.equalsIgnoreCase("item")) {
rssItems.add(currentMessage);
currentMessage = null;
} else if (localName.equalsIgnoreCase("link")) {
currentMessage.setLink(builder.toString());
} else if (localName.equalsIgnoreCase("description")) {
currentMessage.setDescription(builder.toString());
} else if (localName.equalsIgnoreCase("pubDate")){
currentMessage.setDate(builder.toString());
} else if (localName.equalsIgnoreCase("title")) {
currentMessage.setTitle(builder.toString());
}
}
}
#Override
public void characters(char[] ch, int start, int length)
throws SAXException {
super.characters(ch, start, length);
builder.append(ch, start, length);
}
}
I need to log actions fired from managed Bean.This link , Logging the invoked managed bean action in a PhaseListener helps me solve the problem related to actions. But, when I use actionListener , I have a NullPointerException
#Override
public void beforePhase(PhaseEvent event) {
FacesContext context = event.getFacesContext();
if (context.isPostback()) {
UICommand component = findInvokedCommandComponent(context);
if (component != null) {
String methodExpression = component.getActionExpression().getExpressionString();
// It'll contain #{bean.action}.
}
}
}
private UICommand findInvokedCommandComponent(FacesContext context) {
UIViewRoot view = context.getViewRoot();
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
if (context.getPartialViewContext().isAjaxRequest()) {
return (UICommand) view.findComponent(params.get("javax.faces.source"));
} else {
for (String clientId : params.keySet()) {
UIComponent component = view.findComponent(clientId);
if (component instanceof UICommand) {
return (UICommand) component;
}
}
}
return null;
}
THe NullPointerException occurs with line
String methodExpression = component.getActionExpression().getExpressionString();
How can I get the name of the actionListener method ?
I tried
private UICommand findInvokedCommandComponent(FacesContext context) {
UIViewRoot view = context.getViewRoot();
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
if (context.getPartialViewContext().isAjaxRequest()) {
UIComponent component = view.findComponent(params.get("javax.faces.source"));
if (component instanceof UICommand) {
// component.get
UICommand comp= (UICommand) component;
ActionListener[] actionListeners= comp.getActionListeners();
System.out.println("Taille des Listeners : "+actionListeners.length);
ActionListener method;
method = actionListeners[0];
String toString = method.toString();
System.out.println("ActionListener : "+toString);
return (UICommand) component;
}
} else {
for (String clientId : params.keySet()) {
UIComponent component = view.findComponent(clientId);
if (component instanceof UICommand) {
return (UICommand) component;
}
}
}
return null;
}
System.out.println("ActionListener : "+toString); returns ActionListener : `javax.faces.event.MethodExpressionActionListener#16a779b` . What I would like to have is `#{bean.action}` .Maybe I did it the wrong way
You were on the good way, but you need to get the MethodExpressionActionListener which implements the ActionListener. Using it, you still can't get the MethodExpression out of the box, probably the only way is to get it by reflection (not the best thing...).
That said, you can modify your code like this :
if (component != null) {
String methodExpression = "";
if(component.getActionExpression() != null)
{
methodExpression = component.getActionExpression().getExpressionString();
}
else if(component.getActionListeners().length > 0)
{
methodExpression = getActionListener((MethodExpressionActionListener)component.getActionListeners()[0]).getExpressionString();
}
System.out.println("Method Expression : " + methodExpression);
}
And you will need this method to actually get required information out of the MethodExpressionActionListener :
private MethodExpression getActionListener(MethodExpressionActionListener listener)
{
MethodExpression expression = null;
Field field;
try
{
field = listener.getClass().getDeclaredField("methodExpressionZeroArg");
field.setAccessible(true);
expression = (MethodExpression)field.get(listener);
if(expression == null)
{
field = listener.getClass().getDeclaredField("methodExpressionOneArg");
field.setAccessible(true);
expression = (MethodExpression)field.get(listener);
}
}
catch(Exception e)
{
}
return expression;
}
To internationalize a composite component, you have to put a .properties file that has the exact same name than the component itself and in the same folder.
From the xhtml, you can access these translations through ${cc.resourceBundleMap.key}.
Until now, all is fine and works for me. Where the problems starts is when I add more .properties files for other languages. No matter which local is my computer in, the picked language is the default one (component.properties).
This seems to be a recurrent problem since Ziletka also reports the same problem in How to localize JSF 2 composite components, but remained unanswered.
I have tried all sort of possibilities:
no default .properties file
component_fr.properties
component_fr_CA.properties
component_fr_FR.properties
component_en.properties
component_en_CA.properties
component_en_US.properties
but it results in a:
javax.el.ELException: [...] /resources/component/component.xhtml default="${cc.resourceBundleMap.key}": java.lang.NullPointerException
with default .properties file plus Language specification
component.properties
component_fr.properties
component_en.properties
only the default is loaded.
with default .properties file plus Language and Country specifications
component.properties
component_fr_CA.properties
component_fr_FR.properties
component_en_CA.properties
component_en_US.properties
And again: only the default is loaded.
I would love to avoid having to rely on the backing bean to provide the translations and can't resolve into believing that it is not supported. Can anyone help?
This feature was implemented long time ago in MyFaces Core. See: MYFACES-3308. The test case done can be found here
The locale applied to the composite component depends on the value retrieved from UIViewRoot.getLocale().
Apparently the problem is still there and its root is in the javax.faces.component.UIComponent class particularly in the findComponentResourceBundleLocaleMatch method. The snipped is below
private Resource findComponentResourceBundleLocaleMatch(FacesContext context,
String resourceName, String libraryName) {
Resource result = null;
ResourceBundle resourceBundle = null;
int i;
if (-1 != (i = resourceName.lastIndexOf("."))) {
resourceName = resourceName.substring(0, i) +
".properties"; //THE PROBLEM IS HERE
if (null != context) {
result = context.getApplication().getResourceHandler().
createResource(resourceName, libraryName);
InputStream propertiesInputStream = null;
try {
propertiesInputStream = result.getInputStream();
resourceBundle = new PropertyResourceBundle(propertiesInputStream);
} catch (IOException ex) {
Logger.getLogger(UIComponent.class.getName()).log(Level.SEVERE, null, ex);
} finally{
if(null != propertiesInputStream){
try{
propertiesInputStream.close();
} catch(IOException ioe){
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.log(Level.SEVERE, null, ioe);
}
}
}
}
}
}
result = (null != resourceBundle) ? result : null;
return result;
}
You can see it at the line with a comment that states 'THE PROBLEM IS HERE'. Precisely when it looks for a properties file to load it does not respect any language and/or country code. It always loads a default resource.
Possible solution
The 'problematic' method is called from another method getResourceBundleMap of the same class and a part of the code you are interested in is marked with a comment (line #1000)
// Step 2: if this is a composite component, look for a
// ResourceBundle as a Resource
Which is no surprise as you need a composite component. So the solution would be to define a backing component class for your composite component and redefine resourceBundleMap loading. Below you may find the implementation that respects language only which means it would work for files like componentName_en.properties and componentName_de.properties but would not for something like componentName_en_US.properties
Your .properties files should be in the same directory as the definition of your component
testComponent.properties
testComponent_de.properties
testComponent_en.properties
testComponent_fr.properties
in your component testComponent.xhtmlspecify a definition class in componentType attribute.
<cc:interface componentType="test.component">
....
</cc:interface>
The component may look as following. I used the original code mostly with couple changes. The idea is to override the problematic method and withing the code try reading a properties file for a specified language first and if it is not found, read the default one.
#FacesComponent("test.component")
public class TestComponent extends UINamingContainer {
private static final String PROPERTIES_EXT = ".properties";
private Logger LOGGER = <use one you like>;
private Map<String, String> resourceBundleMap = null;
#Override
public Map<String, String> getResourceBundleMap() {
ResourceBundle resourceBundle = null;
if (null == resourceBundleMap) {
FacesContext context = FacesContext.getCurrentInstance();
UIViewRoot root = context.getViewRoot();
Locale currentLocale = null;
if (null != context) {
if (null != root) {
currentLocale = root.getLocale();
}
}
if (null == currentLocale) {
currentLocale = Locale.getDefault();
}
if (this.getAttributes().containsKey(Resource.COMPONENT_RESOURCE_KEY)) {
Resource ccResource = (Resource)
this.getAttributes().get(Resource.COMPONENT_RESOURCE_KEY);
if (null != ccResource) {
InputStream propertiesInputStream = null;
try {
propertiesInputStream = ccResource.getInputStream();
resourceBundle = findComponentResourceBundleLocaleMatch(ccResource.getResourceName(),
ccResource.getLibraryName(), currentLocale.getLanguage());
} catch (IOException ex) {
LOGGER.error(null, ex);
} finally {
if (null != propertiesInputStream) {
try {
propertiesInputStream.close();
} catch (IOException ioe) {
LOGGER.error(null, ioe);
}
}
}
}
}
if (null != resourceBundle) {
final ResourceBundle bundle = resourceBundle;
resourceBundleMap =
new Map() {
// this is an immutable Map
public String toString() {
StringBuffer sb = new StringBuffer();
Iterator<Map.Entry<String, Object>> entries =
this.entrySet().iterator();
Map.Entry<String, Object> cur;
while (entries.hasNext()) {
cur = entries.next();
sb.append(cur.getKey()).append(": ").append(cur.getValue()).append('\n');
}
return sb.toString();
}
// Do not need to implement for immutable Map
public void clear() {
throw new UnsupportedOperationException();
}
public boolean containsKey(Object key) {
boolean result = false;
if (null != key) {
result = (null != bundle.getObject(key.toString()));
}
return result;
}
public boolean containsValue(Object value) {
Enumeration<String> keys = bundle.getKeys();
boolean result = false;
while (keys.hasMoreElements()) {
Object curObj = bundle.getObject(keys.nextElement());
if ((curObj == value) ||
((null != curObj) && curObj.equals(value))) {
result = true;
break;
}
}
return result;
}
public Set<Map.Entry<String, Object>> entrySet() {
HashMap<String, Object> mappings = new HashMap<String, Object>();
Enumeration<String> keys = bundle.getKeys();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
Object value = bundle.getObject(key);
mappings.put(key, value);
}
return mappings.entrySet();
}
#Override
public boolean equals(Object obj) {
return !((obj == null) || !(obj instanceof Map))
&& entrySet().equals(((Map) obj).entrySet());
}
public Object get(Object key) {
if (null == key) {
return null;
}
try {
return bundle.getObject(key.toString());
} catch (MissingResourceException e) {
return "???" + key + "???";
}
}
public int hashCode() {
return bundle.hashCode();
}
public boolean isEmpty() {
Enumeration<String> keys = bundle.getKeys();
return !keys.hasMoreElements();
}
public Set keySet() {
Set<String> keySet = new HashSet<String>();
Enumeration<String> keys = bundle.getKeys();
while (keys.hasMoreElements()) {
keySet.add(keys.nextElement());
}
return keySet;
}
// Do not need to implement for immutable Map
public Object put(Object k, Object v) {
throw new UnsupportedOperationException();
}
// Do not need to implement for immutable Map
public void putAll(Map t) {
throw new UnsupportedOperationException();
}
// Do not need to implement for immutable Map
public Object remove(Object k) {
throw new UnsupportedOperationException();
}
public int size() {
int result = 0;
Enumeration<String> keys = bundle.getKeys();
while (keys.hasMoreElements()) {
keys.nextElement();
result++;
}
return result;
}
public java.util.Collection values() {
ArrayList<Object> result = new ArrayList<Object>();
Enumeration<String> keys = bundle.getKeys();
while (keys.hasMoreElements()) {
result.add(
bundle.getObject(keys.nextElement()));
}
return result;
}
};
}
if (null == resourceBundleMap) {
resourceBundleMap = Collections.EMPTY_MAP;
}
}
return resourceBundleMap;
}
private ResourceBundle findComponentResourceBundleLocaleMatch(String resourceName, String libraryName, String lng) {
FacesContext context = FacesContext.getCurrentInstance();
ResourceBundle resourceBundle = null;
int i;
if (-1 != (i = resourceName.lastIndexOf("."))) {
if (null != context) {
InputStream propertiesInputStream = null;
try {
propertiesInputStream = getResourceInputStream(context, resourceName.substring(0, i), libraryName, lng);
resourceBundle = new PropertyResourceBundle(propertiesInputStream);
} catch (IOException ex) {
LOGGER.error(null, ex);
} finally {
if (null != propertiesInputStream) {
try {
propertiesInputStream.close();
} catch (IOException ioe) {
LOGGER.error(null, ioe);
}
}
}
}
}
return resourceBundle;
}
private InputStream getResourceInputStream(FacesContext context, final String resourceName, String libraryName, String lng) throws IOException {
InputStream propertiesInputStream = null;
propertiesInputStream = getPropertiesResourceInputStream(context, String.format("%s_%s%s", resourceName, lng, PROPERTIES_EXT), libraryName);
if (null == propertiesInputStream) {
propertiesInputStream = getPropertiesResourceInputStream(context, resourceName + PROPERTIES_EXT, libraryName);
}
return propertiesInputStream;
}
private InputStream getPropertiesResourceInputStream(FacesContext context, final String resourceName, String libraryName) throws IOException {
Resource result = context.getApplication().getResourceHandler().createResource(resourceName, libraryName);
if (null == result) {
return null;
}
return result.getInputStream();
}
}
Done.
However that is obviously a bug in the Mojarra and I hope it will be fixed soon. Closer look to the code related to the composite components reveals that the default .properties file for a component is read twice which I guess is not a very good idea too, but this is another story.
PS. You may easily adjust te code to respect country code as well if you wish.
I am facing a strange problem with p:autoComplete, I get following error
java.lang.NumberFormatException: For input string: "player"
My code is as below
xhtml
<p:autoComplete id="schedChemAC" value="#{testMB.selectedPlayer}" completeMethod="#{testMB.completePlay}" process="#this" var="m" itemLabel="#{m.player}" itemValue="#{m}" converter="#{testConverter}">
<p:ajax event="itemSelect" listener="#{testMB.onSelectFrstL}" process="#this"/>
</p:autoComplete>
MBean
public List<Player> getSelectedPlayer() {
return selectedPlayer;
}
public void setSelectedPlayer(List<Player> selectedPlayer) {
this.selectedPlayer = selectedPlayer;
}
public void getName() {
playerName = playerSession.getAll();
}
public List<Player> completePlay(String query) {
List<Player> suggestion = new ArrayList<Player>();
if (playerName == null) {
getName();
}
for (Player c : playerName) {
if (c.getPlayer().toUpperCase().contains(query.toUpperCase())) {
suggestion.add(c);
}
}
return suggestion;
}
public void onSelectFrstL(SelectEvent event) {
}
Converter
#Named(value = "testConverter")
public class TestConverter implements Converter {
#EJB
PlayerSession playSession;
public static List<Player> playLst;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (playLst == null) {
playLst = playSession.getAll();
}
if (value.trim().equals("")) {
return null;
} else {
try {
int number = Integer.parseInt(value);
for (Player c : playLst) {
if (c.getPk() == number) {
return c;
}
}
} catch (Exception ex) {
System.out.println("error");
}
}
return null;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null || value.equals("")) {
return "";
} else {
return String.valueOf(((Player) value).getPk());
}
}
}
I am not able to find what is wrong in the above code, if i remove the var,itemValue,itemLabel,converter part then it works fine but once i put the var,itemValue,itemLabel,converter code (as given in prime showcase) i get the above error.
Kindly guide me on what is that i am doing wrong or what is that i should check.
Note: My sample table has only two columns, pk(int) & player(string).
I figured out the problem, its basically if i Pass a List to value(AutoComplete) then the Multiple="true" has be used. Whereas to just do one selection i need to pass only Player object to value(AutoComplete).
Hope this helps somebody else who post without understanding how it works (like me).