目标:我想用自己的行为来丰富预定义的组件。这通常是列表,表格和树木的情况,执行我的操作,例如"删除","添加之前","添加","移动up",...(文字字段似乎很简单......)
我认为必须有一种方法将关键听众附加到组件本身(假设有类似于"焦点"),例如如果我在页面上有两棵树,按下" Ctrl +"将通过listenerA将一次A添加到treeA,并通过listenerB将另一个B添加到treeB。
在树节点或树本身添加ajax侦听器不起作用。因此,似乎有必要(见下面的两个答案)抓住全球关键并且"发送"他们自己正确。至少有一棵树,这应该没有麻烦。
根据以下答案,这只能使用JavaScript或使用非标准JSF标记来完成。
由于我每年最多关注2次JSF问题,我认为有更多参与者可以在JSF和JavaScript之间的这个黄昏区域提供最佳实践的洞察力。
在这个片段中,我想在" +"时创建一个新的子项目。被压了。
<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>
标签
<f:ajax event="keypress" listener="#{demo.doTest}" />
&#34; treeNode&#34>中不接受;和&#34;树&#34;在&#34;形式&#34;。
中没有任何功能=编辑
从答案中可以看出,只需使用<p:hotkey>
即可支持此具体方案。这个解决方案有两个缺点,它的Primefaces绑定,如果我们添加像这样的输入组件
<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>
实施此类事情的最佳做法是什么?至少,在简单的JSF中有可能吗?如果我只使用普通的JSF,那么最难看的成语是什么。
=编辑
我想指出一个简短的调查结果历史,作为下面的答案给出,以提供有关这个问题背后问题的更多细节
答案 0 :(得分:3)
此实现也可以启用导航和添加/删除。
恕我直言,它具有最佳的功能/努力比。
我不知道你对标准JSF标记或普通JSF 的意思,但在这个例子中没有一行JavaScript < /强>
请注意,p:hotkey
组件行为全局。像p:tree
这样的非输入组件不能拥有密钥监听器,因为它们不能“聚焦”(或者至少是默认行为),就像你指出的那样。
但是,这是:
<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>
这是托管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;
}
}
<强>更新强>
也许我找到了一个有趣的解决方案来将键绑定附加到单个DOM元素:
<?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>
三件重要的事情:
h:panelGroup
属性id
是必需的,否则它不会呈现为DOM元素。 style
,styleClass
和其他渲染使能属性可以与之一起使用。pt:tabindex=1
上的p:tree
:需要启用“焦点”。 pt
是用于“passthrough”属性的命名空间,仅适用于JSF 2.2。HotkeyRenderer
才能将DOM事件监听器附加到特定的DOM元素而不是整个文档:现在它是s:hotkey
而不是p:hotkey
。我的实现将它附加到与父组件关联的DOM元素,继续读取以实现。修改后的渲染器:
@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");
}
}
最后这是新s:hotkey
的taglib定义(它是原始文件的复制/粘贴,唯一区别为<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>
哇,这很难;)
答案 1 :(得分:1)
到目前为止,到目前为止还没有真正令人满意的答案。我总结了我的发现:
<p:tree />
甚至不绑定箭头键导航。<p:hotkey/>
。不,你可以(如@ michele-mariotti的广泛答案所示)感觉你的组件有点舒服。<p:inplace />
或只是添加新的树节点。你的热键坏了。答案 2 :(得分:0)
我发现了一种与确切问题无关的解决方法,但可以处理我的方案。
向表单添加“热键”组件会根据请求调用服务器:
<p:hotkey bind="ctrl+shift+a" update="messages" actionListener="#{demo.doTest}"/>
RichFaces中存在类似的组件,不了解普通的JSF。
我无法相信的是,没有其他方法可以回归到JavaScript(如http://winechess.blogspot.ru/2014/02/datatable-keyboard-navigation.html或http://www.openjs.com/scripts/events/keyboard_shortcuts/)来编写可用的JSF应用程序?
树或表等标准组件没有标准的键盘导航(2015年,我甚至不记得发布Web 2.0的时候)。
任何最佳做法提示?
答案 3 :(得分:0)
在更开明的大脑之前进行更多调查可以解除秘密......
有点类似的q / a解决了如果在JS中处理密钥时如何从JS调用后端方法的问题 - 使用
<p:remoteCommand>
请参阅Catch key pressed ajax event without input fields了解丑陋的细节。
同样,这是一个全局关键问题,而不是组件敏感问题。但很高兴知道。这是否也存在于普通的JSF中?