Key press handling in JSF component, especially <p:tree> - jsf-2

Goal: I want to enrich a predefined component with my own behavior. This is typically the case with list, tables and trees, implementing my actions like "delete", "add before", "add after", "move up",... (with text field this seems to be simple...)
I thought there must be a way to attach key listeners at the component itself (assumed that there's something like a "focus"), e.g. if i have two trees on a page pressing "Ctrl+" will add one time an A to treeA via listenerA and the other a B to treeB via listenerB.
Adding an ajax listener at a tree node or the tree itself does not work. So it seems to be necessary (see two answers below) to catch key globally and "dispatch" them myself properly. At least with one tree this should work without hassle.
According to the answers below this can only be done using JavaScript or using a non standard JSF tag.
As i am concerned with JSF question at most 2 times a year, i think someone more involved can give insight in best practice on this twilight zone between JSF and JavaScript.
In this snippet i want to create a new child item when "+" is pressed.
<h:form>
<p:tree id="document" value="#{demo.root}" var="node"
selectionMode="single" selection="#{demo.selection}">
<p:treeNode>
<h:outputText value="#{node.label}" />
</p:treeNode>
</p:tree>
</h:form>
The tag
<f:ajax event="keypress" listener="#{demo.doTest}" />
is not accepted in "treeNode" and "tree" and has no function in "form".
= EDIT
As can be seen in the answers, this concrete scenario is supported by simply using <p:hotkey>. This solution has 2 drawbacks, its Primefaces bound and it fails if we add input components like this
<h:form>
<p:tree id="document" value="#{demo.root}" var="node"
selectionMode="single" selection="#{demo.selection}">
<p:treeNode>
<p:inputText value="#{node.label}" />
</p:treeNode>
</p:tree>
</h:form>
What is the best practice to implement such things? At least, is it possible in plain JSF at all? If i only use plain JSF, what would be the least ugly idiom.
= EDIT
I want to point to a short history of findings, given as an answer below, to give more detail on the problem behind this question

This implementation enables navigation and add/remove as well.
IMHO it has the best functionality/effort ratio.
I don't know what you mean with standard JSF tag or plain JSF, but in this example there isn't a single line of JavaScript.
Note that p:hotkey component behavior is global. Non-input components like p:tree cannot have owned key listeners since they can't be "focused" (or at least by default behavior), just like you pointed.
However, here it is:
<h:form>
<p:hotkey bind="left" actionListener="#{testBean.onLeft}" process="#form" update="target" />
<p:hotkey bind="right" actionListener="#{testBean.onRight}" process="#form" update="target" />
<p:hotkey bind="up" actionListener="#{testBean.onUp}" process="#form" update="target" />
<p:hotkey bind="down" actionListener="#{testBean.onDown}" process="#form" update="target" />
<p:hotkey bind="ctrl+a" actionListener="#{testBean.onAdd}" process="#form" update="target" />
<p:hotkey bind="ctrl+d" actionListener="#{testBean.onDelete}" process="#form" update="target" />
<h:panelGroup id="target">
<p:tree value="#{testBean.root}" var="data" selectionMode="single"
selection="#{testBean.selection}" dynamic="true">
<p:treeNode expandedIcon="ui-icon-folder-open" collapsedIcon="ui-icon-folder-collapsed">
<h:outputText value="#{data}" />
</p:treeNode>
</p:tree>
<br />
<h3>current selection: #{testBean.selection.data}</h3>
</h:panelGroup>
</h:form>
and this is the managed bean:
#ManagedBean
#ViewScoped
public class TestBean implements Serializable
{
private static final long serialVersionUID = 1L;
private DefaultTreeNode root;
private TreeNode selection;
#PostConstruct
public void init()
{
root = new DefaultTreeNode("node");
root.setSelectable(false);
DefaultTreeNode node_0 = new DefaultTreeNode("node_0");
DefaultTreeNode node_1 = new DefaultTreeNode("node_1");
DefaultTreeNode node_0_0 = new DefaultTreeNode("node_0_0");
DefaultTreeNode node_0_1 = new DefaultTreeNode("node_0_1");
DefaultTreeNode node_1_0 = new DefaultTreeNode("node_1_0");
DefaultTreeNode node_1_1 = new DefaultTreeNode("node_1_1");
node_0.setParent(root);
root.getChildren().add(node_0);
node_1.setParent(root);
root.getChildren().add(node_1);
node_0_0.setParent(node_0);
node_0.getChildren().add(node_0_0);
node_0_1.setParent(node_0);
node_0.getChildren().add(node_0_1);
node_1_0.setParent(node_1);
node_1.getChildren().add(node_1_0);
node_1_1.setParent(node_1);
node_1.getChildren().add(node_1_1);
selection = node_0;
node_0.setSelected(true);
}
private void initSelection()
{
List<TreeNode> children = root.getChildren();
if(!children.isEmpty())
{
selection = children.get(0);
selection.setSelected(true);
}
}
public void onLeft()
{
if(selection == null)
{
initSelection();
return;
}
if(selection.isExpanded())
{
selection.setExpanded(false);
return;
}
TreeNode parent = selection.getParent();
if(parent != null && !parent.equals(root))
{
selection.setSelected(false);
selection = parent;
selection.setSelected(true);
}
}
public void onRight()
{
if(selection == null)
{
initSelection();
return;
}
if(selection.isLeaf())
{
return;
}
if(!selection.isExpanded())
{
selection.setExpanded(true);
return;
}
List<TreeNode> children = selection.getChildren();
if(!children.isEmpty())
{
selection.setSelected(false);
selection = children.get(0);
selection.setSelected(true);
}
}
public void onUp()
{
if(selection == null)
{
initSelection();
return;
}
TreeNode prev = findPrev(selection);
if(prev != null)
{
selection.setSelected(false);
selection = prev;
selection.setSelected(true);
}
}
public void onDown()
{
if(selection == null)
{
initSelection();
return;
}
if(selection.isExpanded())
{
List<TreeNode> children = selection.getChildren();
if(!children.isEmpty())
{
selection.setSelected(false);
selection = children.get(0);
selection.setSelected(true);
return;
}
}
TreeNode next = findNext(selection);
if(next != null)
{
selection.setSelected(false);
selection = next;
selection.setSelected(true);
}
}
public void onAdd()
{
if(selection == null)
{
selection = root;
}
TreeNode node = createNode();
node.setParent(selection);
selection.getChildren().add(node);
selection.setExpanded(true);
selection.setSelected(false);
selection = node;
selection.setSelected(true);
}
public void onDelete()
{
if(selection == null)
{
return;
}
TreeNode parent = selection.getParent();
parent.getChildren().remove(selection);
if(!parent.equals(root))
{
selection = parent;
selection.setSelected(true);
if(selection.isLeaf())
{
selection.setExpanded(false);
}
}
else
{
selection = null;
}
}
// create the new node the way you like, this is an example
private TreeNode createNode()
{
int prog = 0;
TreeNode lastNode = Iterables.getLast(selection.getChildren(), null);
if(lastNode != null)
{
prog = NumberUtils.toInt(StringUtils.substringAfterLast(String.valueOf(lastNode.getData()), "_"), -1) + 1;
}
return new DefaultTreeNode(selection.getData() + "_" + prog);
}
private TreeNode findNext(TreeNode node)
{
TreeNode parent = node.getParent();
if(parent == null)
{
return null;
}
List<TreeNode> brothers = parent.getChildren();
int index = brothers.indexOf(node);
if(index < brothers.size() - 1)
{
return brothers.get(index + 1);
}
return findNext(parent);
}
private TreeNode findPrev(TreeNode node)
{
TreeNode parent = node.getParent();
if(parent == null)
{
return null;
}
List<TreeNode> brothers = parent.getChildren();
int index = brothers.indexOf(node);
if(index > 0)
{
return findLastUnexpanded(brothers.get(index - 1));
}
if(!parent.equals(root))
{
return parent;
}
return null;
}
private TreeNode findLastUnexpanded(TreeNode node)
{
if(!node.isExpanded())
{
return node;
}
List<TreeNode> children = node.getChildren();
if(children.isEmpty())
{
return node;
}
return findLastUnexpanded(Iterables.getLast(children));
}
public TreeNode getRoot()
{
return root;
}
public TreeNode getSelection()
{
return selection;
}
public void setSelection(TreeNode selection)
{
this.selection = selection;
}
}
UPDATE
Maybe I found an interesting solution to attach key bindings to single DOM elements:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:cc="http://xmlns.jcp.org/jsf/composite" xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:fn="http://xmlns.jcp.org/jsp/jstl/functions" xmlns:p="http://primefaces.org/ui"
xmlns:o="http://omnifaces.org/ui" xmlns:of="http://omnifaces.org/functions"
xmlns:s="http://shapeitalia.com/jsf2" xmlns:sc="http://xmlns.jcp.org/jsf/composite/shape"
xmlns:e="http://java.sun.com/jsf/composite/cc" xmlns:pt="http://xmlns.jcp.org/jsf/passthrough">
<h:head>
<title>test hotkey</title>
</h:head>
<h:body>
<h:form>
<h:panelGroup id="container1">
<s:hotkey bind="left" actionListener="#{testBean.onLeft}" update="container1" />
<s:hotkey bind="right" actionListener="#{testBean.onRight}" update="container1" />
<s:hotkey bind="up" actionListener="#{testBean.onUp}" update="container1" />
<s:hotkey bind="down" actionListener="#{testBean.onDown}" update="container1" />
<s:hotkey bind="ctrl+a" actionListener="#{testBean.onAdd}" update="container1" />
<s:hotkey bind="ctrl+d" actionListener="#{testBean.onDelete}" update="container1" />
<p:tree value="#{testBean.root}" var="data" selectionMode="single"
selection="#{testBean.selection}" dynamic="true" pt:tabindex="1">
<p:treeNode expandedIcon="ui-icon-folder-open"
collapsedIcon="ui-icon-folder-collapsed">
<h:outputText value="#{data}" />
</p:treeNode>
</p:tree>
<br />
<h3>current selection: #{testBean.selection.data}</h3>
</h:panelGroup>
</h:form>
</h:body>
</html>
three important things:
h:panelGroup attribute id is required, otherwise it is not rendered as DOM element. style, styleClass, and other render-enable attributes can be used with or instead.
Note that pt:tabindex=1 on p:tree: it is required to enable "focus". pt is the namespace used for "passthrough" attributes and only works in JSF 2.2.
I had to customize HotkeyRenderer in order to attach the DOM event listener to a specific DOM element instead of the entire document: now it's s:hotkey instead of p:hotkey. My implementation attachs it to DOM element associated to parent component, continue read for implementation.
the modified renderer:
#FacesRenderer(componentFamily = Hotkey.COMPONENT_FAMILY, rendererType = "it.shape.HotkeyRenderer")
public class HotkeyRenderer extends org.primefaces.component.hotkey.HotkeyRenderer
{
#SuppressWarnings("resource")
#Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
Hotkey hotkey = (Hotkey) component;
String clientId = hotkey.getClientId(context);
String targetClientId = hotkey.getParent().getClientId();
writer.startElement("script", null);
writer.writeAttribute("type", "text/javascript", null);
writer.write("$(function() {");
writer.write("$(PrimeFaces.escapeClientId('" + targetClientId + "')).bind('keydown', '" + hotkey.getBind() + "', function(){");
if(hotkey.isAjaxified())
{
UIComponent form = ComponentUtils.findParentForm(context, hotkey);
if(form == null)
{
throw new FacesException("Hotkey '" + clientId + "' needs to be enclosed in a form when ajax mode is enabled");
}
AjaxRequestBuilder builder = RequestContext.getCurrentInstance().getAjaxRequestBuilder();
String request = builder.init()
.source(clientId)
.form(form.getClientId(context))
.process(component, hotkey.getProcess())
.update(component, hotkey.getUpdate())
.async(hotkey.isAsync())
.global(hotkey.isGlobal())
.delay(hotkey.getDelay())
.timeout(hotkey.getTimeout())
.partialSubmit(hotkey.isPartialSubmit(), hotkey.isPartialSubmitSet())
.resetValues(hotkey.isResetValues(), hotkey.isResetValuesSet())
.ignoreAutoUpdate(hotkey.isIgnoreAutoUpdate())
.onstart(hotkey.getOnstart())
.onerror(hotkey.getOnerror())
.onsuccess(hotkey.getOnsuccess())
.oncomplete(hotkey.getOncomplete())
.params(hotkey)
.build();
writer.write(request);
}
else
{
writer.write(hotkey.getHandler());
}
writer.write(";return false;});});");
writer.endElement("script");
}
}
and finally this is the taglib definition for the new s:hotkey (it's a copy/paste of the original with the only difference of <renderer-type>it.shape.HotkeyRenderer</renderer-type>):
<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facelettaglibrary_2_2.xsd">
<namespace>http://shapeitalia.com/jsf2</namespace>
<tag>
<description><![CDATA[HotKey is a generic key binding component that can bind any formation of keys to javascript event handlers or ajax calls.]]></description>
<tag-name>hotkey</tag-name>
<component>
<component-type>org.primefaces.component.Hotkey</component-type>
<renderer-type>it.shape.HotkeyRenderer</renderer-type>
</component>
<attribute>
<description><![CDATA[Unique identifier of the component in a namingContainer.]]></description>
<name>id</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Boolean value to specify the rendering of the component, when set to false component will not be rendered.]]></description>
<name>rendered</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
<attribute>
<description><![CDATA[An el expression referring to a server side UIComponent instance in a backing bean.]]></description>
<name>binding</name>
<required>false</required>
<type>javax.faces.component.UIComponent</type>
</attribute>
<attribute>
<description><![CDATA[An actionlistener that'd be processed in the partial request caused by uiajax.]]></description>
<name>actionListener</name>
<required>false</required>
<type>javax.faces.event.ActionListener</type>
</attribute>
<attribute>
<description><![CDATA[A method expression that'd be processed in the partial request caused by uiajax.]]></description>
<name>action</name>
<required>false</required>
<type>javax.el.MethodExpression</type>
</attribute>
<attribute>
<description><![CDATA[Boolean value that determines the phaseId, when true actions are processed at apply_request_values, when false at invoke_application phase.]]></description>
<name>immediate</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
<attribute>
<description><![CDATA[The Key binding. Required.]]></description>
<name>bind</name>
<required>true</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Client side id of the component(s) to be updated after async partial submit request.]]></description>
<name>update</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Component id(s) to process partially instead of whole view.]]></description>
<name>process</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Javascript event handler to be executed when the key binding is pressed.]]></description>
<name>handler</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Javascript handler to execute before ajax request is begins.]]></description>
<name>onstart</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Javascript handler to execute when ajax request is completed.]]></description>
<name>oncomplete</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Javascript handler to execute when ajax request fails.]]></description>
<name>onerror</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Javascript handler to execute when ajax request succeeds.]]></description>
<name>onsuccess</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Global ajax requests are listened by ajaxStatus component, setting global to false will not trigger ajaxStatus. Default is true.]]></description>
<name>global</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
<attribute>
<description><![CDATA[If less than delay milliseconds elapses between calls to request() only the most recent one is sent and all other requests are discarded. The default value of this option is null. If the value of delay is the literal string 'none' without the quotes or the default, no delay is used.]]></description>
<name>delay</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description><![CDATA[Defines the timeout for the ajax request.]]></description>
<name>timeout</name>
<required>false</required>
<type>java.lang.Integer</type>
</attribute>
<attribute>
<description><![CDATA[When set to true, ajax requests are not queued. Default is false.]]></description>
<name>async</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
<attribute>
<description><![CDATA[When enabled, only values related to partially processed components would be serialized for ajax
instead of whole form.]]></description>
<name>partialSubmit</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
<attribute>
<description><![CDATA[If true, indicate that this particular Ajax transaction is a value reset transaction. This will cause resetValue() to be called on any EditableValueHolder instances encountered as a result of this ajax transaction. If not specified, or the value is false, no such indication is made.]]></description>
<name>resetValues</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
<attribute>
<description><![CDATA[If true, components which autoUpdate="true" will not be updated for this request. If not specified, or the value is false, no such indication is made.]]></description>
<name>ignoreAutoUpdate</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
</tag>
</facelet-taglib>
whew, it was hard ;)

