Primefaces: Bind component value from valueExpression string - primefaces

I read some articles here and they always say:
Don't create components programatically, all you can do by code is possible in xhtml too
So I have an interesting question.
I have component Calendar:
<p:calendar>
<f:attribute name="value" value="#{column.properties['from_value']}" />
</p:calendar>
Or you can simplify it:
<p:calendar value="#{column.properties['from_value']}" />
Properties
column is var of <p:columns> component
It has variable properties which is Map<String,String>
In properties I have key from_value and inside it I have String : "#{bean.object.dateFrom}"
My question is:
How can i convert this string to ValueExpression ?
Because when I run this code. Inside p:calendar is value "#{bean.object.dateFrom}" ... I need to set it as valueExpression and not String
What I get:
Calendar: #{bean.object.dateFrom}
What I want to achieve:
Calendar: 02.11.2017
-
Same code but Programmatically :
public ValueExpression createValueExpression(String valueExpression, Class<?> valueType) {
FacesContext context = FacesContext.getCurrentInstance();
return context.getApplication().getExpressionFactory().createValueExpression(context.getELContext(), valueExpression, valueType);
}
...
Calendar fromCalendar = new Calendar();
fromCalendar.setValueExpression("value", createValueExpression(properties.get("from_value"), Object.class));
Hope it's clear what I want to achieve.
Thank's for replies

Related

PropertyNotFoundException for p:column filterValue attribute

