Moving away from parameterized getters - jsf

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;
}

Related

Pass initialization argument from JSF view to managed bean in background

I am new to JSF and am struggling with passing data to my backing bean. I have a bean that should read and save data to one specific table. This table has two columns "Property" and "Val". The simplified version of this class looks like this:
#ManagedBean
#SessionScoped
public class GlobalProperties implements Serializable {
// private void setKey(param);
// private String getKey();
// some more attributes and getters / setters
private String property; // + getter/setter
private void saveProperty() throws SQLException {
try {
dbHandler = dbConnection.connect();
ps = dbHandler.prepareStatement("UPDATE TABLEXXX SET VAL = '" + getValue() + "' WHERE PROPERTYKEY = '" + getProperty() + "'");
ps.executeQuery();
} catch (SQLException ex) {
Logger.getLogger(GlobalProperties.class.getName()).log(Level.SEVERE, null, ex);
} finally {
if (dbHandler != null) {
dbHandler.close();
}
}
}
private void readProperty() throws SQLException {
try {
dbHandler = dbConnection.connect();
ps = dbHandler.prepareStatement("SELECT VAL FROM TABLEXXX WHERE PROPERTYKEY = '" + getProperty() + "'");
rs = ps.executeQuery();
while (rs.next()) {
setValue("VAL");
}
} catch (SQLException ex) {
Logger.getLogger(GlobalProperties.class.getName()).log(Level.SEVERE, null, ex);
} finally {
if (dbHandler != null) {
dbHandler.close();
}
}
}
}
This class is being used on several pages. On each page it is being used, it needs to be filled with a different value for the property-attribute since I have to read different keys from the table. On JSF-side there is a text input which displays the value and sets it when user clicks a save button.
Whenever an instance of this class is created, I need to pass an information to the class about what key it has to read. But i cannot find an appropriate mechanism on JSF-side to accomplish that, since it is heavily important, that the user can not change that value.
My last try was to use the "preRenderView"-Event like this:
<f:metadata>
<f:event type="preRenderView" listener="#{globalProperties.setKey('SYSTEM_NAME')}" />
</f:metadata>
Is that the right way?
What is the best practice to set a property in a ManagedBean in the background, without the need of any user action and in a safe way, so that it can not be manipulated by the user?
Thank you guys.
<f:event type="preRenderView"> is invoked on every request. That may be OK for request scoped beans, but is unnecessary for view/session scoped beans. See also What can <f:metadata>, <f:viewParam> and <f:viewAction> be used for?
On the other hand, whilst the preRenderView approach will work, the JSF view should be declarative. The model (the backing bean in this specific perspective) should actually be performing the initialization based on the view and not the other way round. So your intuition was right when you got doubts on this.
JSF doesn't have a dedicated approach for this. The closest declarative approach is using <f:attribute> in <f:metadata> ("define a metadata attribute on the view").
<f:metadata>
<f:attribute name="key" value="SYSTEM_NAME" />
</f:metadata>
This is available by UIViewRoot#getAttributes() in a.o #PostConstruct:
String key = (String) context.getViewRoot().getAttributes().get("key");
Or when you happen to use OmniFaces:
String key = Faces.getMetadataAttribute("key");
The OmniFaces showcase application also uses this approach to declare paths to documentation and source code (see for example /push/socket.xhtml source of <o:socket> showcase).
The alternative approach is to have an application wide mapping of keys by view ID and rely on the view ID instead.
Map<String, String> keysByViewId = new HashMap<>();
keysByViewId.put("/foo.xhtml", "SYSTEM_NAME");
String key = keysByViewId.get(context.getViewRoot().getViewId());

Primefaces tree and how to navigate based on TreeNode leaf entity ID

