RichFaces extendedDataTable disable sorting - jsf

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.

Related

Primefaces update fails after removing component in a model

My goal is to create a panel where I can drag and drop elements of two different types: button and textfield. All of the elements are stored in a TreeMap and are iterated over with JSTL <c:forEach> tag. I can add extra components and remove the most recently selected ones thorough dedicated p:commandXxx fields outside of the viewPanel. Once draggable components are in the viewPanel, I can drag and drop them. Through javascript, I'm appending top and left coordinates of a particular component before the drop action to update component's location is invoked. The delete commandButton should remove the selected element from the list and trigger the update of the viewPanel.
Problem: Deleting any component except the most recently added fails to update the viewPanel resulting in the following exception:
Severe: Error Rendering View[/developer/testDr.xhtml]
org.primefaces.expression.ComponentNotFoundException: Cannot find >component for expression "button-" referenced from "defStep-10:j_idt13"..
It appears that in render time draggable's for attribute evaluates #{child.idstepNode} to null. Does anyone know why this happens, or how to circumvent this?
Interestingly, when Delete button is clicked again or the whole page is refreshed, viewPanel renders without a hitch.
<h:form id="viewPanel" class="default-step droppable" style="width:500px;height:500px;background:green;">
<p:droppable for="viewPanel" tolerance="touch" activeStyleClass="ui-state-highlight" >
<p:ajax listener="#{stepUtility.onDrop}" update="viewPanel" />
</p:droppable>
<c:forEach items="${stepUtility.stepNodesMap}" var="child">
<c:if test="${child.value.elementType == 'BUTTON'}">
<p:outputLabel id="button-${child.value.idstepNode}" style="top:${child.value.top};left:${child.value.left};
background-color: beige;position:absolute;" value="button ${child.value.elementValue}"/>
<p:draggable for="button-${child.value.idstepNode}"/>
</c:if>
<c:if test="${child.value.elementType == 'TEXT'}">
<p:outputLabel id="text-${child.value.idstepNode}" style="top:${child.value.top};left:${child.value.left};
background-color: beige;position:absolute;" value="text ${child.value.elementValue}"/>
<p:draggable for="text-${child.value.idstepNode}" />
</c:if>
</c:forEach>
</h:form>
//...
<p:commandButton value="Delete" actionListener="#{stepUtility.removeStepNode()}"
update=":viewPanel"/>
stepUtility bean
#Named(value = "stepUtility")
#SessionScoped
public class StepUtility implements Serializable {
private Integer selecteNode;
public void setSelectedElement(Integer idstepNode) //'StepNode')
{
selecteNode=idstepNode;
}
public void addNewComp(String typeNode)
{
if(stepNodesMap == null)
stepNodesMap = new TreeMap<>();
StepNodeSimple snode = new StepNodeSimple();
Integer id = 0;
if(stepNodesMap.size()>0)
{
Integer lastNodeId = ((StepNodeSimple) stepNodesMap.lastEntry().getValue()).idstepNode;
id = lastNodeId+1;
}
snode.idstepNode = id;
snode.elementType = typeNode;
snode.elementValue = typeNode +"value";
snode.left = "2px;";
snode.top = "2px;";
stepNodesMap.put(id, snode);
}
NavigableMap stepNodesMap;
public NavigableMap getStepNodesMap() {
return stepNodesMap;
}
public void setStepNodesMap(NavigableMap stepNodesMap) {
this.stepNodesMap = stepNodesMap;
}
public void removeStepNode()
{
if(selecteNode!=null)
{
stepNodesMap.remove(selecteNode);
}
}
public void onDrop(DragDropEvent dragDropEvent)
{
String dragId = dragDropEvent.getDragId();
Map<String, String> params = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
String left = params.get(dragId + "_left");
String top = params.get(dragId + "_top");
Integer idstepNode = Integer.parseInt(dragId.substring(dragId.lastIndexOf("-")+1));
setSelectedElement(idstepNode);
StepNodeSimple element = (StepNodeSimple) stepNodesMap.get(selecteNode);
element.left = left+"px;";
element.top = top+"px;";
}
}

Refreshing PrimeFaces form element dataTable (code details provided)

