Wiki

Clone wiki

User Apps / HTML-UI / Beispiele / Gaestebuch

HTML-UI Beispiel: Gästebuch

Dieser Artikel erläutert die Verwendung und die Möglichkeiten die das HTML-UI Feature mit sich bringt.

HTML Content definieren

Zu aller erst sollte man das Grundgerüst seiner HTML-Inhalte aufstellen. Der angezeigt Content wird in HTML-Dateien im Unterordner /www der App hinterlegt. Diese sollten wie normale HTML-Dateien auf Webseiten auch das Grundgerüst (<html>, <head> und <body>) beinhalten. Desweiteren müssen wir zwei fixe JavaScript-Dateien von Knuddels importieren. Unsere HTML-Datei könnte also z.B. display.html heißen und wie folgt aufgebaut sein.

/www/display.html

#!html

<html>
    <head>
        <script src="/apps/api/jquery.min.js"></script>
        <script src="/apps/api/knuddels-api.js"></script>
    </head>
    <body>
    </body>
</html>
Da eine leere Seite aber noch nicht so ganz das wahre ist müssen wir sie noch Appspezifisch mit Inhalt füllen. In diesem Beispiel werden wir ein kleines Gästebuch basteln. Dazu legen wir uns einen kleinen Infotext, einen Container und eine Tabelle für die Einträge an.

/www/display.html

#!html
<html>
    <head>
        <script src="/apps/api/jquery.min.js"></script>
        <script src="/apps/api/knuddels-api.js"></script>
    </head>
    <body>
        <p>Willkommen in meinem Channel! Hinterlasse mir doch mit <b>/entry&nbsp;TEXT</b> einen Gästebucheintrag!</p>
        <div id="gbcontainer">
            <table id="gbtable">
                <tr>
                    <th colspan="2">Bisherige G&auml;stebucheintr&auml;ge</th>
                </tr>
                <tr>
                    <td colspan="2" id="noentrys">- keine Eintr&auml;ge vorhanden -</td>
                </tr>
            </table>
        </div>
    </body>
</html>

Dateien importieren

Unsere HTML-Datei sieht zwar im Code ganz nett aus, hat aber zwei Probleme.

  1. Wenn man sie aufruft sieht sie ziemlich trist aus.

  2. Sollte sich an unseren eingebundenen JavaScript-Dateien etwas ändern würden wir aufgrund des Caches (wie im Browser auch) eventuell eine veraltete Dateiversion geladen bekommen.

Hier kommt nun da erste mal die API ins Spiel, genauergesagt das Client-Objekt. Dieses stellt uns die beiden Methoden .includeJS(file) und .includeCSS(file) zur Verfügung und ist durch die Einbindung von knuddels-api.js standardmäßig verfügbar. Besagte Methoden ermöglichen das Einbinden von CSS- und JavaScript-Dateien ohne das Cache-Problem. Mit diesem wissen bauen wir nun zuerst einmal unsere Imports um und werten unser Gästebuch um ein wenig CSS auf, welches wir in der Datei www/style.css auslagern.

Unsere beiden Dateien sehen danach wie folgt aus:

/www/display.html

#!html
<html>
    <head>
        <script src="/apps/api/knuddels-api.js"></script>
        <script>
            Client.includeJS('/apps/api/jquery.min.js');
            Client.includeCSS('style.css');
        </script>
    </head>
    <body>
        <p>Willkommen in meinem Channel! Hinterlasse mir doch mit <b>/entry&nbsp;TEXT</b> einen Gästebucheintrag!</p>
        <div id="gbcontainer">
            <table id="gbtable">
                <tr>
                    <th colspan="2">Bisherige G&auml;stebucheintr&auml;ge</th>
                </tr>
                <tr>
                    <td colspan="2" id="noentrys">- keine Eintr&auml;ge vorhanden -</td>
                </tr>
            </table>
        </div>
    </body>
</html>

/www/style.css

#!css
body {
    background-color: #FFA22F;
}

#gbcontainer {
    background-color: #E8711F;
    padding: 10px;
}

#gbcontainer table {
    width: 100%;
    border: none;
}

#gbcontainer th {
    background-color: #E82C13;
    padding: 3px;
}

#gbcontainer td {
    background-color: #D14411;
    padding: 3px;
}

td #noentrys {
    text-align:center;
}

Das Interface einbinden

