Visualforce-native Auto-Refresh Dashboards für Smart-TVs

Es gibt viele Browser-Extensions, die das Aktualisieren von Dashboards übernehmen. Daran hat mich immer gestört, daß viele der erschwinglichen Smart TVs einen nativen Browser mitbringen. Am Ende hängt also doch ein Computer/Laptop/Raspberry an dem TV. Das sind zweimal Anschaffungskosten, Ausfallgefahr, Energieverbrauch, usw. - und das geht mir als Purist nicht in den Kopf. Es geht auch ohne Extension auf jedem Smart-TV Browser, der Javascript beherrscht. Der Trick? Auf Dashboards verzichten. Stattdessen eine Visualforce Page einsetzen.

Visualforce Page mit Analytics

Wir fangen mit einer Visualforce Page an, auf der wir hauptsächlich analytics:reportChart einsetzen. So gut wie alle (s.u.) Charts und Graphen eines Dashboards können auch als ReportChart umgesetzt und mit dem Report/Bericht gespeichert werden. Mittels analytics:reportChart lassen sich diese in eine Visualforce Page einbinden. Einfach analytics:reportChart den Report Developername übergeben. Das sieht dann in etwa so aus:

<apex:page sidebar="false" showHeader="false" title="DashboardRefreshTemplate">  
<style>  
.distributeEvenly {text-align:justify;}
.distributeEvenly:after { content:' '; display:inline-block; width: 100%; height: 0 }
.distributeEvenly > span {display:inline-block} 
<!-- alternatively replace above with this:  
    .distributeEvenly { display: flex; justify-content: space-around; } 
    if there are enough elements on page for a large screen -->
</style>  
    <apex:pageBlock title="Dashboard">
            <apex:form styleClass="distributeEvenly">
            <!--  ADD REPORT CHARTS HERE  -->
            <span><analytics:reportChart hideOnError="false" developerName="leads_created_last_week" size="small"/></span>
            <span><analytics:reportChart hideOnError="false" developerName="leads_created_per_country_this_week" size="small"/></span> 
            <span><analytics:reportChart hideOnError="false" developerName="leads_created_per_country_this_month_V2" size="small"/></span> 
  </apex:form>            
    </apex:pageBlock>
</apex:page>  

Sie enthält minimales CSS, um die einzelnen Elemente gleichmäßig auf der Fläche zu verteilen. Jedes analytics:reportChart Element bringt in der Anzeige einen kleinen Refresh-Button mit, den wir später benutzen werden.

So gut wie fertig. Wenn es da nicht noch die Tachos/Gauges gäbe. Die gibt es nicht als ReportChart. Ich habe es nicht überprüft, vielleicht gibt es weitere Ausnahmen. An diesen Tachos wäre mein Projekt fast gescheitert. Mit etwas mehr Javascript und etwas Salesforce-Hack geht auch das.

Tachos und andere Ausnahmen

Dafür holen wir die Inspektor-Tools (meistens F12) unseres Browsers raus und holen uns den Link zur Tacho-Bilddatei (die noch über cgi geservt wird o_O?) aus einem echten Dashboard heraus. Diesen Link bauen wir als Bild mit apex:image ein und verpassen jedem Bild auf unserer Seite eine eindeutige ID nach einem bestimmten Muster.
Pro-Tip: Durch Manipulation der URL zB &title= läßt sich Titel und auch die Legende der Grafik anpassen

<apex:page sidebar="false" showHeader="false" title="Dashboard">  
<style>  
.distributeEvenly {text-align:justify;}
.distributeEvenly:after { content:' '; display:inline-block; width: 100%; height: 0 }
.distributeEvenly > span {display:inline-block} 
<!-- alternatively replace above with this:  
    .distributeEvenly { display: flex; justify-content: space-around; } 
    if there are enough elements on page for a large screen -->
