如何在JSF复合组件中使用Javascript中的属性值?

Toa*_*tor 2 javascript jsf primefaces jsf-2

对于我的 JSF / primefaces 项目之一,我想显示自给定日期以来经过的时间 - 认为“此条目的最后编辑时间是 3 小时 5 分钟前”。

首先,我计算了支持 bean 中的时间间隔,并让视图轮询它。这意味着每秒一次 ajax 调用,而且很容易崩溃 - 不好。

因此,我为该任务制作了第一个简单的 JSF 复合组件。基本上它只是 h:outputText 的包装:它将开始日期作为属性,然后在 Javascript 中每秒计算到当前日期的时间间隔并相应地更新 outputText。

这是代码:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:composite="http://java.sun.com/jsf/composite">

    <composite:interface>

        <composite:attribute name="start" />

    </composite:interface>

    <composite:implementation>

        <div id="#{cc.clientId}" class="elapsedTime">
            <h:outputText id="outTxt" value="#{cc.attrs.start}" />
        </div>

        <script type="text/javascript">
            var outTxt = document.getElementById("#{cc.clientId}:outTxt");
            var a = outTxt.innerHTML.split(/[^0-9]/);
            var baseDate = new Date(a[0], a[1] - 1, a[2], a[3], a[4], a[5]);

            function timeStringFromSeconds(s)
            {
                var hours = Math.floor((s / 86400) * 24);
                var minutes = Math.floor(((s / 3600) % 1) * 60);
                var seconds = Math.round(((s / 60) % 1) * 60);

                if (minutes &lt; 1) {
                    minutes = "00";
                } else if (minutes &lt; 10) {
                    minutes = "0" + minutes;
                }

                if (seconds &lt; 1) {
                    seconds = "00";
                } else if (seconds &lt; 10) {
                    seconds = "0" + minutes;
                }

                return(hours + ":" + minutes + ":" + seconds);
            }

            function update() {
                var currentDate = new Date();
                var elapsed = (currentDate - baseDate) / 1000;
                outTxt.innerHTML = timeStringFromSeconds(elapsed);
            }

            update();
            setInterval(update, 1000);
        </script>

    </composite:implementation>

</html>
Run Code Online (Sandbox Code Playgroud)

这按预期工作。但是,由于我无法直接从 JS 检索开始属性值,因此我让 h:outputText 首先显示日期值,然后 JS 将从渲染的 HTML 中检索它并将其替换为经过的时间。

因此,尽管我立即更新该值,但在某些浏览器/设备上,原始日期值会短暂可见。对我来说,整件事感觉就像是一个丑陋的解决方法。如果出于某种原因我想使用第二个属性,我根本无法使用它,因此该方法显然受到限制/损坏。

所以我的问题是:是否有更干净的方法来做到这一点,例如直接访问属性(如果可能)?

或者这只是您在复合材料中无法做到的事情?

非常感谢!

小智 6

您可以通过仅在第一次遇到组件时渲染函数来避免 JS 函数冲突。如果您愿意将 JS 外部化,您可以将所有内容包含在 xhtml 中。请参阅BalusC的这篇文章

<cc:interface/>

<cc:implementation>
    <h:outputScript target="head" name="js/myJs.js"/>
    <div id="#{cc.clientId}">
        <h:outputText id="output" value="#{cc.attrs.value}" />
        <script>
            update("#{cc.clientId}:output");
            setInterval(update, 1000, "#{cc.clientId}:output");
        </script>
    </div>
</cc:implementation>
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用JavacomponentType类的属性<cc:interface/>,然后使用@FacesComponent(name=...注释来注释 Java 类来完成此操作。在您的 Java 类中,您可以重写encodeBegin以将 JS 写入响应(如果您还没有这样做)。这是复合的

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:cc="http://java.sun.com/jsf/composite">

<cc:interface componentType="jsTestComp" />

<cc:implementation>
    <div id="#{cc.clientId}">
        <h:outputText id="output" value="#{cc.attrs.value}" />
        <script>
            update("#{cc.clientId}:output");
            setInterval(update, 1000, "#{cc.clientId}:output");
        </script>
    </div>
</cc:implementation>
</html>
Run Code Online (Sandbox Code Playgroud)

和Java代码

@FacesComponent(value="jsTestComp")
public class JSTestComp extends UINamingContainer {
    private static final String JS_RENDERED_KEY = "JSTestComp.jsRendered";
    private static final String js = 
        "function update(elementId){\n" +
        "    var outTxt = document.getElementById(elementId);\n" +                  
        "    outTxt.innerHTML = outTxt.innerHTML + \"more\";\n" +
        "}\n";

    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        Map<String, Object> reqMap = FacesContext.getCurrentInstance()
                .getExternalContext().getRequestMap();

        if(reqMap.get(JS_RENDERED_KEY) == null){
            reqMap.put(JS_RENDERED_KEY, Boolean.TRUE);
            ResponseWriter out = FacesContext.getCurrentInstance().getResponseWriter();
            out.startElement("script", null);
            out.write(js);
            out.endElement("script");
        }
        super.encodeBegin(context);
    }
}
Run Code Online (Sandbox Code Playgroud)