Praktikum BKSPP: Blatt 4 PD Dr. David Sabel WS 2014/15 Einleitung Yesod Yesod Web Framework http://www.yesodweb.com Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 2/22 Einleitung Webframefork allgemein Software für die Entwicklung von dynamischen Webseiten Vorgefertigter Code für wesentliche Systemteile und wiederkehrende Programmierarbeiten Templates (Vorlagen) zur Generierung der Webseiten Datenbankanbindung Trennung von Datenmodell, Repräsentation und Steuerung (Model View Controller) Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 3/22 Einleitung Yesod Webframefork in Haskell programmiert benutzt viele Erweiterungen des Typsystems Typinferenz funktioniert dabei nicht immer, d.h. Typen selbst angeben benutzt Template Haskell: Programmtemplates, erst beim Compilieren wird der echte Haskell-Code erstellt verwendet Cabal um den Webservice zu kompilieren Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 4/22 Einleitung Aufgabenblatt Zwei Teile: Einführung in Yesod: Installation und kurzes Tutorial über die wichtigsten Features Danach Aufgaben zum Programmieren eines Quiz Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 5/22 Einleitung Ziel: Quiz und Administrationsmöglichkeiten Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 6/22 Einleitung Erste Schritte Installation von Yesod (siehe Anleitung) Neues Yesod-Projekt anlegen: yesod init Projektname eingeben und Sqllite als Datenbank auswählen Projekt kompilieren (siehe Anleitung) Entwicklungsmodus starten yesod devel Im Browser http://localhost:3000 aufrufen Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 7/22 Einleitung Data.Text statt String verwenden In Foundation.hs die Zeile import Data.Text einfügen. In anderen Modulen qualifiziert importieren: import qualified Data.Text as T pack :: String -> Text unpack :: Text -> String Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 8/22 Einleitung Wesentliche Dateien im Projekt Datei config/routes: Routen URL nach Code (Handler) Verzeichnis Handler/: Haskell-Module für die Handler Verzeichnis templates/: HTML und CSS Templates Verzeichnis config/models: Datenmodelle für die Datenbank Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 9/22 Einleitung Eine erste Seite Quiz/> yesod add-handler Name of route (without trailing R): Echo Enter route pattern (ex: /entry/#EntryId): /echo/#Text Enter space-separated list of methods (ex: GET POST): GET Yesod legt dadurch an: /echo/#Text EchoR GET in /config/routes Bedeutet: GET-Anfragen an die URL /echo/irgendeinText werden dem Handler EchoR behandelt Modul Handler.Echo, dort muss die Funktion getEchoR implementiert werden Importe des neuen Moduls, Eintrag in Quiz.cabal Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 10/22 Einleitung Implementierung in Handler.Echo Default (durch Yesod angelegt) module Handler.Echo where import Import getEchoR :: Text -> Handler Html getEchoR = error "Not yet implemented: getEchoR" Text ist der mit der URL übergebene Text Handler ist eine Monade (genauer ein MonadTransformer), IO-Aktionen können mit liftIO in der Handler-Monade ausgeführt werden z.B. do ... liftIO (putStrLn "Hallo") zufallszahl <- liftIO randomIO ... Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 11/22 Einleitung Implementierung in Handler.Echo Eigene Implementierung module Handler.Echo where import Import getEchoR :: Text -> Handler Html getEchoR txt = defaultLayout [whamlet|<h1>#{txt}|] defaultLayout: erzeugt Webseite im Standardlayout [whamlet|<h1>#{txt}]: Hamlet-Widget, dass in die Seite eingefügt wird, erzeugt: <h1>irgendeinText</h1> wenn txt gerade "irgendeinText" ist Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 12/22 Einleitung Hamlet-Templates per Datei einbinden Statt getEchoR :: Text -> Handler Html getEchoR txt = defaultLayout [whamlet|<h1>#{txt}|] besserer Stil: getEchoR :: Text -> Handler Html getEchoR txt = defaultLayout $(widgetFile "echo") und in /templates/echo.hamlet: <h1>#{txt} Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 13/22 Einleitung Hamlet-Templates Syntax und Features Statt öffnenden und schließenden HTML-Tags, nur öffende: <h1>Meine Überschrift! statt <h1>Meine Überschrift</h1> Einrückung ist wichtig: <ul> <li>Erstes Listenelement <li>Zweites Listenelement <li>Drittes Listenelement ergibt <ul> <li>Erstes Listenelement</li> <li>Zweites Listenelement</li> <li>Drittes Listenelement</li> </ul> ergibt <ul> <li>Erstes Listenelement</li> <li>Zweites Listenelement</li> </ul> <li>Drittes Listenelement</li> aber: <ul> <li>Erstes Listenelement <li>Zweites Listenelement <li>Drittes Listenelement Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 14/22 Einleitung Hamlet-Templates Syntax und Features (2) (mit import qualified Data.Text as T in Handler.Echo) echo.hamlet mit mehr Features: <h1> Der eingegebene Text ist #{txt} <ul> <li> Der Text hat #{T.length txt} Zeichen <li> und lautet in Gro&szlig;buchstaben: #{T.toUpper txt} <p> Ein neuer Absatz. Dieser Text ist im gleichen Absatz Dieser Text nicht mehr, da Hamlet Einr&uuml;ckungen ernst nimmt. <p> <a href=@{EchoR "EinAndererText"}> Ein Link mit anderem Text #{ code } führt den code aus und setzt das Ergebnis im Template ein. Funktionen und Namen die im Scope von $(widgetFile "echo") stehen, können im Template verwendet werden Mit @{ route } kann man innerhalb des Webframeworks verlinken! Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 15/22 Einleitung Hamlet-Templates Syntax und Features (3) if-then-else: $if Haskell-Code HTML-Code1 $else HTML-Code2 Je nachdem ob Haskell-Code zu True oder False auswertet, wird HTML-Code1 oder HTML-Code2 in die Seite eingefügt. Z.B. <h1> Der eingegebene Text ist #{txt} <p> $if T.length txt > 20 der eingegebe Text ist sehr lang $else der eingebene Text ist eher kurz Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 16/22 Einleitung Hamlet-Templates Syntax und Features (4) Iterieren über eine Liste $forall x <- xs: Z.B. <h1> Der eingegebene Text ist #{txt} <ul> $forall x <- T.unpack txt <li> #{x} Erzeugt alle Buchstaben in der Liste als Items. Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 17/22 Einleitung Hamlet-Templates Syntax und Features (5) Cascading Style Sheets: Spezialsyntax statt class="name" kann .name verwendet werden statt id="name" kann #name verwendet werden Stylesheet: Lucius-Templates echo.hamlet wird automatisch mit echo.lucius verknüpft. Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 18/22 Einleitung Hamlet und Lucius Beispiel: echo.hamlet enthält: <h1> Der eingebene Text ist <span .red> #{txt} echo.lucius enthält: .red { color:red; background-color:black; } Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 19/22 Einleitung Hamlet, Lucius und Julius Es gibt noch Julius-Templates für JavaScript. Doku: siehe http://www.yesodweb.com/book/shakespearean-templates Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 20/22 Einleitung Formulare und POST-Requests Beispiel: Neue Route: yesod add-handler Name of route (without trailing R): Hallo Enter route pattern (ex: /entry/#EntryId): /hallo Enter space-separated list of methods (ex: GET POST): GET POST In Handler.Hallo sind zu implementieren: getHalloR für GET-Anfragen postHalloR für POST-Anfragen Beispiel Formular: bei GET wird das Formular angezeigt, welches als POST-Request abgeschickt wird bei POST werden die übermittelten Formulardaten ausgelesen und verarbeitet Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 21/22 Einleitung Formulare und POST-Requests (2) getHalloR = do defaultLayout [whamlet|<form method=post action=@{HalloR}> <h1>Formular <p> Dein Vorname: <input type=text name=Vorname> <p> Dein Nachname: <input type=text name=Nachname> <p> <input type=submit> <form>-Tag enthält Methode POSTund das Ziel (wieder der Handler HalloR) Zwei Eingabefelder für Vor- und Nachnamen Submit-Button zum Absenden des Formulars Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 22/22 Einleitung POST-Request – Auslesen der Daten postHalloR muss die erhaltenen Formulardaten einlesen und verarbeiten. runInputPost als Hauptfunktion, Funktionen ireq und iopt zum Parsen ireq zum Parsen eines erforderlichen Eintrags iopt zum Parsen eines optionalen Eintrags (Ergebnis im Maybe-Typ verpackt) Argumente: ein Feldtyp und Name des Felds Feldtypen: textField und z.B. intField Beispiel: postHalloR = do v <- runInputPost (ireq textField "Vorname") defaultLayout [whamlet|<p> Hallo #{v} |] Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 23/22 Einleitung POST-Request – Auslesen der Daten (2) Mehrere Daten auslesen mit dem Applicative Interface pure :: a -> m a <*> :: m (a -> b) -> m a -> m b <$> :: (a -> b) -> m a -> m b f n-stellige pure Funktion, dann ist f <$> a1 <*> ... <*> an die sequentielle Anwendung: Werte a1 bis an aus und wende die Ergebnisse auf f an. postHalloR :: Handler Html postHalloR = do (v,n) <- runInputPost (pair <$> (ireq textField "Vorname") <*> (ireq textField "Nachname")) defaultLayout [whamlet|<p> Hallo #{v} #{n} |] where pair a b = (a,b) Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 24/22 Einleitung Datenbank-Anbindung Yesod verwendet das sogenannte Persistent-Interface Da wir sqllite ausgewählt haben, sind die Daten lokal in einer Datei (Quiz.sqlite3) abgelegt Datenmodelle sind in config/models Beispiel: Person vorname Text nachname Text Legt Tabelle Person mit zwei Attributen vom Typ Text an Automatisch: Schlüssel namens PersonId Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 25/22 Einleitung Beispiel (Forts.) Person vorname Text nachname Text Auf der Haskell-Ebene wird dadurch (ungefähr) ein Datentyp angelegt: data Person = Person { personVorname :: Text , personNachname :: Text } Groß-/Kleinschreibung beachten! Außerdem ein Typ PersonId Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 26/22 Einleitung Datenbankzugriff Wesentliche Funktionen: runDB zum Ausführen der Abfrage get: holt Datenbankeintrag anhand seines Schlüssels (Rückgabe mit Maybe verpackt) getPerson :: PersonId -> Handler (Maybe Person) getPerson personid = runDB (get personid) insert: Eintrag einfügen, Id wird als Rückgabe erzeugt insertPerson :: Person -> Handler (PersonId) insertPerson p = runDB (insert p) delete: Löschen anhand der Id deletePersonById :: PersonId -> Handler () deletePersonById pid = runDB (delete pid) update: aktualisieren eines Eintrags. Argumente: Id und Liste von Aktualisierungen der Form TypnameAttribut =. neuerWert updateVorname :: PersonId -> Text -> Handler () updateVorname pid neuerName = runDB (update pid [PersonVorname =. neuerName]) Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 27/22 Einleitung Datenbankzugriff (2) selectList filter selectopts: alle Einträge als Liste, wobei mit filter nur bestimmte Einträge und mit selectopts die Ausgabe bearbeitet werden kann (sortieren etc.) selectList [] [] ergibt alle Einträge getAllPersonsDB :: Handler ([Entity Person]) getAllPersonsDB = runDB (selectList [] []) Rückgabe: Liste von Entity Person: data Entity Person = Entity PersonId Person Beispiele: getAllPersonIds :: Handler ([PersonId]) getAllPersonIds = do list <- getAllPersonsDB return (map (\(Entity pid person) -> pid) list) getAllPersons :: Handler ([Person]) getAllPersons = do list <- getAllPersonsDB return (map (\(Entity pid person) -> person) list) Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 28/22 Einleitung Beispiel: Namen speichern und Anzeigen postHalloR :: Handler Html postHalloR = do (v,n) <- runInputPost (pair <$> (ireq textField "Vorname") <*> (ireq textField "Nachname")) insertPerson (Person {personVorname = v, personNachname = n}) defaultLayout [whamlet|<p> Hallo #{v} #{n} |] where pair a b = (a,b) Route PersonsR mit der URL /persons und der GET-Methode getPersonsR :: Handler Html getPersonsR = do personen <- getAllPersons defaultLayout [whamlet|<table> $forall x <- personen <tr> <td>#{personVorname x} <td>#{personNachname x} |] Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 29/22 Einleitung Schlüssel type PersonId = Key Person Key ist vordefinierter Phantom-Typ data Key a = Key {unKey = PersistValue} PersistValue ist Datentyp für persistente Werte, im Beispiel PersistInt64 i Beispiel intToPersonId :: Integer -> PersonId intToPersonId i = Key (PersistInt64 (fromIntegral i)) personIdToInt :: PersonId -> Integer personIdToInt pid = case (unKey pid) of PersistInt64 i -> fromIntegral i _ -> error "kein PersistInt64-Wert" Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 30/22 Einleitung Sessions und Cookies Daten die nicht permanent, sondern nur während einer Sitzung gebraucht werden z.B. Anzahl richtige Antworten im Quiz Speichern in der Datenbank? Ungeeignet Lösung: Cookies: Client speichert die Daten im Cookie und übermittelt sie an Server Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 31/22 Einleitung Cookies in Yesod Wesentliche Methoden lookupSession: Eintrag nachschauen setSession Eintrag einfügen Beispiel: Zählen, wie oft ein Benutzer die Seite mit dem Formular aufgerufen hat: getHalloR :: Handler Html getHalloR = do wert <- lookupSession "anzahlAufrufe" let anzahl = case wert of Nothing -> 1 Just i -> 1+(read (T.unpack i))::Int setSession "anzahlAufrufe" (T.pack (show anzahl)) defaultLayout [whamlet|<form method=post action=@{HalloR}> <h1>Formular (Ihr #{anzahl}.Aufruf) ... Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 32/22 Einleitung Aufgaben Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 33/22 Einleitung Aufgaben HomeR / GET Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 33/22 Einleitung Aufgaben HomeR / QuizStartR GET /quiz GET Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 33/22 Einleitung Aufgaben HomeR / QuizStartR GET /quiz GET QuizR /quiz/#Text GET Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 33/22 Einleitung Aufgaben HomeR / QuizStartR GET /quiz GET QuizR /quiz/#Text GET QuizR /quiz/#Text POST Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 33/22 Einleitung Aufgaben HomeR / QuizStartR /quiz GET AdminShowR GET /admin GET QuizR /quiz/#Text GET QuizR /quiz/#Text POST Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 33/22 Einleitung Aufgaben HomeR / QuizStartR /quiz GET AdminShowR GET /admin GET QuizR /quiz/#Text GET QuizR AdminNewR /quiz/#Text /admin/new POST GET POST Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 33/22 Einleitung Aufgaben HomeR / QuizStartR /quiz GET AdminShowR GET /admin GET QuizR /quiz/#Text AdminDeleteR GET /admin/delete/#QuestionId GET POST QuizR AdminNewR /quiz/#Text /admin/new POST GET POST Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 33/22 Einleitung Aufgaben HomeR / QuizStartR /quiz GET AdminShowR GET /admin GET QuizR /quiz/#Text GET AdminEditR AdminDeleteR /admin/edit/#QuestionId /admin/delete/#QuestionId GET POST GET POST QuizR AdminNewR /quiz/#Text /admin/new POST GET POST Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 33/22 Einleitung Datenmodell Question question answer1 answer2 answer3 answer4 right deriving Text Text Text Text Text Int Show Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 34/22 Einleitung Sessioncookie Liste der gestellten Fragen (QuestionIds) Anzahl der richtigen Antworten Anzahl der gestellten Fragen (Länge der Liste) Praktikum BKSPP: Blatt 4 – WS 2014/15 – D. Sabel 35/22