Nun geht der spannende Teil los. Wie bei den User Apps üblich benötigt auch unsere Gästebuch-App eine main.js in ihrem Hauptverzeichniss. Bevor wir diese anlegen überlegen wir uns, was wir für den Anfang brauchen. Man muss wissen, dass der angezeigte App-Content nicht pro App, sondern pro User definiert ist. Mit anderen Worten ist es möglich verschiedenen Usern verschiedene Inhalte anzuzeigen. Aus diesem Grund müssen wir jedem User einzeln den Content übermitteln. Um unser Gästebuch standardmäßig anzuzeigen müssen wir es folglich allen Usern die den Channel betreten sowie allen Usern die beim Appstart bereits im Channel sind übermitteln. Wir benötigen daher die beiden Hooks App.onAppStart und App.onUserJoined. Ebenso benötigen wir zwei Chatcommands, nämlich zum Ausblenden der Box für Nutzer die das Gästebuch nicht lesen wollen sowie die in der HTML-Datei angekündigte /entry TEXT-Funktion. Daher werden wir ebenso die Eigenschaft App.chatCommands benötigen. Das Grundgerüst unserer main.js sieht daher wie folgt aus.

/main.js

#!javascript
var App = (new function() {

    this.onAppStart = function() {
        // Aufruf beim Starten der App
    }

    this.onUserJoined = function(user) {
        // Aufruf wenn ein User den Channel betritt
    }

    this.chatCommands = {
        entry: function(user, param, command) {
            // Gästebucheintrag machen
        },

        disablegb: function(user, param, command) {
            // Gästebuch ausblenden
        }
    };

}());

Zuerst einmal müssen wir einige Strukturen definieren. HTML-Dateien werden in der Knuddels-API durch Instanzen der Klasse HTMLFile dargestellt. Dieser übergibt man als Konstruktorargument den Dateipfad sowie einen optionalen Startwert. Ebenso müssen wir einen AppContent definieren, also gewissermaßen eine "Darstellungsart". Wir entscheiden uns für die Overlay-Darstellung und verwenden die statische Methode AppContent.overlayContent(file, width, hight) um einen passenden AppContent zu erhalten. Diese beiden Objekte fügen wir (referenziert) in unser App-Objekt ein - jedoch als Funktion, damit wir neuen User, die während der Laufzeit in den Channel kommen immer den aktuellsten "Startwert" haben - und beziehen die Daten aus dem AppPersistence-Objekt "gb".

Jedes User-Objekt hat eine .setAppContent(appcontent) Methode, mit welchem man dem User einen AppContent übermitteln kann. Diese rufen wir auf jeden betroffenen User auf, damit auch jeder User im Channel die App angezeigt bekommt. Da allerdings nicht alle Clienttypen (zB. die Smartphone-Apps) die Anzeige unterstützen müssen wir vorher mit User.canSendAppContent(mode) überprüfen ob der Content überhaupt dargestellt werden kann. Äquivalent zum Anzeigen gibt es natürlich auch User.removeAppContent() um die Anzeige wieder zu verbergen, was wir uns für unsere /hidegb Funktion zu nutze machen werden.

/main.js

#!javascript
var App = (new function() {

    function getActualContent() {
        if(KnuddelsServer.getPersistence().hasObject("gb")) {
            var data = KnuddelsServer.getPersistence().getObject("gb");
        }
        else {
            var data = [];
        }

        var file = new HTMLFile('display.html', data);
        return AppContent.overlayContent(file, 250, 300);
    }

    function hidegb(user) {
        user.removeAppContent();
        user.sendPrivateMessage("Das Gästebuch wurde ausgeblendet.");
    }

    this.onAppStart = function() {
        var onlineUsers = KnuddelsServer.getChannel().getOnlineUsers(UserType.Human);
        var content = getActualContent();
        for(var i = 0; i < onlineUsers.length; i++) {
            if(onlineUsers[i].canSendAppContent(content)){
                onlineUsers[i].setAppContent(content);
            }
            else {
                onlineUsers[i].sendPrivateMessage("Leider unterstützt dein Client die Anzeige unseres Gästebuchs nicht.");
            }
        }
    }

    this.onUserJoined = function(user) {
        var content = getActualContent();
        if(user.canSendAppContent(content)) {
            user.setAppContent(content);
        }
        else {
            user.sendPrivateMessage("Leider unterstützt dein Client die Anzeige unseres Gästebuchs nicht.");
        }
    }

    this.chatCommands = {
        entry: function(user, param, command) {
            // Gästebucheintrag machen
        },

        disablegb: function(user, param, command) {
            hidegb();
        }
    };

}());
Für unsere /entry-Methode definieren wir nun noch ein wenig Hintergrundlogik, welche nicht direkt mit der HTML-UI zu tun wird, und daher hier nicht erarbeitet sondern nur eingefügt wird.

