For one of my JSF / primefaces projects, I want to display the elapsed time since a given date - think "this entry has been last edited 3h 5m ago".
At first, I calculated the time interval in the backing bean and let the view poll for it. That meant one ajax call per second and would also break easily - not good.
So I made my first simple JSF composite component for the task. Basically it is just a wrapper around h:outputText: it takes the start date as an attribute and then in Javascript it calculates the time interval to the present date every second and updates the outputText accordingly.
Here's the code:
<?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 < 1) {
minutes = "00";
} else if (minutes < 10) {
minutes = "0" + minutes;
}
if (seconds < 1) {
seconds = "00";
} else if (seconds < 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>
This is working as expected. However, since I was unable to retrieve the start attribute value directly from JS, I let the h:outputText display the date value first and then JS will retrieve it from the rendered HTML and replace it with the elapsed time.
Therefore, although I update the value right away, on some browsers / devices the original date value is briefly visible. The whole thing feels like an ugly workaround to me. And if for some reason I would like to use a second attribute, I wouldn't be able to use it at all, so the approach is clearly limited / broken.
So my question is: Is there a cleaner way to do this, for example by directly accessing attributes (if possible)?
Or ist this simply something you can't do in a composite?
Many thanks!