I have Search form. After clicking on Search button data refreshes, but dataTable columns not (columns get refreshed only on second button click). Apparently I am doing something wrong. Dynamic column code is inspired by PrimeFaces Showcase
myForm.xhtml:
<h:form id="MY_FORM">
#{myBean.initBean()}
...
<h:panelGrid id="main">
<h:panelGrid id="buttons">
<p:commandButton id="submitSearch"
value="#{msg['button.execute']}"
actionListener="#{myBean.submitSearch}"
update="resultPanel"/>
</h:panelGrid>
</h:panelGrid>
<h:panelGrid id="resultPanel" border="0">
<p:dataTable id="resultTable" var="result" value="#{myBean.searchResults}">
<p:columns value="#{myBean.columns}" var="column" columnIndexVar="colIndex">
<f:facet name="header">
<h:outputText value="#{column.header}" />
</f:facet>
<h:outputText value="#{result[column.property]}" />
</p:columns>
</p:dataTable>
</h:panelGrid>
</h:form>
I've called column calculation method createColumns() from myBean.initBean() and myBean.submitSearch() with the same result (I see it works correctly from debugger).
#ManagedBean("myBean")
#Scope(value = "session")
public class MyBean {
private ArrayList<HashMap<String,Object>> searchResults;
private List<ColumnModel> columns;
...
public void initBean() {
...
createColumns(selectedDates);
}
public void submitSearch() {
...
ArrayList<HashMap<String, Object>> results = prpRepository.getSearchResultByParams(searchParams);
createColumns(selectedDates);
}
private void createColumns(ArrayList<String> selectedDates){
columns = new ArrayList<ColumnModel>();
columns.add(new ColumnModel("Name", "NAME"));
columns.add(new ColumnModel("Amount", "AMOUNT"));
for (int i = 0; i < selectedDates.size(); i++) {
columns.add(new ColumnModel(selectedDates.get(i), "DATE" + i));
}
setColumns(columns);
}
public List<ColumnModel> getColumns() {
return columns;
}
public void setColumns(List<ColumnModel> columns) {
this.columns = columns;
}
}
Additional information:
I am using:
Oracle's Implementation of the JSF 2.1 Specification
JavaServer Pages API 2.2
Springframework 3.1.2 (incl. spring-context, spring-web)
Glassfish Javax.annotation 3.1.1
The problem is that columns is calculted only once time in the org.primefaces.component.datatable.DataTabe
public List<UIColumn> getColumns() {
// if(columns == null) {
columns = new ArrayList<UIColumn>();
FacesContext context = getFacesContext();
char separator = UINamingContainer.getSeparatorChar(context);
for(UIComponent child : this.getChildren()) {
if(child instanceof Column) {
columns.add((UIColumn) child);
}
else if(child instanceof Columns) {
Columns uiColumns = (Columns) child;
String uiColumnsClientId = uiColumns.getClientId(context);
uiColumns.updateModel(); /* missed code */
for(int i=0; i < uiColumns.getRowCount(); i++) {
DynamicColumn dynaColumn = new DynamicColumn(i, uiColumns);
dynaColumn.setColumnKey(uiColumnsClientId + separator + i);
columns.add(dynaColumn);
}
}
}
// }
return columns;
}
You could comment it and overwrite the class file,
or find the DataTable object and setColumns to NULL inside the createColumns method
Finally I've worked better with debugger and found an answer. Solution was to put createColumns() method call into columns getter getColumns() as it is called automatically before submitSearch(). My problem was in application lifecycle understanding.

Setting datatable sortBy attribute dynamically from managed bean

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.

How to get DataTable selection (CheckBox) in primefaces 2.2 in validator

