0251 / 590 837 15
info@a-coding-project.de

Menü für die Smart TV App

In unserer Smart-TV App werden wir heute das Menü erstellen. Das Besondere daran: Es wird über die Tasten der Fernbedienung gesteuert. Zunächst einmal gibt es zwei neue JavaScript-Dateien. Eine steht für das Menü, die andere für einen einzelnen Eintrag des Menüs.

Neben diesen Dateien habe ich auch Platzhalter für das Logo und das Menü eingebaut – und auch ein wenig aufgeräumt:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title>Hello World</title>

        <script type="text/javascript" language="javascript" 
                src="$MANAGER_WIDGET/Common/API/Widget.js"></script>
        <script type="text/javascript" language="javascript" 
                src="$MANAGER_WIDGET/Common/API/TVKeyValue.js"></script>

        <script language="javascript" type="text/javascript" 
                src="app/javascript/Main.js"></script>
        <script language="javascript" type="text/javascript" 
                src="app/javascript/Menu.js"></script>
        <script language="javascript" type="text/javascript" 
                src="app/javascript/MenuEntry.js"></script>

        <link rel="stylesheet" href="app/stylesheets/Main.css" type="text/css">
    </head>

    <body onload="Main.onLoad();" onunload="Main.onUnload();">
        <div id="sidebar">
            <div id="logo">
            </div>
            <ul id="menu">
            </ul>
        </div>
    </body>
</html>

Das Menü ist wie auf vielen Seiten üblich mit einem ul-Konstrukt aufgebaut. Jeder Menüpunkt wird dann später als li in der Liste auftauchen. Das Befüllen der Einträge wird über JavaScript erledigt, weshalb die index.html auch relativ leer ist. Die Dateipfade werde ich wie im Beispiel fortführen, also großgeschrieben.

Auch in der Main.css hat sich etwas getan. Hier werden nun Sidebar, Logo und Menü positioniert:

*
{
    padding: 0;
    margin: 0;
    border: 0;
}

/* Layout */
body
{
    background-color:#535353;
    color:#fff;
}
#sidebar{
    position:absolute;
    top:0;
    bottom:0;
    left:0;
    width:25%;
    border-right:1px solid #fff;
    padding:15px;
    font-size:1.3em;
    line-height:1.5em;
}
#logo{
    height:150px;
}
#menu{
    list-style-type:none;
}
#menu li{
    padding-left:10px;
}
#menu li.active{
    background-color:#b00000;
}

Die Sidebar wird komplett absolute positioniert. Da wir keine Scrollbars haben möchten, ist dies ohne Probleme möglich. Auch habe ich die active-Klasse erstellt, mit der ich später den aktuell ausgewählten Menüeintrag kennzeichne.

Kommen wir zur MenuEntry.js, die in den app/javascript Ordner kommt. Diese steht für einen einzelnen Menüpunkt:

function MenuEntry(){
    this.control    = false;     //Control
    this.title      = undefined; //Bezeichnung
    this.active     = false;     //Menüpunkt aktiv?
    this.OnActivate = false;     //Callback
    
    /**
     * Liefert das <li>-Element zurück
     */
    this.GetControl = function(){
        if(!this.control){
            //Menüpunkt noch nicht erzeugt, also nachholen
            
            this.control = document.createElement("li");
            this.control.innerText = this.title;
            
            if(this.active){
                //Menüpunkt bereits aktiv, also active-Klasse hinzufügen
                this.control.className = "active";
            }
        }
        
        //Control zurückgeben
        return this.control;
    };
    
    /**
     * Wird aufgerufen, um den Menüpunkt auszuwählen
     */
    this.Activate = function(){
        this.active = true;
        
        //Klasse active hinzufügen, wenn das Control
        //schon erstellt wurde
        if(this.control){
            this.control.className = "active";
        }
        
        //Callback aufrufen, wenn vorhanden.
        if(this.OnActivate){
            this.OnActivate();
        }
    };
    
    /**
     * Wird aufgerufen, um den Menüpunkt abzuwählen
     */
    this.Deactivate = function(){
        this.active = false;
        
        //Klasse wieder zurücksetzen
        if(this.control){
            this.control.className = "";
        }
    };
}