Until now, there's no really satisfying answer so far. I summarize my findings:
Some JSF components have "inner logic" that bind some keys to component specific features. Too bad that "intelligent components" like <p:tree /> don't even bind arrow key navigation.
So you try to emulate and find <p:hotkey/>. No you can (as shown in the very extensive answer by #michele-mariotti) feel a little comfortable wih your component.
Then you add input features to the tree... And hotkeys are breaking down. You do not know for what reasons (and, really, i think you should not have to...).
So you start digging around and suddenly find yourself in JavaScript and DOM wonderland.
The "hotkey" library for the ubiquitous jQuery seems to bring help. Or one of the 1000 others you bring up when searching for this stuff. Better take the right one from the start (which one is it?).
So you start adding ugly jQuery expressions for each and every accelerator, first on the document, then down on every input component (as shown e.g. here). Your page starts beeing a mess.
But you're happy - at least after two days you have brought up a simple tree..
Now you add sugar. You add <p:inplace /> or simply add new tree nodes. Your hotkeys break down.
Oh, yes, you should have known: The dynamic inputs are not bound to hotkeys. Add some more JavaScript hacks to the page...
But hey, what's this: Testing all your hotkey stuff, you forgot to enter values in the tree input fields. Now you realize: it's not working!! Again some searching: Seems to be a well known bug/missing feature for years. Primefaces removes focus immediately after activating the tree input. Well, who on earth makes input in a tree...
So, here's where you could debug some some sophisticated Primefaces JavaScript or add some other equally sophisticated JavaScript to force the focus back to this field. You could realize you use the wrong component library and restart with Richfaces tree, Omnifaces tree or whatever. You could resign to use web technology, sleep another 2 years and come back to see if basic technology has evolved to be usable. Is Java web simply a playground for tinkerers?
After this piece of rant, is there anybody that can help with some advice?

I found a workaround that does not exactly what was asked, but can handle my scenario.
Adding a "hotkey" component to the form calls the server as requested:
<p:hotkey bind="ctrl+shift+a" update="messages" actionListener="#{demo.doTest}"/>
Similiar component exists in RichFaces, don't know about plain JSF.
What i can't believe is that there's no other way then reverting down to JavaScript (like http://winechess.blogspot.ru/2014/02/datatable-keyboard-navigation.html or http://www.openjs.com/scripts/events/keyboard_shortcuts/) to write usable JSF apps?
And that standard components like tree or table do not have standard keyboard navigation (its 2015, i even do not remember when Web 2.0 was invented).
Any hint to best practice?

And some more investigation before a more enlightened brain can lift the secret...
A somewhat similiar q/a solves the problem on how to get to call a backend method from JS if a key was handled in JS - use the
<p:remoteCommand>
see Catch key pressed ajax event without input fields for the ugly details.
Again, this is a global key catch, not component sensitive. But nice to know. Does this exist in plain JSF, too?

Related

PrimeFaces inputText enable/disable

I have two radio buttons on JSF2/PrimeFaces page, which I need to enable/disable an inputText field depending on the choice.
<h:form prependId="false">
<p:panelGrid id="notif">
<p:row id="notif1">
<p:column>
<h:outputText value="label" />
</p:column>
<p:column>
<p:selectOneRadio id="radiobuttons" value="#{state.radioStatus}" layout="pageDirection">
<f:selectItem itemLabel="enable" itemValue="enable" />
<f:selectItem itemLabel="disable" itemValue="disable" />
<p:ajax update="date" />
</p:selectOneRadio>
</p:column>
<p:column>
<p:inputText id="date" value="#{state.infoDate}" disabled="#{!state.checkStatus}" >
</p:inputText>
<p:inputText id="test" value="#{state.infoDateTest}">
</p:inputText>
</p:column>
</p:row>
</p:panelGrid>
</h:form>
Also this all is inside a tab, but there is no nested forms when checked the generated html. The fields seem to get the id of the tab, but they seem to be correct like this (Did not work when tried to use id notation 'tabView:date' for the ajax.)
The problem is that when entering the page, the 'actual' text field is disabled by default and should be enabled when the radio button is set to enable it. When the radio button status is updated, I can see the event is registered on the server side and the inputText field gets "visually" enabled (by default it is disabled as supposed to). However the field is not "focusable" after enabling it, so can't write to it.
The more bizarre it gets when I added the 'test' inputText field, which does not get it self enabled/disabled regarding to radio buttons. On a fresh page load you can write to it, but after changing the radio button status, it is not writable anymore (the text that was entered prior to ajax call, will remain, but is not editable).
However the radio buttons continue to work and will fire events on the server side.
Could someone point out what I may be doing incorrectly, or could this be a bug with PrimeFaces?
// Update
Like said, the managed bean works as supposed to, so I don't see any problems there, but here is the code (simplified and renamed for readability as the relevant parts are taken from a bigger context).
#ManagedBean(name = "state")
#SessionScoped
public class ExampleBean implements Serializable {
private static final String disable = "disable";
private static final String enable = "enable";
private String text;
private List<TestRow> rows = null;
private TestRow row = null;
private String radioStatus = "enable";
private String infoDate = "";
private String infoDateTest = "";
#PostConstruct
public void initEmpty() {
}
public String getRadioStatus() {
return this.radioStatus;
}
public void setRadioStatus(String radioStatus) {
this.radioStatus = radioStatus;
}
public String getInfoDate() {
return infoDate;
}
public void setInfoDate(String infoDate) {
this.infoDate = infoDate;
}
public String getInfoDateTest() {
return infoDateTest;
}
public void setInfoDateTest(String infoDateTest) {
this.infoDateTest = infoDateTest;
}
public boolean isCheckStatus() {
return this.radioStatus.equalsIgnoreCase(enable) ? true : false;
}
}
Unfortunately I was not able to figure out what is causing the actual problem. The basic thing seems to be that the inherited javascript (primefaces) is not working properly on this page (maybe because of some other js inherited). Finally I just made it a custom enable/disable js-function:
js.toggleDisableStatus = function(elementId) {
// Can't use jQuery $("#xx:yy"), the syntax is not accepted by jquery but
// used by JSF/PrimeFaces
var element = document.getElementById(elementId);
var attrValue = element.getAttribute('disabled');
if (attrValue === undefined || attrValue == null) {
element.disabled = true;
element.style.background = "#dddddd";
} else {
element.removeAttribute('disabled');
element.style.background = "";
}
};
This may be flawed but works for now. The radio buttons are selected by the persisted status (loaded in state) and the enable/disable state for the input-field is also set when initially loading the page. Generally I think this should work for this particular case.

composite component button action issue in jsf2 mojarra

Trying to develop a composite component using jsf2.0 (Mojarra) which should render command buttons dynamically based on the list from the bean. I was able to render the buttons but action is not getting triggered.Could any one please help me to resolve the issue?
Here follows the code
<composite:interface>
<composite:attribute name="buttonList" required="true"
type="java.util.List" />
<composite:attribute name="beanName" required="true"
type="java.lang.Object" />
</composite:interface>
<composite:implementation>
<ui:repeat var="listItem" value="#{cc.attrs.buttonList}">
<h:commandButton value="#{listItem.buttonName}"
action="#{cc.attrs.beanName.listItem.buttonAction}">
</h:commandButton>
</ui:repeat>
</composite:implementation>
This is used as
<utils:buttonGroup buttonList="#{testButtonBean.buttonList}"
beanName="#{testButtonBean}" />
The bean looks like
public class TestButtonBean {
public List<ButtonPOJO> buttonList = new ArrayList<ButtonPOJO>();
public List<ButtonPOJO> getButtonList() {
return buttonList;
}
public void setButtonList(List<ButtonPOJO> buttonList) {
this.buttonList = buttonList;
}
public void preProcess() {
if (null != buttonList && buttonList.size() == 0) {
ButtonPOJO ob1 = new ButtonPOJO("Continue", "next");
ButtonPOJO ob2 = new ButtonPOJO("Back", "prev");
buttonList.add(ob1);
buttonList.add(ob2);
}
}
public String next() {
return "page1";
}
public String prev() {
return "page2";
}
}
action="#{cc.attrs.beanName.listItem.buttonAction}"
This is not right. This syntax is basically looking for a property listItem on beanName and then trying to invoke the literal action buttonAction() on it.
You need the brace notation action="#{bean[methodName]}" if you want to specify the action method name as string coming from another bean property.
action="#{cc.attrs.beanName[listItem.buttonAction]}"
Unrelated to the concrete problem, if the above solution still fails, then that can only mean that the value="#{cc.attrs.buttonList}" has incompatibly changed during the form submit request. You need to make sure that exactly the same list is prepared during the postback as it was during the initial request. See also point 4 of commandButton/commandLink/ajax action/listener method not invoked or input value not updated.

Handling view parameters in JSF after post

I have a few pages that needs a userId to work, thus the following code:
userpage.xhtml
<!-- xmlns etc. omitted -->
<html>
<f:metadata>
<f:viewParam name="userId" value="#{userPageController.userId}"/>
</f:metadata>
<f:view contentType="text/html">
<h:head>
</h:head>
<h:body>
<h:form>
<h:commandButton action="#{userPageController.doAction}" value="post"/>
</h:form>
</h:body>
</f:view>
userPageController.java
#Named
#ViewScoped
public class userPageControllerimplements Serializable {
private static final long serialVersionUID = 1L;
#Inject protected SessionController sessionController;
#Inject private SecurityContext securityContext;
#Inject protected UserDAO userDAO;
protected User user;
protected Long userId;
public UserPage() {
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
if(!FacesContext.getCurrentInstance().isPostback()){
User u = userDAO.find(userId);
this.userId = userId;
this.user = u;
}
}
public void doAction(){
}
}
However, after doAction is called, the view parameter in the url disappears. The bean still works due to its viewscoped nature, but it ruins my attempts of future navigation. When i search around, I get the impression that the view parameter should remain after a post thus reading userpage.jsf?userId=123, but this is not the case. What is really the intended behaviour?
Related to this, I've tried to implement automatic adding of view parameters when navigating to another page where I want to keep the userId. It seems to work for others, but for me, the userId in the ViewRoot is always null. Code below used to retrieve the viewparameter (i know i could use my temporarily stored userId in the bean for navigation, but this solution would be much fancier):
String name = "userId";
FacesContext ctx = FacesContext.getCurrentInstance();
ViewDeclarationLanguage vdl = ctx.getApplication().getViewHandler().getViewDeclarationLanguage(ctx, viewId);
ViewMetadata viewMetadata = vdl.getViewMetadata(ctx, viewId);
UIViewRoot viewRoot = viewMetadata.createMetadataView(ctx);
UIComponent metadataFacet = viewRoot.getFacet(UIViewRoot.METADATA_FACET_NAME);
// Looking for a view parameter with the specified name
UIViewParameter viewParam = null;
for (UIComponent child : metadataFacet.getChildren()) {
if (child instanceof UIViewParameter) {
UIViewParameter tempViewParam = (UIViewParameter) child;
if (name.equals(tempViewParam.getName())) {
viewParam = tempViewParam;
break;
}
}
}
if (viewParam == null) {
throw new FacesException("Unknown parameter: '" + name + "' for view: " + viewId);
}
// Getting the value
String value = viewParam.getStringValue(ctx); // This seems to ALWAYS be null.
One last thought is that the setter methods still seem to work, setUserId is called with the correct value on post.
Have I completly missunderstood how view parameters work, or is there some kind of bug here? I think my use case should be extremly common and have basic support in the framework.
When i search around, I get the impression that the view parameter should remain after a post thus reading userpage.jsf?userId=123, but this is not the case. What is really the intended behaviour?
This behaviour is correct. The <h:form> generates a HTML <form> element with an action URL without any view parameters. The POST request just submits to exactly that URL. If you intend to keep the view parameters in the URL, then there are basically 3 ways:
Bring in some ajax magic.
<h:commandButton action="#{userPageController.doAction}" value="post">
<f:ajax execute="#form" render="#form" />
</h:commandButton>
This way the initially requested page and thus also the request URL in browser's address bar remains the same all the time.
If applicable (e.g. for page-to-page navigation), make it a GET request and use includeViewParams=true. You can use <h:link> and <h:button> for this:
<h:button outcome="nextview?includeViewParams=true" value="post" />
However, this has an EL security exploit in Mojarra versions older than 2.1.6. Make sure that you're using Mojarra 2.1.6 or newer. See also issue 2247.
Control the generation of action URL of <h:form> yourself. Provide a custom ViewHandler (just extend ViewHandlerWrapper) wherein you do the job in getActionURL().
public String getActionURL(FacesContext context, String viewId) {
String originalActionURL = super.getActionURL(context, viewId);
String newActionURL = includeViewParamsIfNecessary(context, originalActionURL);
return newActionURL;
}
To get it to run, register it in faces-config.xml as follows:
<application>
<view-handler>com.example.YourCustomViewHandler</view-handler>
</application>
This is also what OmniFaces <o:form> is doing. It supports an additional includeViewParams attribute which includes all view parameters in the form's action URL:
<o:form includeViewParams="true">
Update: obtaining the view parameters of the current view programmatically (which is basically your 2nd question) should be done as follows:
Collection<UIViewParameter> viewParams = ViewMetadata.getViewParameters(FacesContext.getCurrentInstance().getViewRoot());
for (UIViewParameter viewParam : viewParams) {
String name = viewParam.getName();
Object value = viewParam.getValue();
// ...
}
Am I right that for solution 1 <f:ajax execute="#form" render="#form" /> to work one would also have to include something like
<f:param name="userId" value="#{userPageController.userId}"/>
inside the <h:commandButton>?
Like it is described in https://stackoverflow.com/a/14026995/811046

Primefaces Datalist Pagination

I'm using a datalist with Ajax pagination and the first page loads fine, showing 5 results. It knows I've got 3 pages worth of results, but when I click through to the second/third pages, I get an empty list.
My model extends LazyDataModel and the first time my page loads, I can set a breakpoint on the load() method and I can see it's asking for results 1-5 which is great. But clicking on 'page 2' doesn't result in another call to the load method (although it does result in about 3 calls to my lazyDataModel field itself (just not the load method inside it).
New to this and despite reading a lot and trying out various things, I can't understand quite how it should work. The showcase example doesn't seem quite 'complete' to me.
Here's the most relevant bits of my code (I think). Sorry about the formatting:
#PostConstruct
public void LoadData() {
lazyModel = new LazyDataModel<MessageboardThread>() {
#Override
public List<MessageboardThread> load(int first, int pageSize, String sortField, SortOrder so, Map<String, String> map) {
List<MessageboardThread> result = new ArrayList<MessageboardThread>();
try {
result = mbDao.findAll(pageSize, first);
} catch (Exception ex) {
ex.printStackTrace();
}
return result;
}
};
getLazyModel().setRowCount(mbDao.count());
getLazyModel().setPageSize(pageSize);
}
This method DOES get executed every time I click on next/prev page
/**
* #return the lazyModel
*/
public LazyDataModel getLazyModel() {
return lazyModel;
}
And this is my xhtml file
<p:dataList value="#{messageboardBean.lazyModel}" var="thread" id="threads"
paginator="true" rows="5" effectSpeed="fast"
paginatorTemplate="{PreviousPageLink} {CurrentPageReport} {NextPageLink} {RowsPerPageDropdown}"
rowsPerPageTemplate="5,10,15" type="none"
paginatorPosition="bottom">
<f:facet name="header">
Conversations
</f:facet>
<p:column>
<h:outputText value="#{thread.title}" style="margin-left:10px" />
<br />
</p:column>
</p:dataList>
I'm stumped so really grateful for any help
PS I put a #PostContruct annotation there because it seemed to be a good place to do the setup but it could be wrong, I haven't seen it on any other examples, but then I couldn't get any other examples to work either.
Solved it. I forgot to wrap the dataList attribute in an h:form tag. It works fine now.

commandbutton not executing action if other attributes are bind to managed bean

i'm really new to jsf.
I need to enable,disable, render or not render some buttons on a page depending on the logged user privileges. The page is bound to a request scoped managed bean , i bind the rendered or disabled attribute of the commandbutton to a "visible" property of the managed bean, and an actionlistener to a managedbean method which has the only purpose to navigate to another page.
If i bind one of the above properties to the visibile property, the navigation method doesn't get called, and the same page gets rerendered with the button disabled or not rendered.
The more urget need is the answer to "how do i disable or hide the button?" of course,
But , since i guess the problem is due to jsf page life cycle and the bean scope, i'd like also to be direct to some tutorial a bit more advanced than the hello world that can be found around.
below are part of the code.
Thank you in advance for any help
the xhtml page
<ui:define name="content">
<p:layoutUnit position="center" header="Dettaglio Pratica" scrollable="true">
<h:form id="formDettaglioPratica">
<!-- i've used javascript to redirect -->
<p:commandButton styleClass="commandButton" value="Modifica pratica" ajax="false"
rendered="#{praticaCtrl.visible}" onclick="navigateToChange();return false;"/>
<p:commandButton id="backButton" styleClass="commandButton" value="Torna alla lista"
onclick="navigateToHome();return false;"></p:commandButton>
<!-- never redirect to page defined in method checkIn -->
<p:commandButton id="checkinButton" styleClass="commandButton" value="Rilascia pratica" ajax="false"
actionListener="#{praticaCtrl.checkIn}" disabled="#{praticaCtrl.visible}"></p:commandButton>
<!-- printing output values -->
<h:inputHidden id="idPratica" value="#{praticaCtrl.idPratica}"></h:inputHidden>
<h:inputHidden id="idBox" value="#{praticaCtrl.box}"></h:inputHidden>
</h:form>
</p:layoutUnit>
</ui:define>
the relevant part of the managed bean
public boolean isVisible() {
return isVisible;
}
public void setVisible(boolean isVisible) {
this.isVisible = isVisible;
}
public void setUser(IUserProvider user) {
try{
if(pratica.getUtenteCheckOut() == null
|| (pratica.getUtenteCheckOut().getMatricola().equalsIgnoreCase(user.getUser().getMatricola()))
)
setVisible(true);
else
setVisible(false);
this.user = user;
}
catch(Exception ex)
{log.error(ex);}
}
public void checkIn(ActionEvent event)
{
try{
log.info(String.format("modifica della pratica %s", idPratica));
ajaxRedirect(String.format("../Box/ListaRichieste.xhtml?box=%s",box));
}
catch(Exception ex)
{
log.error(ex.getMessage(),ex);
}
}
this is the ajaxRedirect method,we don't use the navigation rules neither methods return string
protected void ajaxRedirect(String url) throws IOException {
FacesContext.getCurrentInstance().getExternalContext()
.redirect(url);
}
the answer for the described behaviour can be found at : http://forum.primefaces.org/viewtopic.php?f=3&t=13269&p=39996 ,which refers a BalusC answer on a similar question on stackoverflow : commandButton/commandLink/ajax action/listener method not invoked or input value not updated

Resources