In Primefaces 2.2 , if you want to get DataTable selection, you need to commit the datatable without validation failed or converter failed.
Like below:
<p:dataTable update="outputPanel" id="dataTable" var="car"
value="#{tableBean.cars}" selection="#{tableBean.selectedCars}">
<p:column selectionMode="multiple" />
<p:column style="width:200px" id="Model">
<f:facet name="header">
Model
</f:facet>
<h:outputText value="#{car.model}" />
<h:inputText value="#{car.model}" >
<f:attribute name="datatableClientId" value="form:dataTable" />
<f:attribute name="datatable" value="#{dataTableBinding}" />
<f:validator validatorId="dataTableRequiredValidator" />
</h:inputText>
</p:column>
</p:dataTable>
When the validator is falied, how to get the selectedCars in the validator?
I look into the Primefaces 2.2 source code , when decode a datatable will use the DataHelper class to decodeSelection. So I copy the code to write a util class ,like below:
//when the validator in a row, the datatable clientId will be wrong append the row number. so please specify the table clientId.
public Object getDataTableSelection(FacesContext context, DataTable table, String dataTableclientId) {
String clientId = dataTableclientId != null ? dataTableclientId : table.getClientId(context);
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
String selection = params.get(clientId + "_selection");
Object data = null;
if (table.isSingleSelectionMode()) {
data = decodeSingleSelection(table, selection);
} else {
data = decodeMultipleSelection(table, selection);
}
table.setRowIndex(-1); // clean
return data;
}
private Object decodeSingleSelection(DataTable table, String selection) {
Object data = null;
if (isValueBlank(selection)) {
table.setSelection(null);
table.setEmptySelected(true);
} else {
int selectedRowIndex = Integer.parseInt(selection);
int first = table.getFirst();
int rows = table.getRows();
int last = rows == 0 ? table.getRowCount() : rows;
if (first <= selectedRowIndex && (first + last) > selectedRowIndex) {
table.setRowIndex(selectedRowIndex);
data = table.getRowData();
table.setSelection(table.getRowData());
}
}
return data;
}
private boolean isValueBlank(String value) {
if (value == null)
return true;
return value.trim().equals("");
}
private Object decodeMultipleSelection(DataTable table, String selection) {
Class<?> clazz =
table.getValueExpression("selection").getType(FacesContext.getCurrentInstance().getELContext());
Object data = null;
if (isValueBlank(selection)) {
data = Array.newInstance(clazz.getComponentType(), 0);
table.setSelection(data);
} else {
if (!table.isCellSelection()) {
String[] rowSelectValues = selection.split(",");
data = Array.newInstance(clazz.getComponentType(), rowSelectValues.length);
for (int i = 0; i < rowSelectValues.length; i++) {
table.setRowIndex(Integer.parseInt(rowSelectValues[i]));
Array.set(data, i, table.getRowData());
}
table.setSelection(data);
}
}
return data;
}
So you can use the getDataTableSelection(current facescontext instance, datatable instance, table clientId) method to get the selection.It will return a array object(never be null).
Note: when the validator in a row, the datatable clientId will be wrong append the row number. so please specify the table clientId.

JSF links in table not updating, sorting not swapping

I have a table displayed. When the user clicks on the headers i want the table to reorganise itself, sorted according to the selected header. Clicking on the same header again, swaps the order of sorting
Within the table, there are several commandLinks.
Now, the problems are as follows
the first update to the sorting order happens, but clicking on the header again will not swap the order.
When the table is refreshed, the links have their value changed, but the link still maps to the object that was previously there
Code:
Table:
<h:form title="table">
<h:dataTable value="#{employee.employeeList}" var="a"
styleClass="order-table" headerClass="order-table-header"
rowClasses="order-table-odd-row,order-table-even-row"
style="width:100%">
<h:column>
<f:facet name="header"><h:commandLink value="Number" action="#{employee.sortByNumber()}" /></f:facet>
<h:commandLink value="#{a.number}" action="#{employee.setEmp(a)}" />
</h:column>
...
...
...
</h:dataTable>
</h:form>
Employee Bean:
public String filter = ""; //has getters and setters
public String order = ""; //has getters and setters
public static final String ORDER_ASCENDING = "asc";
public static final String ORDER_DESCENDING = "desc";
public static final String FILTER_NUMBER = "number";
public void sortByNumber(){
if(filter.equals(FILTER_NUMBER)){
swapOrder();
}else{
filter = FILTER_NUMBER;
order = ORDER_ASCENDING;
}
refreshPage();
}
public void swapOrder(){
if(order.equals(ORDER_ASCENDING)){
order = ORDER_DESCENDING;
}else{
order = ORDER_ASCENDING;
}
}
public void refreshPage(){
FacesContext context = FacesContext.getCurrentInstance();
String viewId = context.getViewRoot().getViewId();
ViewHandler handler = context.getApplication().getViewHandler();
UIViewRoot root = handler.createView(context, viewId);
root.setViewId(viewId);
context.setViewRoot(root);
}
public String setEmp(Employee employee) {
this.employee = employee;
return "details"; //redirects to the details page where the employee set in the previous line is used
}

Resources