#!javascript
entry: function(user, param, command) {
            var per = KnuddelsServer.getPersistence();
            if(per.hasObject("gb")) {
                var temp = per.getObject("gb");
                temp.push({user : user, text: param});
                temp.splice(0, temp.length - 10); 
                per.setObject("gb", temp);
            }
            else {
                var temp = [{user : user, text: param}];
                per.setObject("gb", temp);
            }
            user.sendPrivateMessage("Dein Eintrag wurde hinzugefügt.");
        }

Die Standardtdaten im Clienten entgegen nehmen

Nun müssen wir die Standarddaten die wir übergeben haben auch entgegen nehmen. Dazu legen wir uns die Datei /www/gb.js an und binden sie in der Datei /www/display.html auch mit Client.includeJS('gb.js') ein. Da wir das Framework JQuery implementieren werden wir dies auch hier verwenden. Infos zu JQUery findest du hier. Mit Client.pageData haben wir Zugriff auf das Array, welches wir mit übergeben haben. Wir basteln uns nun eine Updatefunktion und rufen diese dann auch direkt einmal auf.

/www/gb.js

#!javascript
function update(data)
{
    $('#gbtable tr').not(":first").remove();
        for(var i in data) {
            $('#gbtable').append("<tr><td>" + data[i].user + "</td><td>" + data[i].text + "</td></tr>");
        }
}

$(function() {
    update(Client.pageData.reverse());
});

Kommunikation Server -> Client

Nur eine statische Seite anzuzeigen und für jeden neuen Eintrag die App neu zu starten oder alle User zu bitten den Raum neu zu betreten ist natürlich nicht Sinn der Sache, wir müssen unsere neuen Einträge natürlich auch an die HTML-Datei übermitteln können. Dazu dient die Methode User.sendAppEvent(key, data) und ihr zugehöriger Empfänger in der HTML-Datei. Wir verpacken daher die zu übermittelnden Daten in ein Objekt und versenden es nach jedem neuen Eintrag an alle Clienten. In unserer HTML-Datei brauchen wir nun noch JavaScript-Code um die Datenpakete entgegenzunehmen zu zu verarbeiten. Dazu fangen wir in unserer /www/gb,js die Daten mit document.addEventListener('eventReceived', function(event){ /* UNSER CODE */}) ab und updaten den Inhalt unserer Tabelle. Unsere Dateien sehen dann wie folgt aus.

main.js

#!javascript
var App = (new function() {

    function getActualContent() {
        if(KnuddelsServer.getPersistence().hasObject("gb")) {
            var data = KnuddelsServer.getPersistence().getObject("gb");
        }
        else {
            var data = [];
        }

        var file = new HTMLFile('display.html', data);
        return AppContent.overlayContent(file, 250, 300);
    }

function hidegb(user) {
        user.removeAppContent();
        user.sendPrivateMessage("Das Gästebuch wurde ausgeblendet.");
    }


    this.onAppStart = function() {
        var onlineUsers = KnuddelsServer.getChannel().getOnlineUsers(UserType.Human);
        var content = getActualContent();
        for(var i = 0; i < onlineUsers.length; i++) {
            if(onlineUsers[i].canSendAppContent(content)){
                onlineUsers[i].setAppContent(content);
            }
            else {
                onlineUsers[i].sendPrivateMessage("Leider unterstützt dein Client die Anzeige unseres Gästebuchs nicht.");
            }
        }
    }

    this.onUserJoined = function(user) {
        var content = getActualContent();
        if(user.canSendAppContent(content)) {
            user.setAppContent(content);
        }
        else {
            user.sendPrivateMessage("Leider unterstützt dein Client die Anzeige unseres Gästebuchs nicht.");
        }
    }

    this.chatCommands = {
        entry: function(user, param, command) {
            var per = KnuddelsServer.getPersistence();
            if(per.hasObject("gb")) {
                var temp = per.getObject("gb");
                temp.push({user : user.getNick(), text: param});
                temp.splice(0, temp.length - 10);
                per.setObject("gb", temp);
            }
            else {
                var temp = [{user : user.getNick(), text: param}];
                per.setObject("gb", temp);
            }

            var onlineUsers = KnuddelsServer.getChannel().getOnlineUsers(UserType.Human);

            for(var i in onlineUsers) {
                var content = getActualContent();
                if(onlineUsers[i].canSendAppContent(content)){
                    onlineUsers[i].sendAppEvent("update", temp);
                }
            }
            user.sendPrivateMessage("Dein Eintrag wurde hinzugefügt.");
        },

        disablegb: function(user, param, command) {
            hidegb(user);
        }
    };

}());
/www/gb.js
#!javascript
function update(data)
{
    $('#gbtable tr').not(":first").remove();
        for(var i in data) {
            $('#gbtable').append("<tr><td>" + data[i].user.val() + "</td><td>" + data[i].text.val() + "</td></tr>");
        }
}

