Setting datatable sortBy attribute dynamically from managed bean - jsf

We wanted to have are datatables bookmarkable, so the state of the datatable is carried in URL (offset, limit, sortOrder, sortBy, etc). The problem is I need to get the actual sortBy value for the datatable from the managed bean and set it dynamically (not statically as in the implementation).
We extended the DataTable code so the dynamic value of sortBy can evaluate. The code below does the trick, when referencing the property using the bean name:
CustomDataTable.java
#Override
protected String resolveSortField() {
UIColumn column = this.getSortColumn();
String sortField = null;
ValueExpression sortVE = this.getValueExpression("sortBy");
if(column == null) {
sortField = resolveDynamicField(sortVE);
}
else {
if(column.isDynamic()) {
((DynamicColumn) column).applyStatelessModel();
sortField = resolveDynamicField(sortVE);
} else {
sortField = resolveStaticField(sortVE);
}
}
return sortField;
}
#Override
public String resolveDynamicField(ValueExpression expression) {
if(expression != null) {
String expressionString = expression.getExpressionString();
if (expressionString.startsWith("#{")) {
FacesContext context = getFacesContext();
ELContext eLContext = context.getELContext();
ValueExpression dynaVE = context.getApplication()
.getExpressionFactory().createValueExpression(eLContext, expressionString , String.class);
String result = (String) dynaVE.getValue(eLContext);
if (StringUtils.isNotEmpty(result)) {
return result;
} else {
return resolveStaticField(expression);
}
} else {
return expressionString.substring(expressionString.indexOf(".") + 1);
}
} else {
return null;
}
}
In template:
<p:dataTable value="#{concreteBean.dataModel}"
var="obj"
selection="#{concreteBean.selected}"
selectionMode="single"
sortBy="#{concreteBean.sortBy}"
sortOrder="#{concreteBean.sortOrder}"
first="#{concreteBean.first}"
rows="#{concreteBean.rows}"
rowKey="#{obj}">
...
However we needed to refactor the template because of duplications and now we include the datatable and giving it the managed bean through ui:param:
...
<ui:param name="bean" value="#{concreteBean}" />
<ui:include src="datatable.xhtml" />
...
...
<p:dataTable value="#{bean.dataModel}"
var="obj"
selection="#{bean.selected}"
selectionMode="single"
sortBy="#{bean.sortBy}"
sortOrder="#{bean.sortOrder}"
first="#{bean.first}"
rows="#{bean.rows}"
rowKey="#{obj}">
...
However the dynamic value in this case evaluates to empty string and not the property value of the referencing bean (getter for sortBy is not called at all).
Can anyone help to do some magic, so the value of sortBy attribute can be dynamically set from the property of the managed bean?

Turns out I can simply evaluate the sortBy attribute by calling DataTable.getSortBy(), which uses StateHelper the same way as the regular attributes do when evaluating the value.

Related

Expand EL-Context for some value

Example in xhtml works, because #{row} is defined in p:dataTable. If I call getData not in context of p:dataTable it returns null. See Values.iterateOverDatatableValues for this situation where method returns null. How can I define row to use in my context. Possibly has datatable some functions to iterate over values so that #{row} will be defined?
Java:
#Named
#SessionScoped
class Test {
public Object getData () {
return Faces.evaluateExpressionGet("#{row.someProperty}"); //The String "#{row.someProperty}" comes from a collection.
}
}
XHTML #{row} is defined in Test.getData():
<p:dataTable value="#{bean.values}" var="row">
<p:column>
<h:outputText value="#{test.data}" />
</p:column>
</p:dataTable>
Java, #{row} is undefined:
class Values {
#Inject
Test test;
public void iterateOverDatatableValues (){
DataTable dt = Components.findComponent("datatableId");
for (Object o : dt.getValues()){
test.getData(); // <---- NULL because #{row} is not defined.
}
}
}
The solution was to use expressionFactory().createValueExperssion method.
private void setRowEL(Object o) {
ELContext elContext = Faces.getELContext();
ExpressionFactory expressionFactory = Faces.getApplication().getExpressionFactory();
ValueExpression aliasValueExpression = expressionFactory.createValueExpression(elContext, "#{row}", MyValue.class);
aliasValueExpression.setValue(elContext, o);
}