</style>  
    <apex:pageBlock title="Dashboard">
            <apex:form styleClass="distributeEvenly">
            <!--  ADD REPORT CHARTS HERE  -->
            <span><analytics:reportChart hideOnError="false" developerName="leads_created_last_week" size="small"/></span>
            <span><analytics:reportChart hideOnError="false" developerName="leads_created_per_country_this_week" size="small"/></span> 
            <span><analytics:reportChart hideOnError="false" developerName="leads_created_per_country_this_month_V2" size="small"/></span> 
            <span><apex:image id="gauge1" value="/servlet/servlet.ChartServer?rsid=0FLb0000000jcDI&amp;ruid=005b0000001NOrz&amp;did=01Zb0000000Ptnq&amp;s=8&amp;fs=12&amp;tfg=12&amp;tfs=-16777216&amp;explode=0&amp;c=gauge&amp;cs=0&amp;title=yesterday&amp;eh=no&amp;compo=no&amp;fg=-16777216&amp;bg1=-1&amp;bg2=-1&amp;bgdir=2&amp;dl1=Country+%28text+only%29&amp;dl2=&amp;l=2&amp;sax=yes&amp;Yman=no&amp;nc=0&amp;actUrl=%2F00Ob0000003nNPu%3Fdbw%3D1&amp;sd=1&amp;scv=no&amp;sct=yes&amp;spt=no&amp;bd=yes&amp;cu=EUR&amp;ab=X&amp;u=0&amp;vt=0&amp;ab2=Y&amp;u2=1&amp;vt2=0&amp;vl0=Record+Count&amp;spoc=no&amp;topn=no&amp;gm=0.0&amp;gc0=-4041644&amp;gm0=500.0&amp;gc1=-4013484&amp;gm1=760.0&amp;gc2=-11222444&amp;gm2=1000.0&amp;sona=0"/>
<div style="{height: 300px}">&nbsp;&nbsp;&nbsp;</div></span>  
            <span><apex:image id="gauge2" value="/servlet/servlet.ChartServer?rsid=0FLb0000000jcD8&amp;ruid=005b0000001NOrz&amp;did=01Zb0000000Ptnq&amp;s=8&amp;fs=12&amp;tfg=12&amp;tfs=-16777216&amp;explode=0&amp;c=gauge&amp;cs=0&amp;title=this+week&amp;eh=no&amp;compo=no&amp;fg=-16777216&amp;bg1=-1&amp;bg2=-1&amp;bgdir=2&amp;dl1=Country+%28text+only%29&amp;dl2=&amp;l=2&amp;sax=yes&amp;Yman=no&amp;nc=0&amp;actUrl=%2F00Ob0000003nLV2%3Fdbw%3D1&amp;sd=1&amp;scv=no&amp;sct=yes&amp;spt=no&amp;bd=yes&amp;cu=EUR&amp;ab=X&amp;u=0&amp;vt=0&amp;ab2=Y&amp;u2=1&amp;vt2=0&amp;vl0=Record+Count&amp;spoc=no&amp;topn=no&amp;gm=0.0&amp;gc0=-4041644&amp;gm0=3000.0&amp;gc1=-4013484&amp;gm1=3800.0&amp;gc2=-11222444&amp;gm2=5000.0&amp;sona=0"/>
<div style="{height: 300px}">&nbsp;&nbsp;&nbsp;</div></span>  
            </apex:form>            
    </apex:pageBlock>
</apex:page>  

Auto-Refresh hinzufügen mit Javascript

Um unsere Visualorce Page regelmäßig zu aktualisieren, braucht es einen apex:actionpoller und eine refresh() Javascript-Funktion. Der apex:actionpoller ruft alle 5 Minuten (= 300.000 ms) refresh() auf und übergibt die IDs der Dashboard-Bilder, die aktualisiert werden sollen. refresh() sucht auch alle analytics:ReportChart Refresh-Buttons und klickt diese. Voilà, alle Elemente der Seite aktualisieren mit den neuesten Zahlen.

<apex:actionPoller interval="300000" oncomplete="refresh(['{!$Component.gauge1}', '{!$Component.gauge2}'])"/>  
function refresh(arGaugeIds){  
    var refreshButtons = document.getElementsByClassName("refreshButton");      
    for (var i = 0; i < refreshButtons.length; i++) {
        refreshButtons[i].click();
    }
    for (var i = 0; i < arGaugeIds.length; i++){ 
        var refreshImage = document.getElementById(arGaugeIds[i]);
        refreshImage.src = refreshImage.src + "&refreshts=" + new Date().getTime();
    }
}

Mittels eines Timestamps &refreshts= überlisten wir den Browsercache, so daß der Link auf jeden Fall nachgeladen wird. Das macht Salesforce in den echten Dashboards auch so.

Und hier die fertige VF Page.

<apex:page sidebar="false" showHeader="false" title="Dashboard">  
<script>  
function refresh(arGaugeIds){  
    var refreshButtons = document.getElementsByClassName("refreshButton");      
    for (var i = 0; i < refreshButtons.length; i++) {
        refreshButtons[i].click();
    }
    for (var i = 0; i < arGaugeIds.length; i++){ 
        var refreshImage = document.getElementById(arGaugeIds[i]);
        refreshImage.src = refreshImage.src + "&refreshts=" + new Date().getTime();
    }
}
</script>  
<style>  
.distributeEvenly {text-align:justify;}
.distributeEvenly:after { content:' '; display:inline-block; width: 100%; height: 0 }
.distributeEvenly > span {display:inline-block} 
<!-- alternatively replace above with this:  
    .distributeEvenly { display: flex; justify-content: space-around; } 
    if there are enough elements on page for a large screen -->