$(function() {
    update(Client.pageData.reverse());
});

document.addEventListener('eventReceived', function(event){
    var key = event.eventKey;
    var data = event.eventData;

    if(key == "update") {
        update(data.reverse());
    }
    else {
        $("body").html("Unbekannter Key empfangen: " + key);
    }

 });

Kommunikation Client -> Server

Nun möchten wir dem User natürlich noch ein wenig Komfort bieten. Dazu binden wir ein [X] oben rechts in der Ecke ein und bieten zudem die Möglichkeit an, Gästebucheinträge im HTML-UI selbst zu verfassen. Desweiteren wollen wir die Namen mit /w NICK verknüpfen. Wir passen zuerst einmal unsere display.html um diese beiden Elemente an und ergänzen unsere /www/style.css um einige Zeilen.

/www/display.html

#!html
<html>
    <head>
        <script src="/apps/api/knuddels-api.js"></script>
        <script>
            Client.includeJS('/apps/api/jquery.min.js');
            Client.includeJS('gb.js');
            Client.includeCSS('style.css');
        </script>
    </head>
    <body>
        <div id="close">[X]</div>
        <p>Willkommen in meinem Channel! Hinterlasse mir doch mit <b>/entry&nbsp;TEXT</b> einen Gästebucheintrag!</p>
        <div id="gbcontainer">
            <textarea id="entrytext" rows="3"></textarea>
            <button id="entrysubmit">eintragen</button>
            <table id="gbtable">
                <tr>
                    <th colspan="2">Neuste G&auml;stebucheintr&auml;ge</th>
                </tr>
                <tr>
                    <td colspan="2" id="noentrys">- keine Eintr&auml;ge vorhanden -</td>
                </tr>
            </table>
        </div>
    </body>
</html>
/www/style.css

#!css
[...]
#entrytext {
    width: 100%
}
#entrysubmit {
    width:100%;
    margin-bottom: 2px;
}

#close {
    float:right;
    color: red;
    font-weight: bold;
}

Nun definieren wir die Aktionen. In unserem Beispiel möchten wir dass ein Klick auf den Button im Chat die Funktion /entry mit dem Wert des Textfeldes aufruft und dass ein Klick auf das [X] unsere Funktion hidegb() ausführt. Dafür verwenden wir zwei weitere Methoden des Objektes Client, nämlich Client.executeSlashCommand(command), welches den User einen Befehl ausführen lässt und Client.sendEvent(key, data) welches Daten an den Server schickt (analog zu User.sendEvent(key, data)). Diese binden wir mittels JQuery und haben dann die folgende /www/gb.js Datei.

/www/gb.js

#!javascript
function update(data)
{
    $('#gbtable tr').not(":first").remove();
        for(var i in data) {
            $('#gbtable').append("<tr><td class=\"namefield\">" + data[i].user + "</td><td>" + data[i].text + "</td></tr>");
        }
     $(".namefield").click(function() {
         Client.executeSlashCommand("/w " + $(this).text());
     });
}

$(function() {
    update(Client.pageData.reverse());

    $("#entrysubmit").click(function() {
        Client.sendEvent("newentry", $("#entrytext").val());
        $("#entrytext").val("");
    });

    $("#close").click(function() {
        Client.sendEvent("close");
    });

});

document.addEventListener('eventReceived', function(event){
    var key = event.eventKey;
    var data = event.eventData;

    if(key == "update") {
        update(data.reverse());
    }
    else {
        $("body").html("Unbekannter Key empfangen: " + key);
    }

 });

Nun müssen wir das ganze noch in der App entgegen nehmen. Dazu gibt es den onEventReceived Hook im App-Objekt. Im Kontext unserer App würden wir also wie folgt dort agieren.

/main.js

#!javascript
[...]
//Verweis auf den AppContainer
    var appinstance = this;
    this.onEventReceived = function(user, key, data) {
        if(key == "newentry") {
            //Den /entry-Befehl Simulieren
            appinstance.chatCommands.entry(user, data, "entry");
        }
        else if (key == "close") {
            hidegb(user);
        }
        else {
            user.sendPrivateMessage("Unbekannter Key: " + key);
        }
    }
[...]

Updated