How to get a UIComponent by its component id in icefaces

I'm trying to access an icefaces component, exactly an Accordion so i can set its activeIndex from my bean. the problem is that the returned value is always null. this is my code.
public static UIComponent findComponentInRoot(String id) {
UIComponent component = null;
FacesContext facesContext = FacesContext.getCurrentInstance();
if (facesContext != null) {
UIComponent root = facesContext.getViewRoot();
component = findComponent(root, id);
}
return component;
}
public static UIComponent findComponent(UIComponent base, String id) {
if (id.equals(base.getId()))
return base;
UIComponent kid = null;
UIComponent result = null;
Iterator kids = base.getFacetsAndChildren();
while (kids.hasNext() && (result == null)) {
kid = (UIComponent) kids.next();
if (id.equals(kid.getId())) {
result = kid;
break;
}
result = findComponent(kid, id);
if (result != null) {
break;
}
}
return result;
}
and i call this method like this:
Accordion acco = (Accordion)findComponentInRoot("menuFormId:menu");
my page look like this or to say a part of it:
<h:form id="menuFormId">
<icecore:singleSubmit />
<ace:accordion id="menu" collapsible="true" autoHeight="false" >
<ace:accordionPane id="system" title="#{msgs.LABEL_ADMINISTRATION}"
rendered="#{navigationCtrl.functionList['GESUTAD'] or navigationCtrl.functionList['GESPROF'] or navigationCtrl.functionList['GESUTTOM'] or navigationCtrl.functionList['SYNCPRC']}">
<div class="divLinkStyle">
<ice:commandLink rendered="#{navigationCtrl.functionList['GESPROF']}" styleClass="linkMenu" action="#{navigationCtrl.redirectConsulterProfil}"
onmouseover="this.style.backgroundColor='#DEEDF8'" onmouseout="this.style.backgroundColor='#FFFFFF'">
<h:graphicImage value="../resources/images/util.png" />
<h:outputLabel value="#{msgs.LABEL_GESTION_PROFIL}" style="cursor: pointer;" />
</ice:commandLink>
</div>
...
Any ideas ?
my bean is session scoped.
i'm using icefaces 3.3.0 and jsf 2.2
You're confusing component ID with client ID. You're passing a client ID "menuFormId:menu" instead of component ID "menu" to your utility method, while the utility method actually finds the component by component ID instead of client ID.
Just use UIViewRoot#findComponent().
public static UIComponent findComponentInRoot(String id) {
return FacesContext.getCurrentInstance().getViewRoot().findComponent(id);
}
Unrelated to the concrete problem. You're making here a design mistake. The model should not be interested in the view. It should be the other way round. Set the activeIndex as a bean property and let the view hook on it the usual way.
<ace:accordion ... activeIndex="#{bean.activeIndex}">
In any case you're trying to grab/create/bind/manipulate/whatever a physical UIComponent instance in a backing bean class, you should absolutely stop coding and think twice if you're really doing things the right way. Ask if necessary at Stack Overflow if you can't figure out the right way.

RichFaces extendedDataTable disable sorting

