2017-04-09 53 views
2

對於我的一個JSF/primefaces項目,我想顯示自給定日期以來的經過時間 - 認爲「這個條目最後編輯了3h 5m前」。如何在Javascript中使用JSF複合組件中的屬性值?

首先,我計算了後臺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> 

這是按預期工作。但是,由於我無法直接從JS檢索start屬性值,因此我讓h:outputText首先顯示日期值,然後JS將從呈現的HTML中檢索它,並用經過的時間替換它。

因此,儘管我立即更新了值,但在某些瀏覽器/設備上,原始日期值只是短暫可見的。整件事對我來說就像是一個醜陋的解決方法。如果由於某種原因,我想使用第二個屬性,我根本無法使用它,所以這種方法明顯受到限制/破壞。

所以我的問題是:是否有更乾淨的方式來做到這一點,例如通過直接訪問屬性(如果可能)?

或者這只是你在複合材料中無法做到的事情?

非常感謝!

+1

首先,這不是'複合'相關的。其次,你是什麼意思_「但是,因爲我無法直接從JS中檢索起始屬性值,」_你的解決方案沒有錯,通常是這樣做的。試着找出'延遲'來自何處。你知道這個:http://timeago.yarp.com/(不需要自己構建它) – Kukeltje

+0

@Kukeltje:謝謝!我的問題是,應該計算傳遞時間的值是通過屬性引入組件的。因爲我無法直接讀取它的值,所以我將outputText組件綁定到屬性,然後從HTML中選取值。在此期間,我發現你可以在Javascript中使用EL - 這是我想要的直接訪問,請參閱我的答案。感謝提到timeago,我會看看它!最好的問候,Toastor – Toastor

回答

4

您可以通過在第一次遇到組件時渲染函數來避免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> 

另外,您可以使用<cc:interface/>componentType屬性,然後標註一個Java類與@FacesComponent(name=...註解做到這一點。在您的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> 

和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); 
    } 
} 
+0

爲什麼不使用'@ ResourceDependency'註釋?這樣,你可以完全外部化js(包括例如timeago和css,如果需要和......)將你的代碼減少10行;-) – Kukeltje

+0

@Kukeltje好點,但是如果你想將它外化,我發現了更清潔的方法http://stackoverflow.com/a/7393741/2780051。我會更新我的答案,包括這一點。 – axemoi

+0

更好的一點...謝謝.. !!! (我真的會將「FacesComponent」解決方案或切換它們(後者更好) – Kukeltje

1

實際上這不是JSF問題。您只需要使用JSF檢索初始值並將其傳遞給腳本。 http://timeago.yarp.com/,而不是重新發明輪子。下面是從演示頁一個簡單的例子:

<time class="timeago" datetime="2008-07-17T09:24:17Z">July 17, 2008</time> 

這將輸出:9 years ago

與axemoi建議的一樣,將該值作爲參數傳遞。例如:

Last modified : <time class="loaded timeago" datetime="#{YourBean.lastEdited}">#{YourBean.lastEdited}</time> 
+1

'傳遞'是(問題的一部分);-)。 **編輯**:AHHHH現在我看到timeago尋找'

+2

這看起來像一個很好的JSF組件。剛剛完成並上傳到Sonatype。 https://github.com/jepsar/Timeago-JSF-Component。隨時在GitHub貢獻。 –

+0

@JasperdeVries太棒了!我可能最終也會使用它! – ForguesR

2

由於ForguesR已經pointed out,你可以只使用timeago jQuery plugin

我拿了插件,把它包裝成JSF component。只需添加這種依賴性:

<dependency> 
    <groupId>com.github.jepsar</groupId> 
    <artifactId>timeago-jsf</artifactId> 
    <version>1.0</version> 
</dependency> 

這個命名空間:

xmlns:ta="http://jepsar.org/timeago" 

現在,您可以通過使用value屬性傳遞java.util.Date使用的組件:

<ta:timeAgo value="#{bean.date}" /> 

jQuery是從PrimeFaces加載, BootsFaces或timeago組件。

根據JSF UIViewRoot#getLocale()自動加載本地化腳本。

+0

Wow !!像魅力一樣工作,非常感謝!這不僅是我的問題的完美解決方案,但也是一個很好的例子,也是一個自定義的jsf組件,然而,對於一個有類似問題但不同用例的人來說,axemois的回答可能會更有幫助,所以我會接受他的回答。這個答案雖然,所以我冒昧給你一些其他答案upvote。再次感謝! – Toastor