In der Funktion GetControl wird das li-Element erzeugt. Dabei wird die Bezeichnung, die in der Eigenschaft title gespeichert ist, sowie die Klasse active, wenn der Menüpunkt ausgewählt ist, hinzugefügt. Die Funktionen Activate und Deactivate werden aufgerufen, wenn der Menüpunkt aktiv oder inaktiv gestellt werden soll. Wird der Menüpunkt aktiviert, wird auch der Callback OnActivate ausgeführt. Darin kann man später z.B. die Videos der App laden.

Ich habe es übrigens so gemacht, dass Eigenschaften klein und Funktionen groß geschrieben werden. So kann man das ein wenig unterscheiden.

var Menu = {};

/**
 * Funktion wird nach dem laden der Seite aufgerufen
 */
Menu.Load = function(){
    this.entries = new Array();
    
    /**
     * Funktion zum Hinzfügen eines neuen Menüpunkts
     */
    this.AddEntry = function(entry){
        //Damit wir ihn später wiederfinden
        this.entries[this.entries.length] = entry;
        
        //Menüpunkt anzeigen
        this.GetControl().appendChild(entry.GetControl());
    };
    
    /**
     * Liefert das HTML-Element (ul) zurück
     */
    this.GetControl = function(){
        return document.getElementById("menu");
    };
    
    /**
     * Liefert die aktuell ausgewählte Position zurück
     * Auf 0 basierend.
     */
    this.GetPosition = function(){
        for(var i = 0; i < this.entries.length; i++){
            if(this.entries[i].active){
                return i;
            }
        }
    };

    /**
     * Wählt einen Menüpunkt weiter unten aus
     */
    this.MoveDown = function(){
        //Position auslesen
        var pos = this.GetPosition();
        
        //Aktuellen Menüpunkt deaktivieren
        this.entries[pos].Deactivate();
        
        if(pos < this.entries.length-1){
            //Nächsten Punkt auswählen
            this.entries[pos+1].Activate();
        }
        else{
            //Liste bereits durchlaufen,
            //also zum ersten Punkt zurückkehren
            this.entries[0].Activate();            
        }
    };

    /**
     * Wählt einen Menüpunkt weiter oben aus
     */
    this.MoveUp = function(){
        //Position auslesen
        var pos = this.GetPosition();
        
        //Aktuellen Menüpunkt deaktivieren
        this.entries[pos].Deactivate();
        
        if(pos > 0){
            //Vorherigen Menüpunkt aktivieren
            this.entries[pos-1].Activate();
        }
        else{
            //Letzten Punkt in der Liste aktivieren
            this.entries[this.entries.length - 1].Activate();            
        }
    };
    
    /**
     * Fügt einen Dummy-Link hinzu,
     * um die Fernbedienung-Knöpfe abzufangen
     */
    this.AddFocus = function(){
        //Link erstellen
        var a = document.createElement("a");
        a.href = "javascript:void(0);"; //Keine Aktion beim Klick
        a.id = "menu_focus";
        
        //Wird aufgerufen, wenn eine Taste gedrückt wurde
        a.onkeyup = function(){
            var keyCode = event.keyCode;

            switch(keyCode)
            {
                case tvKey.KEY_DOWN:
                    //Pfeil nach unten,
                    //also Menüpunkt weiter unten auswählen
                    Menu.MoveDown();
                    break;
                case tvKey.KEY_UP:
                    //Pfeil nach oben, 
                    //also Menüpunkt weiter oben auswählen
                    Menu.MoveUp();
                    break;
            }
        };
        
        //Dummy-Link hinzufügen
        document.body.appendChild(a);
        //und fokussieren...
        a.focus();
    };
    
    //Neuen Menüpunkt erstellen
    var news = new MenuEntry();
    news.title = "Neuvorstellungen";
    news.OnActivate = function(){
        //Wird aufgerufen, wenn der Menüpunkt
        //aktiv wird
        alert(this.title);
    };
    this.AddEntry(news);
    
    //Weitere Menüpunkte
    var category1 = new MenuEntry();
    category1.title = "Kategorie 1";
    this.AddEntry(category1);
    
    var category2 = new MenuEntry();
    category2.title = "Kategorie 2";
    this.AddEntry(category2);
    
    //Als Default werden die Neuvorstellungen gewählt
    news.Activate();
    
    //Versteckten Fokus hinzufügen
    this.AddFocus();
};