I faced problem with disabling RichFaces sorting by column on button click.
Maybe someone can help
I have AlertsList datatable:
<rich:extendedDataTable id="#{module}AlertsList"
tableState="#{alertsController.dataModel.tableState}"
enableContextMenu="false"
height="220px"
sortMode="single"
value="#{alertsController.dataModel}"
var="alert" width="100%"
selection="#{alertsController.selection}"
reRender="#{module}AlertsListDatascroller"
rows="#{alertsController.dataModel.rowsPerPage}"
binding="#{alertsController.dataModel.extandetDataTable}"
rowClasses="evenRow,oddRow">
......
<rich:column sortBy="#{alert.lockedByUsername}" width="7%"
style="#{(not empty alert.first4OfLockedByUsername and (alertsController.dataModel.selectedItem != alert)) ? 'background-color: darkgray' : ((alert.action != null and alert.action.classificationTypeEntity.classificationType eq 'POSTPONED') ? 'background-color: thistle' : '')}"
label="#{message['alertsnalysis.alertList.table.lock']}"
selfSorted="#{currentUser.authorities['SVWI_MODIFICATION']}"
id="#{module}AL_lockedByUsername">
<f:facet name="header">#{message['alertsnalysis.alertList.table.lock']}</f:facet>
<h:outputText value="#{alert.first4OfLockedByUsername}" />
</rich:column>
......
<rich:column sortBy="#{alert.id}" width="5%"
style="#{(not empty alert.first4OfLockedByUsername and (alertsController.dataModel.selectedItem != alert)) ? 'background-color: darkgray' : ((alert.action != null and alert.action.classificationTypeEntity.classificationType eq 'POSTPONED') ? 'background-color: thistle' : '')}"
label="#{message['alertsnalysis.alertList.table.id']}"
selfSorted="#{currentUser.authorities['SVWI_MODIFICATION']}"
id="#{module}AL_id">
<f:facet name="header">#{message['alertsnalysis.alertList.table.id']}</f:facet>
<h:outputText value="#{alert.id}" />
</rich:column>
....
<rich:datascroller id="#{module}AlertsListDatascroller" for="#{module}AlertsList" ajaxSingle="false" page="#{alertsController.dataModel.currentPage}"></rich:datascroller>
So I added button to change soring table results by 3 columns at a time, cause due my current realisation i cant sorting data by clicking on different column headers(way with sortMode="multiply" is not accepted):
<h:form id="buttonReset">
<h:panelGrid columns="2">
<a4j:commandButton id="resetSortingButton" styleClass="FatButtonStyle" reRender="#{module}AlertsList"
action="#{alertsController.dataModel.defaultSortField}" value="Default Sorting"/>
</h:panelGrid>
</h:form>
Also I have implementaion of Modifiable:
public abstract class TableDataModel<T, U> extends SerializableDataModel implements Modifiable, Serializable {
...
protected String sortField = null;
..
public void defaultSortField(){ // call on button "Default Sorting" click action
this.sortField = "default"; //set field to default
this.detached = false;
this.defaultFlag = true;
this.i = 0;
}
...
#Override
public void walk(final FacesContext context, final DataVisitor visitor, final Range range, final Object argument)
throws IOException {
final int firstRow = ((SequenceRange) range).getFirstRow();
final int numberOfRows = ((SequenceRange) range).getRows();
if (detached) {
for (final U key : wrappedKeys) {
setRowKey(key);
visitor.process(context, key, argument);
}
} else {
List<T> list = Collections.<T>emptyList();
if (rangeChanged((SequenceRange) range) || sortChanged(sortField) || filterMapChanged(filterMap) || descChanged(descending) || ((MethodReRendering) this).getReRenderingEnabled()) {
lastRange = (SequenceRange) range;
lastSortField = sortField;
lastFilterMap = new HashMap<String, Object>(filterMap);
lastDescending = descending;
((MethodReRendering) this).setReRenderingEnabled(false);
list = findObjects(firstRow, numberOfRows, sortField, filterMap, descending); //in findObjects() checking if sortField == "default", then sorting by 3 columns, else by column in field value
wrappedKeys = new CopyOnWriteArrayList<U>();
for (final T object : list) {
wrappedKeys.add(getId(object));
wrappedData.put(getId(object), object);
visitor.process(context, getId(object), argument);
}
} else {
for (U id :wrappedKeys)
visitor.process(context, id, argument);
}
}
}
...
public void modify(List<FilterField> filterFields, List<SortField2> sortFields) {
filterMap.clear();
SortField2 sortField2 = null;
String expressionStr = null;
ExtendedFilterField extendedFilterField = null;
String value = null;
Expression expression = null;
if (sortFields != null && !sortFields.isEmpty()) {
sortField2 = sortFields.get(0);
expression = sortField2.getExpression();
......
}
...
}
So problem is when I click my btn I change sortField value and walk() method returns new sorted by 3 columns data to my table ( see findObjects(firstRow, numberOfRows, sortField, filterMap, descending);).
All works fine.
But on my page I still can see (by icon or if i refresh my page it calls modify() method with List sortFields parameter == "alertId") that means what data still sorted by alertId column, but new sorting implemented. What logic I need add to my defaultSortField() method (calls on "Default Sorting" button click), for disable sorting by alertId column?
UPDATE1:
Added binding my extendedDataTable to property:
But maybe someone know how can I disable current sorting?
UIExtendedDataTable extandetDataTable = null;
public void setExtandetDataTable(UIExtendedDataTable extandetDataTable) {
this.extandetDataTable = extandetDataTable;
List<SortField2> sortField2s = extandetDataTable.getSortFields();
SortField2 sortField2;
}
public UIExtendedDataTable getExtandetDataTable() {
return extandetDataTable;
}
Why can't you use the sortMode=multi?
The table also has sortPriority - a list of ids by which to sort, I assume your not adding the ids of the columns to the sortPriority so it gets sorted by the id that is there. The handling is done in org.richfaces.renderkit.SortingFilteringRowsRenderer.decodeSorting().
EDIT: If you want to disable sorting you can set sortType="custom" on rich:column, but I don't know if that won't interfere with your implementation.

Moving away from parameterized getters

I'm planning to use an application scoped bean (as a common bean) to return a value of rowsPerPageTemplate attribute of <p:dataTable> like 5,10,15,20 - a comma-separated list of values indicating the page size of <p:dataTable>.
<p:dataTable var="row" value="#{viewScopedBean}"
lazy="true"
pageLinks="10"
rows="10"
rowsPerPageTemplate="#{pageTemplate.getCommonTemplate(viewScopedBean.rowCount)}">
...
</p:dataTable>
The pageTemplate bean :
#ManagedBean
#ApplicationScoped
public final class PageTemplate {
private static final int pageSize = 10;
public PageTemplate() {}
private static String getTemplate(int rowCount) {
if (pageSize >= rowCount) {
return String.valueOf(pageSize);
}
int sum = 0;
int pages = (int) Math.ceil(rowCount / (double) pageSize);
pages = pages >= 100 ? 100 : pages;
String templateString = "";
for (int i = 0; i < pages; i++) {
sum += pageSize;
templateString += sum + ",";
}
return new String(templateString.substring(0, templateString.length() - 1));
}
public String getCommonTemplate(int rowCount) {
return getTemplate(rowCount);
}
}
The bean and XHTML are self-explanatory. A parameterized method getCommonTemplate() is being referenced to by an EL expression #{} and consequently being invoked more than once.
Although the logic contained by the method is very cheap, it should not be the best practice to wrap this kind of logic around a getter method in a bean.
Can the code inside the getCommonTemplate() method be moved in a place where it is guaranteed to be executed only once (this should not the currently used bean for <p:dataTable> - ViewScopedBean, since the code contained by the getCommonTemplate() method should be shared across all beans that use <p:dataTable>)?
If the #{viewScopedBean.rowCount} is available during view build time (i.e. set in #PostConstruct), then you can use <c:set> to evaluate an EL expression once and store it in request, view, session or application scope (when no scope is specified, it defaults to none scoped and acts as "alias").
<c:set var="rowsPerPageTemplate" value="#{pageTemplate.getCommonTemplate(viewScopedBean.rowCount)}" scope="view" />
<p:dataTable ...
rowsPerPageTemplate="#{rowsPerPageTemplate}">
...
</p:dataTable>
However, if it's not available during view build time (which is apparently true in your case with LazyDataModel), then your best bet is to cache the results in #{pageTemplate}.
private static final Map<Integer, String> TEMPLATES_BY_ROW_COUNT = new HashMap<>();
public String getCommonTemplate(int rowCount) {
String template = TEMPLATES_BY_ROW_COUNT.get(rowCount);
if (template == null) {
template = getTemplate(rowCount);
TEMPLATES_BY_ROW_COUNT.put(rowCount, template);
}
return template;
}

Programmatically get expression value of facelets parameter (variable)

Following java code allows to access any object or variable from faces context:
ELContext elCtx = facesContext.getELContext();
ExpressionFactory exprFac = facesContext.getApplication().getExpressionFactory();
MyProperty myProperty = (MyProperty) exprFac.createValueExpression(elCtx, "#{somebean.someattr.someproperty}", MyProperty.class).getValue(elCtx);
I use the code from within my custom converter to read additional converting parameters from context.
The code works correctly if #{somebean} is defined as normal backing bean within JSF context.
Facelets allow to create 'shortcut' to JSF expressions. Example:
<ui:param name="shortcut" value="#{somebean.someattr.someproperty}" />
<div>#{somebean.someattr.someproperty} equals #{shortcut}</div>
In this case both #{somebean.someattr.someproperty} and #{shortcut} have the same value.
However these 'shortcut' names are not accessible using java code above. For example:
MyProperty myProperty1 = (MyProperty) exprFac.createValueExpression(elCtx, "#{somebean.someattr.someproperty}", MyProperty.class).getValue(elCtx);
// myProperty1 has expected value
MyProperty myProperty2 = (MyProperty) exprFac.createValueExpression(elCtx, "#{shortcut}", MyProperty.class).getValue(elCtx);
// myProperty2 is null
Is there a way to access a facelets context and to read 'shortcut' parameter values, defined on the current JSF page?
I had the same problem and have chosen the following approach:
/**
* Führt eine Expression im aktuellen Faces EL Context
* UND im Facelets El Context aus.
*
* #param facesContext
* #param expression
* #return object
*/
private static Object executeExpressionInUIContext (final FacesContext facesContext, final String expression) {
final ELContext elContext = facesContext.getELContext();
final Application application = facesContext.getApplication();
Object result = executeExpressionInElContext(application, elContext, expression);
if (null == result) {
FaceletContext faceletElContext = (FaceletContext) FacesContext.getCurrentInstance().getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY);
result = executeExpressionInElContext(application, faceletElContext, expression);
}
return result;
}
private static Object executeExpressionInElContext (Application application, ELContext elContext, String expression) {
ExpressionFactory expressionFactory = application.getExpressionFactory();
ValueExpression exp = expressionFactory.createValueExpression(elContext, expression, Object.class);
return exp.getValue(elContext);
}
"ui:param" is part of the Facelet view handling technology. Facelets extends JSF.
Both technologies use their own Context when storing variables.
Beside the Faces El Context there is a Facelet El Context (FaceletContext).
The stated method evaluates expressions in both contexts. Be aware that this will not work if two values are stored under the same name in each context.
It seems that facelet shortcuts do not exist in the context, where I try to access them.
I have made following workaround: On JSF page where my input element is placed, I have added a <f:param> element as child of the input with my converter.
<h:inputText id="myid" value="#{action.myinput}">
<f:converter converterId="myConverter" />
<f:param name="converterParameters" shortcut="#{somebean.someattr.someproperty}"/>
</h:inputText>
Then in converter I'm able to find UIParam element as one of the input children and read my shortcuts from it.
public Object getAsObject(FacesContext context, UIComponent component, String value) {
MyProperty myProperty = null;
try {
for (final UIComponent child : component.getChildren()) {
if ("converterParameters".equals(child.getAttributes().get("name"))) {
final ELContext elCtx = context.getELContext();
myProperty = (MyProperty) child.getValueExpression("shortcut").getValue(elCtx);
break;
}
}
if (myProperty == null) {
throw new NullPointerException("My property is undefined.");
}
} catch (Exception e) {
LOG.error("Cannot convert " + value + ". Use <f:param name=\"converterParameters\" "
+ "shortcut=\"#{here.comes.shortcut}\"/> for your input element. ", e);
throw new ConverterException("Cannot initialize converter.", e);
}
//...
}
The mapping of ui:param is not stored in context, it's in the VariableMapper of each individual ValueExpression.
So if you need to create ValueExpression programmatically, relying on another ValueExpression's varMapper, you can do something like this:
VariableMapper varMapper = new DefaultVariableMapper();
varMapper.setVariable(mappingName, component.getValueExpression(mappedAttributeName));
return new ValueExpressionImpl(expression, null, null, varMapper, expectedType);

Resources