</style>  
    <apex:pageBlock title="Dashboard">
            <apex:form styleClass="distributeEvenly">
            <span><analytics:reportChart hideOnError="false" developerName="leads_created_last_week" size="small"/></span>
            <span><analytics:reportChart hideOnError="false" developerName="leads_created_per_country_this_week" size="small"/></span> 
            <span><analytics:reportChart hideOnError="false" developerName="leads_created_per_country_this_month_V2" size="small"/></span> 
            <span><apex:image id="gauge1" value="/servlet/servlet.ChartServer?rsid=0FLb0000000jcDI&amp;ruid=005b0000001NOrz&amp;did=01Zb0000000Ptnq&amp;s=8&amp;fs=12&amp;tfg=12&amp;tfs=-16777216&amp;explode=0&amp;c=gauge&amp;cs=0&amp;title=yesterday&amp;eh=no&amp;compo=no&amp;fg=-16777216&amp;bg1=-1&amp;bg2=-1&amp;bgdir=2&amp;dl1=Country+%28text+only%29&amp;dl2=&amp;l=2&amp;sax=yes&amp;Yman=no&amp;nc=0&amp;actUrl=%2F00Ob0000003nNPu%3Fdbw%3D1&amp;sd=1&amp;scv=no&amp;sct=yes&amp;spt=no&amp;bd=yes&amp;cu=EUR&amp;ab=X&amp;u=0&amp;vt=0&amp;ab2=Y&amp;u2=1&amp;vt2=0&amp;vl0=Record+Count&amp;spoc=no&amp;topn=no&amp;gm=0.0&amp;gc0=-4041644&amp;gm0=500.0&amp;gc1=-4013484&amp;gm1=760.0&amp;gc2=-11222444&amp;gm2=1000.0&amp;sona=0"/>
<div style="{height: 300px}">&nbsp;&nbsp;&nbsp;</div></span>  
            <span><apex:image id="gauge2" value="/servlet/servlet.ChartServer?rsid=0FLb0000000jcD8&amp;ruid=005b0000001NOrz&amp;did=01Zb0000000Ptnq&amp;s=8&amp;fs=12&amp;tfg=12&amp;tfs=-16777216&amp;explode=0&amp;c=gauge&amp;cs=0&amp;title=this+week&amp;eh=no&amp;compo=no&amp;fg=-16777216&amp;bg1=-1&amp;bg2=-1&amp;bgdir=2&amp;dl1=Country+%28text+only%29&amp;dl2=&amp;l=2&amp;sax=yes&amp;Yman=no&amp;nc=0&amp;actUrl=%2F00Ob0000003nLV2%3Fdbw%3D1&amp;sd=1&amp;scv=no&amp;sct=yes&amp;spt=no&amp;bd=yes&amp;cu=EUR&amp;ab=X&amp;u=0&amp;vt=0&amp;ab2=Y&amp;u2=1&amp;vt2=0&amp;vl0=Record+Count&amp;spoc=no&amp;topn=no&amp;gm=0.0&amp;gc0=-4041644&amp;gm0=3000.0&amp;gc1=-4013484&amp;gm1=3800.0&amp;gc2=-11222444&amp;gm2=5000.0&amp;sona=0"/>
<div style="{height: 300px}">&nbsp;&nbsp;&nbsp;</div></span>  
            <span><analytics:reportChart hideOnError="false" developerName="Foyer_widget_in_week" size="small"/></span>
            <apex:actionPoller interval="300000" oncomplete="refresh(['{!$Component.gauge1}', '{!$Component.gauge2}'])"/>
            </apex:form>            
    </apex:pageBlock>
</apex:page>  

Caveats

  • Dashboard anzeigen als / ViewAs wird durch den Nutzer bestimmt, der die VF Page ausführt. Es läßt sich nicht ändern wie auf einem echten Dashboard.
  • Während Änderungen in einem ReportChart sofort übernommen werden, gilt das nicht für die Dashboard-Bilder. Wird ein Dashboard angepaßt (zB die Schwellengrenzen auf einem Tacho), ändert sich die ID zum Bild. Das heißt, die VF Page muß geändert werden. Daher bietet es sich an, die Smart TV Dashboards gesondert von anderen zu pflegen.
  • Das Refreshen zählt gegen die Limits für Dashboard-Refreshs. Daher ist es keine gute Idee das Interval von apex:ActionPoller außer zum Testen zu kurz einzustellen.
Show Comments