Das ist jetzt die Datei Menu.js. Sie steuert das Menü selbst. Wie im Beispiel am Anfang, nutzen wir hier auch einen Dummy-Link, der die Tastaturschläge abfängt. Mit den Funktionen MoveUp und MoveDown wird der Fokus dann entweder nach oben oder nach unten verschoben. Ist die Liste durchlaufen, wird am anderen Ende weitergemacht.

Wenn der Menüpunkt „Neuvorstellungen“ ausgewählt wird, wird bereits das Callback ausgeführt. Dabei erscheint der Titel „Neuvorstellungen“ in der Konsole. Später zeigen wir hier die ausgewählten Videos an.

Smart TV Menü

Smart TV Menü

Kommentare

Jay schrieb am 14.11.2012:

Hallo, ich kenne mich noch nicht so gut mit App-Programmierung aus und habe den Code von dieser Website als Test übernommen. Jedoch zeigt der Emulator mir beim Ausführen des Codes statt dem Menü nur den Hintergrund und die vertikale Linie an. Die Menüpunkte werden also nicht angezeigt. Woran kann das liegen?

Stefan Wienströer schrieb am 15.11.2012:

Es sieht so aus, als würde das JavaScript nicht geladen werden. Überprüf doch nochmal, ob die Pfade bei dir passen.

Jay schrieb am 16.11.2012:

Hallo Stefan, meinst du die Pfade in der index.html? Die scheinen zu stimmen... Ich habe generell den Code von dieser Website benutzt und in /app/javascript die Dateien Menu.js und MenuEntry.js erzeugt, aber dennoch zeigt der Emulator keine Menüeinträge an. Gruß, Jay

Stefan Wienströer schrieb am 16.11.2012:

Kannst du mir das Ganze mal Zippen und per Mail schicken? Adresse steht hier: http://stevieswebsite.de/impressum.php

Hendrik schrieb am 08.01.2013:

Hey. cooles Tutorial. Bei mir klappt es momentan nicht, da du ja den Link in der Index.html weggelassen hast und man da ja in der Main.js drauf zugreift. Wie hast du die Main.js denn angepasst, so dass das Menu erscheint?

Stefan Wienströer schrieb am 08.01.2013:

Hallo Hendrik, ich weiß leider grade nicht genau, was du mit Link zur index.html meinst. Diese wird beim Starten des Eumlators aufgerufen. Aber ich befürchte da fehlt wirklich der Teil der Main.js. Diese sieht zu dem Zeitpunkt so aus: var widgetAPI = new Common.API.Widget(); var tvKey = new Common.API.TVKeyValue(); var Main = {}; Main.onLoad = function() { widgetAPI.sendReadyEvent(); Menu.Load(); }; Main.onUnload = function() { };

Nicole schrieb am 18.08.2013:

Hallo, erst einmal super Tutorial. Ich habe es mir angeguckt und komme so auch damit zurecht, jetzt möchte ich gern noch zusätzlich eine waagerechte Navigation, also das dann Kategorie 3 in der waagerechten weitergeführt wird. Kannst du mir erklären wie ich dieses machen kann? MfG