I have 3 entities - markets, topics and items. Markets is the parent of topics which is the parent of items. I'm hoping to find a simple way to invoke an action by selecting a value from the the final child node (items) and being taken to the page where the selected item can be viewed. The JSF:
<p:tree value="#{treeTestBean.treeTest}" var="tree"
dynamic="true"
selectionMode="single"
selection="#{treeTestBean.selectednode}">
<p:ajax event="select" listener="#{treeTestBean.onNodeSelect}"/>
<p:treeNode>
<h:outputText value="#{tree}"/>
</p:treeNode>
</p:tree>
The managed bean:
#Named(value = "treeTestBean")
#SessionScoped
public class TreeTestBean implements Serializable {
private TreeNode treetest;
private TreeNode selectednode;
private TreeNode node0;
private TreeNode node1;
private TreeNode node2;
private List<Enmarkets> markList;
private List<Entopic> topList;
private ListDataModel<Enitem> itList;
private Enitem selItem;
public TreeNode getTreeTest() {
treetest = new DefaultTreeNode("Root", null);
markList = rootFacade.findAll();
for (Enmarkets m : markList) {
node0 = new DefaultTreeNode(m.getMarketname(), treetest);
int marketid = m.getMarketid();
topList = topfac.marketTopNorm(marketid);
for (Entopic t : topList) {
node1 = new DefaultTreeNode(t.getTopicname(), node0);
int topicid = t.getTopicid();
itList = itfac.itemFroTopic(topicid);
for (Enitem i : itList) {
node2 = new DefaultTreeNode(i.getItemname(), node1);
}
}
}
return treetest;
}
The onNodeSelect method used in the ajax is also in the managed bean. If the selected node is a leaf it will search the item name and return that in the navigated page:
public void onNodeSelect(NodeSelectEvent event) {
this.setSelectednode(event.getTreeNode());
String somekey = selectednode.getRowKey();
if(selectednode.isLeaf()){
String itemName = selectednode.getData().toString();
// Standard JPA call to search for item name here (omitted because this is not how i want to do it)
FacesContext
.getCurrentInstance()
.getApplication()
.getNavigationHandler()
.handleNavigation(FacesContext.getCurrentInstance(), null, "/Main/Starter.xhtml?faces-redirect=true");
}
else {
doNothing();
}
}
onNodeSelect is supposed to search the item name and navigates to the page with details of the selected item. The above method does this by searching for the Item name String and matching this to the name in a list of the item entity values created from the persistence layer. This will allow matching the selectednode String to the correct item name, so that the navigated jsf page is populated with the entity details (for example using a standard h:outputText tag). For several reasons, i prefer to search based on the entity ID instead of a String.
Comments from Kukeltje greatly helped me in the right direction. First I include a Map(String, int) when creating the leaf node:
for (Enitem i : itList) {
node2 = new DefaultTreeNode(i.getItemname(), node1);
String rowK = node2.getRowKey();
int itid = i.getItemid();
rowMap.put(rowK, itid);
Then, in the onNodeSelect method I use this map to match the rowKey of the selectednode to the corresponding entity Id:
public void onNodeSelect(NodeSelectEvent event) {
if(selectednode.isLeaf()){
String rKey = selectednode.getRowKey();
if(rowMap.containsKey(rKey)) {
String xKey = rowMap.get(rKey).toString();
Integer rKeyint = Integer.parseInt(xKey);
selItem = itfac.find(rKeyint);
FacesContext
.getCurrentInstance()
.getApplication()
.getNavigationHandler()
.handleNavigation(FacesContext.getCurrentInstance(), null, "/Main/Client/ItemDetails.xhtml?faces-redirect=true");
}
}
else {
doNothing();
}
This navigates to the page showing the detail of the selected node leaf. I suspect there might be an easier or more efficient way of doing this and would welcome any views. Like, I don't know if it's really necessary to make the string to integer conversions and I didn't think through a simpler way.
For now, this seems to solve my concrete problem. thanks

actionListener commandLink not working on lazy dataScroller

In a project i need to lazy load object from database, and for each element i will put a link to redirect to a specific page.
The lazy loading is working. and when a click on the link for the first element it's ok, the problem is after scrolling, the next element don't call the listner listOi.editer().
<p:dataScroller value="#{listOi.lazyOi}" var="oi" id="OisChoser" widgetVar="scroller"
chunkSize="5" mode="inline" scrollHeight="531" lazy="true" style="width: 597px;" rows="5" >
<h:panelGroup id="info_OI" class="info_OI" align="center" >
...
<h:commandLink actionListener="#{listOi.editer()}" immediate="true" >
<f:param name="selectedoiId" value="#{oi.id}" />
<span class="crayon" style='cursor: pointer;'></span>
</h:commandLink>
...
</h:panelGroup
</p:dataScroller>
The problem is that PrimeFaces' LazyDataModel does not keep a complete model of the data displayed in the view. It only keeps track of the most recently loaded items and discards older items. This makes that these items are no longer accessible from JSF.
But since you are subclassing that class anyway (it's abstract), it's pretty easy to alter that behavior. Basically what you want to do is keep track of all the data you loaded so far and return that data as asked for. At the minimum, you need to override setRowIndex(), getRowData(), isRowAvailable() and load(). Below is an example that works for me.
public class MyLazyModel extends LazyDataModel<SomeType> implements Serializable {
private final List<SomeType> data;
private int rowIndex;
public MyLazyModel() {
super();
data = new ArrayList<>();
}
#Override
public List<SomeType> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
List<SomeType> retData;
if (first >= data.size()) {
retData = ... //Get the data from datasource
data.addAll(retData);
return retData;
} else {
return data.subList(first, Math.min(first + pageSize, data.size()));
}
}
#Override
public void setRowIndex(int index) {
if (index >= data.size()) {
index = -1;
}
this.rowIndex = index;
}
#Override
public SomeType getRowData() {
return data.get(rowIndex);
}
#Override
public boolean isRowAvailable() {
if (data == null) {
return false;
}
return rowIndex >= 0 && rowIndex < data.size();
}
}
Because you can never be sure that setWrappedData() (which is called after load() with the return data from that method) is called only once, you need to guard against adding doubles to your data model. Here I do this by only loading data that has never been loaded before and storing that data in the load() method, ignoring setWrappedData() completely. It's quite ugly and will lead to synchronization problems if your model is never invalidated, but it works. Anyway, you could circumvent this by always loading data and replacing old content with new, but that's not the core of the issue.
Also, because you now keep track of the data in the method itself, you need to override all methods in LazyDataModel that rely on LazyDataModel.data being correct (or at least that subset of them that you are using).
Final note: you of course have to make sure that your model returned to the JSF page is always the same, as discussed here.

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 populate JSF inputText with initial value and submit control value to another property?

Let's say
I have
<h:dataTable var="s" value#{somebean.properties}>
<h:column>
<h:inputText initial=#{s.min} value=#{somebean.mintmp}/>
<h:commandButton action=#{filterbean.addProretryFilter(s.id, somebean.mintmp)} />
</h:column>
</h:dataTable>
"initail" attribute don't exit in inputText.
Is there any way to implement desired functionality?
You can bind your input text field to backing bean and initialize it in constructor or #PostConstruct and set the initial value.
#ManagedBean
public class Bean{
private HtmlInputText inputComponent = new HtmlInputText();
private String min = "5";
private String minData;
#PostConstruct
public void init(){
inputComponent.setValue(min);
}
//....get/set other logic
}
In view you can have
<h:inputText value="#{bean.minData}" binding="#{bean.inputComponent}"></h:inputText>
I think you need to rethink your design. Even without populating the default value, you have a problem. This pseudocode is roughly analogous to your logic:
//beans
SomeBean somebean = ...
DataModel model = ...
FilterBean filterbean = ...
//apply request values phase
for(int i=0; i<model.getRowCount(); i++) {
model.setRowIndex(i)
S s = model.getRowData()
//inputText's state is set to the submitted row state by the dataTable
Object submittedValue = inputText.getSubmittedValue()
somebean.mintmp = submittedValue
}
//invoke application phase
for(int i=0; i<model.getRowCount(); i++) {
model.setRowIndex(i)
S s = model.getRowData();
//commandButton's state is set to the submitted row state by the dataTable
if(commandButton.clicked()) {
filterbean.addProretryFilter(s.id, somebean.mintmp)
}
}
somebean will always be populated with the last row value.
See the JSF lifecycle and the DataModel for more details.
in your managed bean's getter you could return a default value. E.g.
private String mintmp=null;
public String getMintmp()
{
if(mintmp == null)
return "default min";
return mintmp;
}
Use primefaces! There's this really nice update attribute on buttons that update the components you want to refresh. It's magical.

Resources