we are trying to migrate our application from tomcat/websphere to was liberty profile.
Additionally we are upgrading the myfaces-version, we are using 2.1, to myfaces-2.2.
To save the current state of a table (filtering) we store the filtered value in a map and read it when loading the table (filterValue attribute of p:column).
When initially loading the table the correct method will be used (in our case its getFilterValue in the DataModel). But if we start filtering a column, the method wont be found anymore and the following exception occurs:
javax.el.PropertyNotFoundException: Die Eigenschaft 'getFilterValue' wurde nicht im Typ package.LazyModel gefunden.
javax.el.BeanELResolver$BeanProperties.get(BeanELResolver.java:245)
javax.el.BeanELResolver$BeanProperties.access$300(BeanELResolver.java:222)
javax.el.BeanELResolver.property(BeanELResolver.java:332)
javax.el.BeanELResolver.getType(BeanELResolver.java:83)
javax.el.CompositeELResolver.getType(CompositeELResolver.java:99)
org.apache.myfaces.el.unified.resolver.FacesCompositeELResolver.getType(FacesCompositeELResolver.java:150)
org.apache.el.parser.AstValue.setValue(AstValue.java:199)
org.apache.el.ValueExpressionImpl.setValue(ValueExpressionImpl.java:257)
org.jboss.weld.el.WeldValueExpression.setValue(WeldValueExpression.java:64)
org.apache.myfaces.view.facelets.el.ContextAwareTagValueExpression.setValue(ContextAwareTagValueExpression.java:153)
org.primefaces.component.datatable.DataTable.processUpdates(DataTable.java:746)
org.apache.myfaces.context.servlet.PartialViewContextImpl$PhaseAwareVisitCallback.visit(PartialViewContextImpl.java:787)
org.apache.myfaces.component.visit.PartialVisitContext.invokeVisitCallback(PartialVisitContext.java:213)
org.primefaces.component.api.UIData.visitTree(UIData.java:822)
The table:
<p:dataTable id="table" var="group"
value="#{bean.lazyModel}"
selection="#{bean.selectedMulti}"
rows="#{bean.lazyModel.rows}" paginator="true"
currentPageReportTemplate="#{msg['data.table.pagereport']}"
paginatorTemplate="#{msg['data.table.paginator']}"
rowsPerPageTemplate="#{msg['data.table.rows']}"
resizableColumns="true" rowKey="#{group.pkId}" lazy="true"
filterDelay="1000" emptyMessage="#{msg['data.table.empty']}"
style="font-size: 8pt;"
tableStyle="font-size: 8pt; table-layout:auto;"
first="#{bean.lazyModel.first_m}"> >
<p:column headerText="Name"
sortBy="#{group.name}" filterBy="#{group.name}"
filterValue="#{bean.lazyModel.getFilterValue('name')}"
filterStyleClass="column_filter" styleClass="wrap">
<h:outputText value="#{group.name}" />
</p:column>
...
The lazymodel:
#Named
#Scope(value = "prototype")
public class LazyModel extends AbstractLazyModel<Brand> {
private static final long serialVersionUID = 2247660292777600670L;
/**
* Konstruktor
*/
public LazyModel() {
super();
}
public Object getFilterValue(final String keyForColumn) {
return this.filterManager.getFilterField(this.getKeyForPage(), keyForColumn);
}
I think this should be the most important things to know.
So, i dont understand what changed between these versions that trigger the exception.
Every help would be great. TIA!
I don't know why this has worked before (it should not have (properly)), but the error you are currently getting is sort of expected. The filterValue attribute should be bound to a property with read and write access. You could bind each column filter value to an individual property, but it is more convenient to use a Map<String,Object> for your filter values.
Bean (lazy model in your case):
private Map<String,Object> filterValues = new HashMap<>();
// ... add getter and setter for filterValues
XHTML:
filterValue="#{bean.lazyModel.filterValues['name']}"

How to make the asp-for input tag helper generate camelCase names?

If I have a view model like this:
public class MyModel{
public DateTime? StartDate {get;set;}
}
And on a view an input tag is used with an asp-for tag helper like so:
<input asp-for="StartDate" />
The default html that is generated by this is
<input type="datetime" id="StartDate" name="StartDate" value="" />
But what I want it to generate is html that looks like this:
<input type="datetime" id="startDate" name="startDate" value="" />
How can I make the asp-for input tag helper generate camel case names like above without having to make my model properties camelCase?
After studying the code that #Bebben posted and the link provided with it, I continued to dig more into the Asp.Net Core source code. And I found that the designers of the Asp.Net Core provided some extensibility points that could be leveraged to achieve lower camelCase id and name values.
To do it, we need to implement our own IHtmlGenerator which we can do by creating a custom class that inherits from DefaultHtmlGenerator. Then on that class we need to override the GenerateTextBox method to fix the casing. Or alternatively we can override the GenerateInput method to fix the casing of name and id attribute values for all input fields (not just input text fields) which is what I chose to do. As a bonus I also override the GenerateLabel method so the label's for attribute also specifies a value using the custom casing.
Here's the class:
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Text.Encodings.Web;
namespace App.Web {
public class CustomHtmlGenerator : DefaultHtmlGenerator {
public CustomHtmlGenerator(
IAntiforgery antiforgery,
IOptions<MvcViewOptions> optionsAccessor,
IModelMetadataProvider metadataProvider,
IUrlHelperFactory urlHelperFactory,
HtmlEncoder htmlEncoder,
ClientValidatorCache clientValidatorCache) : base
(antiforgery, optionsAccessor, metadataProvider, urlHelperFactory,
htmlEncoder, clientValidatorCache) {
//Nothing to do
}
public CustomHtmlGenerator(
IAntiforgery antiforgery,
IOptions<MvcViewOptions> optionsAccessor,
IModelMetadataProvider metadataProvider,
IUrlHelperFactory urlHelperFactory,
HtmlEncoder htmlEncoder,
ClientValidatorCache clientValidatorCache,
ValidationHtmlAttributeProvider validationAttributeProvider) : base
(antiforgery, optionsAccessor, metadataProvider, urlHelperFactory, htmlEncoder,
clientValidatorCache, validationAttributeProvider) {
//Nothing to do
}
protected override TagBuilder GenerateInput(
ViewContext viewContext,
InputType inputType,
ModelExplorer modelExplorer,
string expression,
object value,
bool useViewData,
bool isChecked,
bool setId,
bool isExplicitValue,
string format,
IDictionary<string, object> htmlAttributes) {
expression = GetLowerCamelCase(expression);
return base.GenerateInput(viewContext, inputType, modelExplorer, expression, value, useViewData,
isChecked, setId, isExplicitValue, format, htmlAttributes);
}
public override TagBuilder GenerateLabel(
ViewContext viewContext,
ModelExplorer modelExplorer,
string expression,
string labelText,
object htmlAttributes) {
expression = GetLowerCamelCase(expression);
return base.GenerateLabel(viewContext, modelExplorer, expression, labelText, htmlAttributes);
}
private string GetLowerCamelCase(string text) {
if (!string.IsNullOrEmpty(text)) {
if (char.IsUpper(text[0])) {
return char.ToLower(text[0]) + text.Substring(1);
}
}
return text;
}
}
}
Now that we have our CustomHtmlGenerator class we need to register it in the IoC container in place of the DefaultHtmlGenerator. We can do that in the ConfigureServices method of the Startup.cs via the following two lines:
//Replace DefaultHtmlGenerator with CustomHtmlGenerator
services.Remove<IHtmlGenerator, DefaultHtmlGenerator>();
services.AddTransient<IHtmlGenerator, CustomHtmlGenerator>();
Pretty cool. And not only have we solved the id and name casing issue on the input fields but by implementing our own custom IHtmlGenerator, and getting it registered, we have opened the door on all kinds of html customization that can be done.
I'm starting to really appreciate the power of a system built around an IoC, and default classes with virtual methods. The level of customization available with little effort under such an approach is really pretty amazing.
Update
#Gup3rSuR4c pointed out that my services.Remove call must be an extension method that's not included in the framework. I checked, and yep that true. So, here is the code for that extension method:
public static class IServiceCollectionExtensions {
public static void Remove<TServiceType, TImplementationType>(this IServiceCollection services) {
var serviceDescriptor = services.First(s => s.ServiceType == typeof(TServiceType) &&
s.ImplementationType == typeof(TImplementationType));
services.Remove(serviceDescriptor);
}
}
The simplest way to do this is to just write
<input asp-for="StartDate" name="startDate" />
Or do you want to have it generated completely automatically in camel case, for the whole application?
To do that, it seems like you have to implement your own InputTagHelpers in Microsoft.AspNetCore.Mvc.TagHelpers.
Here is the method where the name is generated:
private TagBuilder GenerateTextBox(ModelExplorer modelExplorer, string inputTypeHint, string inputType)
{
var format = Format;
if (string.IsNullOrEmpty(format))
{
format = GetFormat(modelExplorer, inputTypeHint, inputType);
}
var htmlAttributes = new Dictionary<string, object>
{
{ "type", inputType }
};
if (string.Equals(inputType, "file") && string.Equals(inputTypeHint, TemplateRenderer.IEnumerableOfIFormFileName))
{
htmlAttributes["multiple"] = "multiple";
}
return Generator.GenerateTextBox(
ViewContext,
modelExplorer,
For.Name,
value: modelExplorer.Model,
format: format,
htmlAttributes: htmlAttributes);
}
(The above code is from https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs, Apache License, Version 2.0, Copyright .NET Foundation)
The line is "For.Name". The name is sent into some other methods, and the one that in the end gives the final name is in a static class (Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.NameAndIdProvider), so nothing we can really plug into easily.

PrimeFaces 5.2 selectCheckboxMenu not support Map<String,String> style selectItems

I am trying to use
Map<String,String>
style selectItems in my
<p:selectCheckboxMenu>
but unfortunately, it not works.
Backing bean:
#Named(value = "roleBean")
#SessionScoped
public class RoleBean(){
private Map<String,String> roleMap;
private String[] selectRoles;
......
......
public void findRoleMap(){
if(roles.size()>0){
for(Role r:roles){
roleMap.put(r.getRoleId(),r.getRoleName());
}
logger.info("Role Map size: " + roleMap.size());
}
}
}
jsf as:
<p:selectCheckboxMenu id="roles" value="#{roleBean.selectRoles}" label="Select your roles" filter="true" filterMatchMode="startsWith" height="200" panelStyle="width:200px">
<f:selectItems value="#{roleBean.roleMap.entrySet}" var="entry" itemLabel="#{entry.value}" itemValue="#{entry.key}"/>
</p:selectCheckboxMenu>
When I access the page, I found there is 6 records were added into the map. but unfortunately, there is nothing shows up in the selectCheckboxMenu drop down list.
If I change the UIComponent to
<p:selectOneMenu>
from
<p:selectCheckboxMenu>
it shows up properly in dropdown list.
I tried to find out through google, I didn't find anyone else trying to use key/value pair in Map style as selectItems in selectCheckboxMenu. I was very confused.
My question is: Does selectCheckboxMenu support key/value pair mapping style or not, if it does. How To do it?
Please advise!!

Selecting a SelectOneMenu populated with enum only after second click

I'm trying to use enums to populate a PrimeFaces selectOneMenu, but could not get the selected value. After the first click, the value assigned is always the one in the post-constructor.
HTML:
<p:selectOneMenu id="periodo"
value="#{dashboardMB.enumDate}">
<f:selectItems value="#{dashboardMB.enumDates}"
var="enumDate"
itemValue="#{enumDate}"
itemLabel="#{enumDate.label}" />
</p:selectOneMenu>
Backing bean:
private EnumDate enumDate;
#PostConstruct
public void init() {
enumDate = EnumDate.YEAR;
}
EnumDate:
public enum EnumData {
EMPTY("- Select -"), DAY("Day"), WEEK("Week"), FORTNIGHT("Fortnight"), MONTH("Month"), BIMESTER("Bimester"), TRIMESTER("Trimester"), SEMESTER("Semester"), YEAR("Year");
public String label;
public static final EnumSet<EnumDate> all = EnumSet.of(EMPTY, DAY, WEEK, FORTNIGHT, MONTH, BIMESTER, TRIMESTER, SEMESTER, YEAR);
private EnumDate(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
}
Thus, it has always the value "Year". Until the button which sends the form is clicked again.
Solved using OmniFaces converter:
<p:selectOneMenu id="period"
converter="omnifaces.SelectItemsConverter"
style="width: 237px !important"
value="#{dashboardMB.enumDate}"
filter="true"
filterMatchMode="contains"
panelStyleClass="oneMenuPanel"
styleClass="oneMenu">
<f:selectItems value="#{dashboardMB.enumDateArray}"
var="enum"
itemValue="#{enum}"
itemLabel="#{enum.label}" />
</p:selectOneMenu>
The enumDateArray attribute is of EnumDate[] type. Its getter returns EnumDate.values().
Better to use the omnifaces generic enum converter. It keeps your code cleaner compared to 'manually' doing conversions of lists etc...
Convert enums into Strings
EnumData[] eds = EnumData.values();
String EDtoString = eds[0].name();
You can pass function String[] EnumDatasToString() to XHTML.
To convert from String to Enum use
EnumData ed = EnumData.valueOf(EDToString);

Combobox created dynamically from database (JSF)

I wanted to create in JSF a combobox (selectOneMenu). I want to fill this combobox with all logins from one column from Database (SELECT logins FROM database).
Will be much gratefull from any help.
In your backing bean (YourBean in the example) you should have an array of Strings (or of objects with a getter method that returns the String you want). For instance lets assume you have the following code in your backing bean:
private ArrayList<String> logins; // read from DB
private String selectedLogin; // this will hold the selected value
// this method will be called by the JSF framework to get the list
public ArrayList<String> getLogins()
{
return logins;
}
public String getSelectedLogin()
{
return selectedLogin;
}
public String setSelectedLogin(String sl)
{
selectedLogin = sl;
}
In you Facelets page, assuming you are on JSF 2.x:
<h:selectOneMenu value="#{YourBean.selectedLogin}">
<f:selectItems value="#{YourBean.logins}" itemLabel="#{l}" itemValue="#{l}" var="l"/>
</h:selectOneMenu>
This will create a select menu with all the options in the array. Once the form is submitted the value will be set in the String value of your backing bean.