Stefan Wienströer schrieb am 19.08.2013:

Hallo Nicole, im Endeffekt wird das in einen der nächsten Artikel erklärt: http://blog.stevieswebsite.de/2012/12/videos-auswaehlen-im-smart-tv/ Hier wählst du in der zweiten Seite ein Video aus. Statt den Video kannst du durch verschiedene Größen auch einfach eine weitere Navigation einfügen

Nicole schrieb am 19.08.2013:

Erst einmal vielen Dank für die schnelle Antwort. Sorry, da muss ich mich etwas schlecht ausgedrückt haben, ich probiere es besser zu erklären. In der Index ist ja folgendes enthalten: Da möchte ich folgendes erweitern: Das ist so gemeint, dass ich dann im Prinzip eine zweite Navigation machen möchte, die waagerecht verläuft, zu der anderen die senkrecht verläuft, dass dann wenn ich auf Kategorie 2 bin und nach unten drücke this.entries[0].Activate(); in die Kategorie 3, komme. Ich hoffe du verstehst mich jetzt besser? Und hoffe du hast dafür vielleicht eine Lösung für mich? Vielleicht auch so gut erklärt, wie oben das Beispiel.:-) MfG

Stefan Wienströer schrieb am 20.08.2013:

Dachte vorher hab ichs verstanden, jetzt bin ich mir nicht mehr sicher^^ Kannst du mir das vielleicht einfach mal aufmalen und per Mail schicken? (info @ stevieswebsite . de) Ein komplett neuer Artikel passt glaube ich von der Relevanz da nicht rein, kann dir dann aber gerne per Mail weiterhelfen.

Zerberus schrieb am 04.09.2013:

Hallo Stefan, auf der Suche nach der Lösung meines Problemes (?) habe ich Dein Tutorial gefunden. Es geht mir um folgendes: auf meinem Samsung-TV spiele ich Musik-Videos im MP4-Format ab. Leider besitzt der interne Media-Player keine Shuffle- bzw. Zufalls-Wiedergabe (auch SKIP wäre nicht schlecht). Ist eine App denkbar, welche folgendes leistet: Ich wähle von der angeschlossenen externen HDD ein Verzeichnis aus, die darin enthaltenen Video-Dateien werden in zufälliger Wiedergabe abgespielt. Ich habe zwar schon etwas Erfahrung in VB-Programmierung, in Java aber bisher nicht. Viele Grüße Zerberus

Stefan Wienströer schrieb am 05.09.2013:

Hallo Zerberus, ich glaube du suchst AllShare: http://www.samsungdforum.com/Guide/d15/index.html (AllShare) Gruß Stefan

Zerberus schrieb am 06.09.2013:

Hallo Stefan, AllShare dient nur dazu, z.B. den PC im Netzwerk als Datenquelle für den TV zu nutzen. Mir geht es aber darum, unabhängig von der Quelle (also z.B. von einer am TV angeschlossenen USB-HDD) die mp4-Dateien in zufälliger Reihenfolge abzuspielen. Bei der mp3-Musik-Wiedergabe bietet das der interne Samsung-Mediaplayer an. Bei der Video-Wiedergabe leider nicht. Gruß Zerberus

Holger Koch schrieb am 29.09.2015:

Hallo Stefan, vielen Dank für das Tutorial. Es bietet wirklich einen guten Einstieg und Basis in die Entwicklung für Samsungs Smart TV. Bitte aktualisiere doch bei Gelegenheit noch die Main.js. Ich habe mich zwei Tage mit dem Fehler TypeError: 'null' is not an object (evaluating 'document.getElementById("anchor").focus') herumgeschlagen, bis ich in den Kommentaren die Lösung gefunden hatte. Möglicherweise geht es noch anderen so und werden dann abgeschreckt. Mit freundlichen Grüßen Holger Koch