我有这个复合组件:
inputMask.xhtml
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<composite:interface>
<composite:attribute name="value" />
<composite:attribute name="mask" type="java.lang.String" required="true" />
<composite:attribute name="converterId" type="java.lang.String" default="br.edu.ufca.eventos.visao.inputmask.inputMask" />
</composite:interface>
<composite:implementation>
<h:outputScript library="script" name="inputmask.js" target="head" />
<h:inputText id="mascara">
<c:if test="#{cc.getValueExpression('value') != null}">
<f:attribute name="value" value="#{cc.attrs.value}" />
</c:if>
<f:converter converterId="#{cc.attrs.converterId}" />
<f:attribute name="mask" value="#{cc.attrs.mask}" />
</h:inputText>
<h:outputScript target="body">
defineMask("#{cc.clientId}", "#{cc.attrs.mask}");
</h:outputScript>
</composite:implementation>
</html>
最后一个问题:
尝试以编程方式添加复合组件时出错("没有为name"定义标签)
我得到这个错误:
javax.faces.view.facelets.TagException: //C:/wildfly-10/standalone/tmp/eventos.ear.visao.war/mojarra7308315477323852505.tmp @2,127 <j:inputMask.xhtml> Tag Library supports namespace: http://xmlns.jcp.org/jsf/composite/componente, but no tag was defined for name: inputMask.xhtml
当尝试用以下代码以编程方式添加上述复合组件时:
Map<String, String> attributes = new HashMap<>();
attributes.put("mask", "999.999");
Components.includeCompositeComponent(Components.getCurrentForm(), "componente", "inputMask.xhtml", "a123", attributes);
但是我设法用这种方法解决了这个问题:
方法Components# inclecompositecomponent 从OmniFaces 2.4(我正在使用的版本)的实现是这样的:
public static UIComponent includeCompositeComponent(UIComponent parent, String libraryName, String tagName, String id, Map<String, String> attributes) {
String taglibURI = "http://xmlns.jcp.org/jsf/composite/" + libraryName;
Map<String, Object> attrs = (attributes == null) ? null : new HashMap<String, Object>(attributes);
FacesContext context = FacesContext.getCurrentInstance();
UIComponent composite = context.getApplication().getViewHandler()
.getViewDeclarationLanguage(context, context.getViewRoot().getViewId())
.createComponent(context, taglibURI, tagName, attrs);
composite.setId(id);
parent.getChildren().add(composite);
return composite;
}
所以我决定尝试从OmniFaces的早期版本的代码(与一些变化添加属性参数从我)的这个方法:
public static UIComponent includeCompositeComponent(UIComponent parent, String libraryName, String resourceName, String id, Map<String, String> attributes) {
// Prepare.
FacesContext context = FacesContext.getCurrentInstance();
Application application = context.getApplication();
FaceletContext faceletContext = (FaceletContext) context.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY);
// This basically creates <ui:component> based on <composite:interface>.
Resource resource = application.getResourceHandler().createResource(resourceName, libraryName);
UIComponent composite = application.createComponent(context, resource);
composite.setId(id); // Mandatory for the case composite is part of UIForm! Otherwise JSF can't find inputs.
// This basically creates <composite:implementation>.
UIComponent implementation = application.createComponent(UIPanel.COMPONENT_TYPE);
implementation.setRendererType("javax.faces.Group");
composite.getFacets().put(UIComponent.COMPOSITE_FACET_NAME, implementation);
if (!attributes.isEmpty()) {
ExpressionFactory factory = application.getExpressionFactory();
ELContext ctx = context.getELContext();
for (Map.Entry<String, String> entry : attributes.entrySet()) {
ValueExpression expr = factory.createValueExpression(ctx, entry.getValue(), Object.class);
composite.setValueExpression(entry.getKey(), expr);
}
}
// Now include the composite component file in the given parent.
parent.getChildren().add(composite);
parent.pushComponentToEL(context, composite); // This makes #{cc} available.
try {
faceletContext.includeFacelet(implementation, resource.getURL());
} catch (IOException e) {
throw new FacesException(e);
} finally {
parent.popComponentFromEL(context);
}
return composite;
}
最后错误消失了。组合组件被动态地添加到页面中。
但是另一个问题出现了。
按钮中添加组件的操作大致如下:
if (Components.findComponent("form:a123") == null)
{
Map<String, String> attributes = new HashMap<>();
attributes.put("value", "#{bean.cpf}");
attributes.put("mask", "999.999.999-99");
includeCompositeComponent(Components.getCurrentForm(), "componente", "inputMask.xhtml", "a123", attributes);
}
可以看到,复合组件只添加了一次。
第一次添加组件时,组件中的脚本代码:
<h:outputScript target="body">
defineMask("#{cc.clientId}", "#{cc.attrs.mask}");
</h:outputScript>
被添加到页面中。当我在浏览器中可视化html源代码时,我可以看到它。但是在回发时,不再呈现此脚本代码。它不在生成的html页面中。每次都呈现带有target="head"
的<h:outputScript>
,如预期的那样,但不是这个。
从我的角度来看,也许在上面的方法中组装复合组件代码来修复脚本代码,甚至在页面上的回发时,仍然缺少一些东西。我真的不知道。这只是一个猜测。
你知道发生了什么事或者少了什么吗?
---- UPDATE 1 ----
我想我确实找到了问题的根源。这似乎是JSF中的一个bug,与以编程方式包含的组合组件中的脚本有关。结果如下:
我注意到OmniFaces包含我的复合组件的正确代码是:
Components.includeCompositeComponent(Components.getCurrentForm(), "componente", "inputMask", "a123", attributes);
正确的是"inputMask"
,而不是"inputMask.xhtml"
。但正如我之前告诉过你的,当我使用这段代码时,我得到了这个错误:
Caused by: javax.faces.FacesException: Cannot remove the same component twice: form:a123:j_idt2
所以我怀疑id 形式的组件:a123:j_idt2是复合组件中存在的h:outputScript之一。因此,我将复合组件代码更改为:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<composite:interface componentType="inputMask">
<composite:attribute name="value" />
<composite:attribute name="mask" type="java.lang.String" required="true" />
<composite:attribute name="converterId" type="java.lang.String" default="br.edu.ufca.eventos.visao.inputmask.inputMask" />
</composite:interface>
<composite:implementation>
<h:inputText id="mascara">
<c:if test="#{cc.getValueExpression('value') != null}">
<f:attribute name="value" value="#{cc.attrs.value}" />
</c:if>
<f:converter converterId="#{cc.attrs.converterId}" />
<f:attribute name="mask" value="#{cc.attrs.mask}" />
</h:inputText>
<script type="text/javascript">
defineMask("#{cc.clientId}", "#{cc.attrs.mask}");
</script>
</composite:implementation>
</html>
删除对h:outputScript标签的所有引用。(当然,我将inputmask.js脚本放在复合组件之外,以便组件继续工作)。
现在,当我运行代码时,组件最终被添加到页面中,没有错误。但是,正如我在前面提到的OmniFaces早期版本的代码一样,该脚本仍然没有在回发中呈现。JSF只在添加组件时呈现它,在回发时丢失它。我知道这不是一个期望的行为。
所以,我问你:你知道我怎么解决这个脚本问题吗?或者至少在这种情况下我可以使用的变通方法?
提前谢谢你。
---- UPDATE 2 ----
我找到了一个解决方法。我在复合组件的后台组件中这样做了,它工作了,脚本总是呈现:
@Override
public void encodeEnd(FacesContext context) throws IOException
{
super.encodeEnd(context);
ResponseWriter writer = context.getResponseWriter();
writer.startElement("script", this);
writer.writeText(String.format("defineMask('%s', '%s');",
getClientId(), getAttributes().get("mask")), null);
writer.endElement("script");
}
,但它有点丑,似乎没有必要。同样,如果组件没有以编程方式包含,则不需要支持组件。这似乎是JSF中的一个bug。你们有人能测试并确认一下吗?我的意思是,测试一个以编程方式添加脚本的复合组件是否会在回发时丢失脚本。
注:: 我用 OmniFaces 2.4 和银鲈2.2.13 。
解决方案(工作区)是从组合组件中删除所有脚本,并为其创建一个后备组件,以精确地执行JSF应该执行的操作:
package br.edu.company.project.view.inputmask;
import java.io.IOException;
import java.util.Map;
import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIInput;
import javax.faces.component.UINamingContainer;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.omnifaces.util.FacesLocal;
@FacesComponent("inputMask")
public class InputMask extends UIInput implements NamingContainer
{
private static final String SCRIPT_FILE_WRITTEN =
"br.edu.company.project.SCRIPT_FILE_WRITTEN";
@Override
public String getFamily()
{
return UINamingContainer.COMPONENT_FAMILY;
}
@Override
public void encodeBegin(FacesContext context) throws IOException
{
writeScriptFileIfNotWrittenYet(context);
super.encodeBegin(context);
}
@Override
public void encodeEnd(FacesContext context) throws IOException
{
super.encodeEnd(context);
writeMaskDefinition(context);
}
private void writeScriptFileIfNotWrittenYet(FacesContext context) throws IOException
{
if (FacesLocal.getRequestMap(context).putIfAbsent(
SCRIPT_FILE_WRITTEN, true) == null)
{
writeScript(context, w -> w.writeAttribute(
"src", "resources/script/inputmask.js", null));
}
}
private void writeMaskDefinition(FacesContext context) throws IOException
{
writeScript(context, w -> w.writeText(String.format(
"defineMask('%s', '%s');", getClientId(),
getAttributes().get("mask")), null));
}
private void writeScript(FacesContext context, WriteAction writeAction)
throws IOException
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement("script", this);
writer.writeAttribute("type", "text/javascript", null);
writeAction.execute(writer);
writer.endElement("script");
}
@FunctionalInterface
private static interface WriteAction
{
void execute(ResponseWriter writer) throws IOException;
}
}
同样,如果复合组件不以编程方式包含,则不需要使用。在这种情况下,JSF按预期工作,您不需要支持组件。
如果有人有时间,我想最好向Mojarra团队提交一个bug报告。