praktikum 3

This commit is contained in:
darthsandmann
2017-01-16 23:32:28 +01:00
parent 41af35a624
commit 0ef842e09e
171 changed files with 22681 additions and 27 deletions

BIN
Praktikum3/lit-x/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,3 @@
# coding: utf-8
# hier können Paket-Initialisierungen eingetragen werden

View File

@ -0,0 +1,348 @@
# coding: utf-8
import json
import cherrypy
from .database import SourceDatabase_cl, EvaluatedDatabase_cl
# Method-Dispatching!
# Übersicht Anforderungen / Methoden
"""
Anforderung GET POST PUT DELETE
----------------------------------------------------------------
/ Liste - - -
Literatur
liefern
/source Liste - - -
Literatur
liefern
/evaluated Liste - - -
Auswertungen
liefern
/source/0 Dokument Dokument - -
mit id=0 anlegen
liefern
(Vorgabe-Werte)
/evaluated/0 Dokument Dokument - -
mit id=0 anlegen
liefern
(Vorgabe-Werte)
/source/{id} Dokument - Dokument Dokument
mit {id} ändern löschen
liefern
(Literatur)
/evaluated/{id} Dokument - Dokument Dokument
mit {id} ändern löschen
liefern
(Auswertungen)
id > 0 !
"""
#-------------------------------------------------------
def adjustId_p(id_spl, data_opl):
#-------------------------------------------------------
if id_spl == None:
data_opl['id'] = ''
elif id_spl == '':
data_opl['id'] = ''
elif id_spl == '0':
data_opl['id'] = ''
else:
data_opl['id'] = id_spl
return data_opl
#----------------------------------------------------------
class Source_cl(object):
#----------------------------------------------------------
#-------------------------------------------------------
def __init__(self):
#-------------------------------------------------------
self.db_o = SourceDatabase_cl()
#-------------------------------------------------------
def GET(self, id):
#-------------------------------------------------------
retVal_o = {
'data': None
}
if id == None:
# Anforderung der Liste
retVal_o['data'] = self.db_o.read_px()
else:
# Anforderung eines Dokuments
data_o = self.db_o.read_px(id)
if data_o != None:
retVal_o['data'] = adjustId_p(id, data_o)
return retVal_o
#-------------------------------------------------------
def POST(self, data_opl):
#-------------------------------------------------------
retVal_o = {
'id': None
}
# data_opl: Dictionary mit den gelieferten key-value-Paaren
data_o = {
'name': data_opl["name_s"],
'typ': data_opl["typ_s"],
'referenz': data_opl["referenz_s"]
}
# Create-Operation
id_s = self.db_o.create_px(data_o)
retVal_o['id'] = id_s
return retVal_o
#-------------------------------------------------------
def PUT(self, data_opl):
#-------------------------------------------------------
# Sichern der Daten: jetzt wird keine vollständige Seite
# zurückgeliefert, sondern nur noch die Information, ob das
# Speichern erfolgreich war
retVal_o = {
'id': None
}
# data_opl: Dictionary mit den gelieferten key-value-Paaren
id_s = data_opl["id_s"]
data_o = {
'name': data_opl["name_s"],
'typ': data_opl["typ_s"],
'referenz': data_opl["referenz_s"]
}
# Update-Operation
retVal_o['id'] = id_s
if self.db_o.update_px(id_s, data_o):
pass
else:
retVal_o['id'] = None
return retVal_o
#-------------------------------------------------------
def DELETE(self, id):
#-------------------------------------------------------
# Eintrag löschen, nur noch Rückmeldung liefern
retVal_o = {
'id': id
}
if self.db_o.delete_px(id):
pass
else:
retVal_o['id'] = None
return retVal_o
#----------------------------------------------------------
class Evaluated_cl(object):
#----------------------------------------------------------
#-------------------------------------------------------
def __init__(self):
#-------------------------------------------------------
self.db_o = EvaluatedDatabase_cl()
#-------------------------------------------------------
def GET(self, id):
#-------------------------------------------------------
retVal_o = {
'data': None
}
if id == None:
# Anforderung der Liste
retVal_o['data'] = self.db_o.read_px()
else:
# Anforderung eines Dokuments
data_o = self.db_o.read_px(id)
if data_o != None:
retVal_o['data'] = adjustId_p(id, data_o)
return retVal_o
#-------------------------------------------------------
def POST(self, data_opl):
#-------------------------------------------------------
retVal_o = {
'id': None
}
# data_opl: Dictionary mit den gelieferten key-value-Paaren
# hier müsste man prüfen, ob die Daten korrekt vorliegen!
data_o = {
'title': data_opl["title_s"],
'author': data_opl["author_s"],
'evalyear': data_opl["evalyear_s"]
}
# Create-Operation
id_s = self.db_o.create_px(data_o)
retVal_o['id'] = id_s
return retVal_o
#-------------------------------------------------------
def PUT(self, data_opl):
#-------------------------------------------------------
# Sichern der Daten: jetzt wird keine vollständige Seite
# zurückgeliefert, sondern nur noch die Information, ob das
# Speichern erfolgreich war
retVal_o = {
'id': None
}
# data_opl: Dictionary mit den gelieferten key-value-Paaren
# hier müsste man prüfen, ob die Daten korrekt vorliegen!
id_s = data_opl["id_s"]
data_o = {
'title': data_opl["title_s"],
'author': data_opl["author_s"],
'evalyear': data_opl["evalyear_s"]
}
# Update-Operation
retVal_o['id'] = id_s
if self.db_o.update_px(id_s, data_o):
pass
else:
retVal_o['id'] = None
return retVal_o
#-------------------------------------------------------
def DELETE(self, id):
#-------------------------------------------------------
# Eintrag löschen, nur noch Rückmeldung liefern
retVal_o = {
'id': id
}
if self.db_o.delete_px(id):
pass
else:
retVal_o['id'] = None
return retVal_o
#----------------------------------------------------------
class Application_cl(object):
#----------------------------------------------------------
exposed = True # gilt für alle Methoden
#-------------------------------------------------------
def __init__(self):
#-------------------------------------------------------
self.handler_o = {
'source': Source_cl(),
'evaluated': Evaluated_cl()
}
# es wird keine index-Methode vorgesehen, weil stattdessen
# die Webseite index.html ausgeliefert wird (siehe Konfiguration)
#-------------------------------------------------------
def GET(self, path_spl = 'source', id=None):
#-------------------------------------------------------
retVal_o = {
'data': None
}
if path_spl in self.handler_o:
retVal_o = self.handler_o[path_spl].GET(id)
if retVal_o['data'] == None:
cherrypy.response.status = 404
return json.dumps(retVal_o)
#-------------------------------------------------------
def POST(self, path_spl = 'source', **data_opl):
#-------------------------------------------------------
retVal_o = {
'id': None
}
# data_opl: Dictionary mit den gelieferten key-value-Paaren
# hier müsste man prüfen, ob die Daten korrekt vorliegen!
if path_spl in self.handler_o:
retVal_o = self.handler_o[path_spl].POST(data_opl)
if retVal_o['id'] == None:
cherrypy.response.status = 409
return json.dumps(retVal_o)
#-------------------------------------------------------
def PUT(self, path_spl = 'source', **data_opl):
#-------------------------------------------------------
# Sichern der Daten: jetzt wird keine vollständige Seite
# zurückgeliefert, sondern nur noch die Information, ob das
# Speichern erfolgreich war
retVal_o = {
'id': None
}
# data_opl: Dictionary mit den gelieferten key-value-Paaren
# hier müsste man prüfen, ob die Daten korrekt vorliegen!
if path_spl in self.handler_o:
retVal_o = self.handler_o[path_spl].PUT(data_opl)
if retVal_o['id'] == None:
cherrypy.response.status = 404
return json.dumps(retVal_o)
#-------------------------------------------------------
def DELETE(self, path_spl = 'source', id=None):
#-------------------------------------------------------
# Eintrag löschen, nur noch Rückmeldung liefern
retVal_o = {
'id': id
}
if path_spl in self.handler_o:
retVal_o = self.handler_o[path_spl].DELETE(id)
if retVal_o['id'] == None:
cherrypy.response.status = 404
return json.dumps(retVal_o)
#-------------------------------------------------------
def default(self, *arguments, **kwargs):
#-------------------------------------------------------
msg_s = "unbekannte Anforderung: " + \
str(arguments) + \
' ' + \
str(kwargs)
raise cherrypy.HTTPError(404, msg_s)
# EOF

162
Praktikum3/lit-x/app/database.py Executable file
View File

@ -0,0 +1,162 @@
# coding: utf-8
import os
import os.path
import codecs
import json
#----------------------------------------------------------
class Database_cl(object):
#----------------------------------------------------------
# Daten in dieser Variante dauerhaft (persistent) speichern
# dazu jedes Element in einer Datei, die entsprechend der id benannt ist, speichern
# alle Elemente werden zur Laufzeit des Servers zur Vereinfachung auch im
# Hauptspeicher abgelegt
# die nächste zu vergebende Id wird ebenfalls dauerhaft gespeichert
# zur Vereinfachung wird hier fest vorgegebenen, dass sich die Daten
# im Unterverzeichnis "data/<type>" befinden (siehe Konstruktor)
# es wird ferner angenommen, dass die Datei "data/<type>/maxid.dat" bereits existiert
# und als einzigen Eintrag den aktuellen Wert der maximalen Id enthält
#-------------------------------------------------------
def __init__(self, type_spl):
#-------------------------------------------------------
self.type_s = type_spl
self.path_s = os.path.join('data', type_spl)
self.data_o = {}
self.readData_p()
#-------------------------------------------------------
def create_px(self, data_opl):
#-------------------------------------------------------
# Überprüfung der Datenn müsste ergänzt werden!
id_s = self.nextId_p()
# Datei erzeugen
file_o = codecs.open(os.path.join(self.path_s , id_s+'.dat'), 'w', 'utf-8')
file_o.write(json.dumps(data_opl, indent=3, ensure_ascii=True))
file_o.close()
self.data_o[id_s] = data_opl
return id_s
#-------------------------------------------------------
def read_px(self, id_spl = None):
#-------------------------------------------------------
# hier zur Vereinfachung:
# Aufruf ohne id: alle Einträge liefern
data_o = None
if id_spl == None:
data_o = self.data_o
elif id_spl == '0':
data_o = self.getDefault_px()
else:
if id_spl in self.data_o:
data_o = self.data_o[id_spl]
return data_o
#-------------------------------------------------------
def update_px(self, id_spl, data_opl):
#-------------------------------------------------------
# Überprüfung der Daten müsste ergänzt werden!
status_b = False
if id_spl in self.data_o:
# Datei aktualisieren
file_o = codecs.open(os.path.join(self.path_s, id_spl+'.dat'), 'w', 'utf-8')
file_o.write(json.dumps(data_opl, indent=3, ensure_ascii=True))
file_o.close()
self.data_o[id_spl] = data_opl
status_b = True
return status_b
#-------------------------------------------------------
def delete_px(self, id_spl):
#-------------------------------------------------------
status_b = False
if id_spl in self.data_o:
# Datei entfernen
os.remove(os.path.join(self.path_s, id_spl+'.dat'))
del self.data_o[id_spl]
status_b = True
return status_b
#-------------------------------------------------------
def getDefault_px(self):
#-------------------------------------------------------
# sollte überschrieben werden!
return {}
#-------------------------------------------------------
def readData_p(self):
#-------------------------------------------------------
files_a = os.listdir(self.path_s)
for fileName_s in files_a:
if fileName_s.endswith('.dat') and fileName_s != 'maxid.dat':
file_o = codecs.open(os.path.join(self.path_s, fileName_s), 'rU', 'utf-8')
content_s = file_o.read()
file_o.close()
id_s = fileName_s[:-4]
self.data_o[id_s] = json.loads(content_s)
#-------------------------------------------------------
def nextId_p(self):
#-------------------------------------------------------
file_o = open(os.path.join(self.path_s, 'maxid.dat'), 'r+')
maxId_s = file_o.read()
maxId_s = str(int(maxId_s)+1)
file_o.seek(0)
file_o.write(maxId_s)
file_o.close()
return maxId_s
#----------------------------------------------------------
class SourceDatabase_cl(Database_cl):
#----------------------------------------------------------
#-------------------------------------------------------
def __init__(self):
#-------------------------------------------------------
super().__init__('source')
#-------------------------------------------------------
def getDefault_px(self):
#-------------------------------------------------------
return {
'name': '',
'typ': 'Typ1',
'referenz': ''
}
#----------------------------------------------------------
class EvaluatedDatabase_cl(Database_cl):
#----------------------------------------------------------
#-------------------------------------------------------
def __init__(self):
#-------------------------------------------------------
super().__init__('evaluated')
#-------------------------------------------------------
def getDefault_px(self):
#-------------------------------------------------------
return {
'title': '',
'author': '',
'evalyear': ''
}
# EOF

View File

@ -0,0 +1,42 @@
# coding: utf-8
import json
import cherrypy
# Method-Dispatching!
# Übersicht Anforderungen / Methoden
# (beachte: / relativ zu /navigation, siehe Konfiguration Server!)
"""
Anforderung GET PUT POST DELETE
----------------------------------------------------------------
/ Nav-Entries - - -
"""
#----------------------------------------------------------
class Navigation_cl(object):
#----------------------------------------------------------
exposed = True # gilt für alle Methoden
#-------------------------------------------------------
def __init__(self):
#-------------------------------------------------------
pass
#-------------------------------------------------------
def GET(self):
#-------------------------------------------------------
# Hinweis: könnte man auch aus einer Datei einlesen
retVal_o = [
{'action': 'source' , 'text': 'Quellen'},
{'action': 'evaluated', 'text': 'Ausgewertet'}
]
return json.dumps(retVal_o)
# EOF

View File

@ -0,0 +1,52 @@
# coding: utf-8
import json
import os
import codecs
import cherrypy
# Method-Dispatching!
# Übersicht Anforderungen / Methoden
# (beachte: / relativ zu /template, siehe Konfiguration Server!)
"""
Anforderung GET PUT POST DELETE
----------------------------------------------------------------
/ Alle - - -
Templates
liefern
"""
#----------------------------------------------------------
class Templates_cl(object):
#----------------------------------------------------------
exposed = True # gilt für alle Methoden
#-------------------------------------------------------
def __init__(self):
#-------------------------------------------------------
pass
#-------------------------------------------------------
def GET(self):
#-------------------------------------------------------
retVal_o = {
'templates': {}
}
files_a = os.listdir('templates')
for fileName_s in files_a:
file_o = codecs.open(os.path.join('templates', fileName_s), 'rU', 'utf-8')
content_s = file_o.read()
file_o.close()
retVal_o["templates"][fileName_s] = content_s
return json.dumps(retVal_o)
# EOF

View File

@ -0,0 +1,5 @@
{
"author": "abc",
"evalyear": "2002",
"title": "xyz123"
}

View File

@ -0,0 +1,5 @@
{
"author": "noch einer",
"evalyear": "2011",
"title": "zweiter"
}

View File

@ -0,0 +1 @@
3

View File

@ -0,0 +1,5 @@
{
"typ": "Typ1",
"name": "x",
"referenz": ""
}

View File

@ -0,0 +1,5 @@
{
"typ": "Typ2",
"name": "y",
"referenz": ""
}

View File

@ -0,0 +1,5 @@
{
"typ": "Typ1",
"name": "xywwww",
"referenz": "http://123.de"
}

View File

@ -0,0 +1,5 @@
{
"referenz": "abc",
"name": "testdaten123",
"typ": "Typ123"
}

View File

@ -0,0 +1,5 @@
{
"referenz": "abcd",
"name": "testdaten1234",
"typ": "Typ1234"
}

View File

@ -0,0 +1,5 @@
{
"typ": "Typ1",
"referenz": "123",
"name": "xyz"
}

View File

@ -0,0 +1,5 @@
{
"typ": "Typ1",
"name": "1234567890xyz",
"referenz": "rrrr"
}

View File

@ -0,0 +1,5 @@
{
"typ": "Typ2",
"name": "3namexxxx",
"referenz": "http://"
}

View File

@ -0,0 +1 @@
40

62
Praktikum3/lit-x/server.py Executable file
View File

@ -0,0 +1,62 @@
#coding: utf-8
import os
import cherrypy
from app import application, templates, navigation
#--------------------------------------
def main():
#--------------------------------------
# Get current directory
try:
current_dir = os.path.dirname(os.path.abspath(__file__))
except:
current_dir = os.path.dirname(os.path.abspath(sys.executable))
# disable autoreload and timeout_monitor
cherrypy.engine.autoreload.unsubscribe()
cherrypy.engine.timeout_monitor.unsubscribe()
# Static content config
staticConfig_o = {
'/': {
'tools.staticdir.root': current_dir,
'tools.staticdir.on': True,
'tools.staticdir.dir': './static',
'tools.staticdir.index': './html/index.html',
'request.dispatch': cherrypy.dispatch.MethodDispatcher()
},
'/favicon.ico': {
'tools.staticfile.on': True,
'tools.staticfile.filename': current_dir+'/static/images/favicon.ico'
}
}
staticConfig2_o = {
'/': {
'request.dispatch': cherrypy.dispatch.MethodDispatcher()
}
}
cherrypy.config.update({
'tools.log_headers.on': True,
'tools.sessions.on': False,
'tools.encode.on': True,
'tools.encode.encoding': 'utf-8',
'server.socket_port': 8080,
'server.socket_timeout': 60,
'server.thread_pool': 10,
'server.environment': 'production',
'log.screen': True,
'request.show_tracebacks': False
})
# Request-Handler definieren
cherrypy.tree.mount(application.Application_cl(), '/', staticConfig_o)
cherrypy.tree.mount(templates.Templates_cl(), '/templates', staticConfig2_o)
cherrypy.tree.mount(navigation.Navigation_cl(), '/navigation', staticConfig2_o)
# Start server
cherrypy.engine.start()
cherrypy.engine.block()
#--------------------------------------
if __name__ == '__main__':
#--------------------------------------
main()
# EOF

View File

@ -0,0 +1,194 @@
/* main.css */
/* allgemeine Vorgaben */
body {
font-family: "Open Sans", sans-serif;
font-size: 12pt;
padding: 0;
margin: 0;
}
/* Basislayout */
.clSiteHeader {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 50px;
line-height: 50px;
margin: 0;
padding: 5px;
font-size: 12pt;
color: white;
background-color: #0000AA;
}
.clNav {
position: absolute;
top: 60px; /* height, padding, border, margin von idSiteHeader beachten */
left: 0;
bottom: 0;
width: 140px;
border-right: 1px solid;
margin: 0;
padding: 5px;
}
.clNav a, a:hover, a:visited, a:active{
display: block;
text-decoration: none;
margin-bottom: 10px;
}
.clNav a:hover {
font-weight: bold;
}
.clContentOuter {
position: absolute;
top: 60px; /* height, padding, border, margin von idSiteHeader beachten */
left: 151px;
right: 0;
bottom: 0;
margin: 0;
padding: 0;
}
.clContent {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: 0;
padding: 5px;
}
/* Elemente im Content-Bereich */
.clContentHeader {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 30px;
line-height: 30px;
margin: 0;
padding: 5px;
font-size: 14pt;
text-align: center;
}
.clContentArea {
position: absolute;
top: 40px; /* height, padding, border, margin von idContentHeader beachten */
left: 0;
right: 0;
bottom: 40px; /* height, padding, border, margin von idButtonArea beachten */
margin: 0;
padding: 0px;
overflow-y: auto;
}
.clButtonArea {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 30px;
line-height: 30px;
margin: 0;
padding: 5px;
text-align: center;
background-color: #EEEEEE;
}
/* Links und Submit-Schalter im Buttonbereich gestalten */
.clButtonArea button, a, input[type="submit"] {
margin: 0 5px;
padding: 3px 6px;
font-size: 10pt;
text-decoration: none;
border: 1px solid;
color: black;
background-color: buttonface;
}
.clButtonArea button:disabled {
color: graytext;
}
/* unterschiedliche Kennzeichnungen je nach Bedienung vermeiden */
.clButtonArea a:hover, a:visited, a:active {
color: black;
}
/* Gestaltung von Tabellen */
#idList {
table-layout: fixed;
width: 80%;
border: 1px solid;
border-collapse: collapse;
margin:auto;
}
#idList th {
text-align: left;
padding-left: 6px;
}
#idList th, #idList td {
padding: 3px;
border: 1px solid;
}
/* damit wird jede ungerade Zeile in Tabellen mit einer anderen Hintergrundfarbe ausgegeben */
/* den Effekt nennt man auch "Zebra"-Tables */
tr:nth-of-type(odd) {
background-color:#ccc;
}
#idList tr.clSelected {
background-color: blue;
color: white;
}
/* Gestaltung von Formularen */
/* das Formular nochmals zusätzlich gestalten */
#idForm .clContentArea {
width: 500px;
margin: auto;
}
.clFormRow {
position: relative; /* damit das Element in jedem Fall "positioniert" ist und damit als Bezugspunkt geeignet ist */
height: 25px;
margin-bottom: 10px;
}
.clFormRow label {
position: absolute;
top: 0;
left: 0;
width: 150px;
text-align: right;
}
.clFormRow input {
position: absolute;
top: 0;
left: 160px;
width: 250px;
}
label span.clRequired {
color: red;
content: '*';
}
/* EOF */

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<title>
Web Engineering 2016/2017 / Beispiel lit-x
</title>
<meta charset="UTF-8" />
<style type="text/css">
@import url("/css/main.css");
</style>
<script type="text/javascript" src="/js/jquery.js"></script>
<script type="text/javascript" src="/js/es.js"></script>
<script type="text/javascript" src="/js/te.js"></script>
<script type="text/javascript" src="/js/tm.js"></script>
<!-- hier zur Verdeutlichung der Strukturierung Aufteilung in mehrere js-Quellen -->
<!-- wird man im produktiven Einsatz zu einer js-Quelle zusammenfassen -->
<script type="text/javascript" src="/js/app.js"></script>
<script type="text/javascript" src="/js/detail.js"></script>
<script type="text/javascript" src="/js/list.js"></script>
<script type="text/javascript" src="/js/nav.js"></script>
</head>
<body>
<header>
<h1 id="idSiteHeader" class="clSiteHeader">
Web-Engineering: Anwendungsbeispiel Lit-x
</h1>
</header>
<section id="idContentOuter" class="clContentOuter">
</section>
<nav id="idNav" class="clNav">
</nav>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

105
Praktikum3/lit-x/static/js/app.js Executable file
View File

@ -0,0 +1,105 @@
// ----------------------------------------------
// Beispiel lit-x
// app.js
// ----------------------------------------------
// Verwendung von jquery, Single-Page / Ajax, Event-Service
// REST-Interface
// ----------------------------------------------
'use strict'
// ----------------------------------------------
// Namensraum einrichten
// ----------------------------------------------
let APP = {};
// ----------------------------------------------
APP.Application_cl = class {
// ----------------------------------------------
constructor () {
this.content_o = null; // das jeweils aktuelle Objekt im Contentbereich
this.nav_o = new APP.Nav_cl();
this.listSources_o = new APP.ListView_cl('source', '/source/', 'sourceslist.tpl');
this.detailSources_o = new APP.SourceDetailView_cl('source', '/source/', 'sourcedetail.tpl');
this.listEvaluated_o = new APP.ListView_cl('evaluated', '/evaluated/', 'evaluatedlist.tpl');
this.detailEvaluated_o = new APP.DetailView_cl('evaluated', '/evaluated/', 'evaluateddetail.tpl');
// Registrierungen
APP.es_o.subscribe_px(this, 'app');
}
notify_px (self_opl, message_spl, data_apl) {
switch (message_spl) {
case 'app':
switch (data_apl[0]) {
case 'init':
APP.tm_o = new TemplateManager_cl();
break;
case 'templates.loaded':
self_opl.nav_o.render_px();
// Liste Quellen im Content-Bereich anzeigen
self_opl.setContent_p(self_opl.listSources_o, data_apl[1]);
break;
case 'source':
// Liste Quellen im Content-Bereich anzeigen
self_opl.setContent_p(self_opl.listSources_o, data_apl[1]);
break;
case 'source.add':
// (leeres) Detailformular im Content-Bereich anzeigen
self_opl.setContent_p(self_opl.detailSources_o, data_apl[1]);
break;
case 'source.edit':
// Detailformular im Content-Bereich anzeigen
self_opl.setContent_p(self_opl.detailSources_o, data_apl[1]);
break;
case 'evaluated':
// Liste Quellen im Content-Bereich anzeigen
self_opl.setContent_p(self_opl.listEvaluated_o, data_apl[1]);
break;
case 'evaluated.add':
// (leeres) Detailformular im Content-Bereich anzeigen
self_opl.setContent_p(self_opl.detailEvaluated_o, data_apl[1]);
break;
case 'evaluated.edit':
// Detailformular im Content-Bereich anzeigen
self_opl.setContent_p(self_opl.detailEvaluated_o, data_apl[1]);
break;
default:
console.warn('[Application_cl] unbekannte app-Notification: '+data_apl[0]);
break;
}
break;
default:
console.warn('[Application_cl] unbekannte Notification: '+message_spl);
break;
}
}
setContent_p (newContent_opl, data_opl) {
if (this.content_o != null) {
if (this.content_o === newContent_opl) { // Achtung: Vergleich auf Identität (===) und nicht nur auf Gleichheit (==)
// wird bereits angezeigt, keine Änderung
} else {
if (this.content_o.canClose_px()) {
this.content_o.close_px();
this.content_o = newContent_opl;
this.content_o.render_px(data_opl);
}
}
} else {
this.content_o = newContent_opl;
this.content_o.render_px(data_opl);
}
}
}
// ----------------------------------------------
$(document).ready(function(){
// ----------------------------------------------
// wird ausgeführt, wenn das Dokument vollständig geladen wurde
APP.es_o = new EventService_cl();
APP.app_o = new APP.Application_cl();
APP.es_o.publish_px('app', ['init', null]);
});
// EOF

View File

@ -0,0 +1,142 @@
// ----------------------------------------------
// Beispiel lit-x
// detail.js
// ----------------------------------------------
// ----------------------------------------------
APP.DetailView_cl = class {
// ----------------------------------------------
constructor (name_spl, path_spl, template_spl) {
this.name_s = name_spl;
this.path_s = path_spl;
this.template_s = template_spl;
}
canClose_px () {
// Prüfen, ob Formularinhalt verändert wurde
let mod_b = this.isModified_p();
if (mod_b) {
if (confirm("Es gibt nicht gespeicherte Änderungen - verwerfen?")) {
mod_b = false;
}
}
return !mod_b;
}
close_px () {
this.exitHandler_p();
}
render_px (data_opl) {
let path_s;
if (data_opl != null) {
path_s = this.path_s + data_opl;
} else {
path_s = this.path_s + '0';
}
$.ajax({
dataType: "json",
url: path_s,
type: 'GET',
context: this
})
.done(function (data_opl) {
this.doRender_p(data_opl);
this.initHandler_p();
})
.fail(function(jqXHR_opl, textStatus_spl) {
alert( "[Detail] Fehler bei Anforderung: " + textStatus_spl );
});
}
doRender_p (data_opl) {
var markup_s = APP.tm_o.execute_px(this.template_s, data_opl);
$("#idContentOuter").html(markup_s);
this.storeFormContent_p();
}
initHandler_p () {
// Ereignisverarbeitung für das Formular einrichten
$("#idForm").on("click", "button", $.proxy(this.onClickButtons_p, this));
}
exitHandler_p () {
// Ereignisverarbeitung für das Formular aufheben
$("#idForm").off("click", "button", $.proxy(this.onClickButtons_p, this));
}
onClickButtons_p (event_opl) {
var do_b = false;
var path_s;
var action_s = $(event_opl.target).attr("data-action");
switch (action_s) {
case "back":
// Weiterleiten: Liste anfordern
APP.es_o.publish_px('app', [this.name_s, null]);
break;
case "save":
// Formularinhalt prüfen
if (this.isModified_p()) {
if (this.checkContent_p()) {
// kein klassisches submit, es wird auch keine neue Anzeige vorgenommen
var path_s = this.path_s;
var data_s = $("#idForm").serialize();
var type_s = 'PUT';
var id_s = $('#id_s').val();
if (id_s == '') {
type_s = 'POST';
}
$.ajax({
context: this,
dataType: "json",
data: data_s,
url: path_s,
type: type_s
})
.done(function (data_opl) {
// Umwandlung der JSON-Daten vom Server bereits erfolgt
$('#id_s').val(data_opl['id']);
// aktuellen Formularinhalt speichern
// (das Formular wird ja nicht mehr neu geladen!)
this.storeFormContent_p();
alert("Speichern ausgeführt!");
})
.fail(function(jqXHR_opl, textStatus_spl) {
alert( "Fehler bei Anforderung: " + textStatus_spl );
});
} else {
alert("Bitte prüfen Sie die Eingaben in den Formularfeldern!")
}
}
break;
}
// Weiterleitung und Standardbearbeitung unterbinden
event_opl.stopPropagation();
event_opl.preventDefault();
}
isModified_p () {
// Prüfen, ob Formularinhalt verändert wurde
var mod_b = this.FormContentOrg_s != $("#idForm").serialize();
return mod_b;
}
checkContent_p () {
return true;
}
storeFormContent_p () {
this.FormContentOrg_s = $("#idForm").serialize();
}
}
// ----------------------------------------------
APP.SourceDetailView_cl = class extends APP.DetailView_cl {
// ----------------------------------------------
//constructor (name_spl, path_spl, template_spl) {
// super.constructor(name_spl, path_spl, template_spl)
//}
checkContent_p () {
// hier nur zur Demonstration Prüfung des Typs gegen eine Werteliste
// (das realisiert man besser mit einer Liste)
var status_b = true;
var typ_s = $("#typ_s").val();
if ((typ_s != "Typ1") && (typ_s != "Typ2")) {
status_b = false;
}
return status_b;
}
}
// EOF

View File

@ -0,0 +1,104 @@
//------------------------------------------------------------------------------
// Event-Service: asynchroner Nachrichtenaustausch
//------------------------------------------------------------------------------
'use strict'
function each(object_opl, iterator_opl, context_opl) {
for (let key_s in object_opl) {
iterator_opl.call(context_opl, object_opl[key_s], key_s);
}
}
function findAll(object_opl, iterator_opl, context_opl) {
let results_a = [];
each(object_opl, function(value_opl, index_ipl) {
if (iterator_opl.call(context_opl, value_opl, index_ipl))
results_a.push(value_opl);
});
return results_a;
}
function compact(object_opl) {
return findAll(object_opl, function(value_opl) {
return value_opl != null;
});
}
//------------------------------------------------------------------------------
class EventService_cl {
//------------------------------------------------------------------------------
constructor () {
this.queue_o = [];
this.Subscriber_o = {};
window.onhashchange = this.send_p.bind(this);
}
send_p (event_opl) {
// der hash-Wert interessiert hier nicht
// gibt es Elemente in der queue?
if (this.queue_o.length > 0) {
let qentry_o = this.queue_o[0];
qentry_o[0].notify_px.apply(qentry_o[0], [qentry_o[0], qentry_o[1], qentry_o[2]]);
this.queue_o.shift();
}
if (this.queue_o.length > 0) {
let d_o = new Date();
window.location.hash = d_o.getTime();
}
event_opl.preventDefault();
return false;
}
subscribe_px (Subscriber_opl, Message_spl) {
if (Message_spl in this.Subscriber_o) {
// Message bekannt, Liste der Subscriber untersuchen
if (this.Subscriber_o[Message_spl].indexOf(Subscriber_opl) == -1) {
this.Subscriber_o[Message_spl].push(Subscriber_opl);
}
} else {
// Message noch nicht vorhanden, neu eintragen
this.Subscriber_o[Message_spl] = [Subscriber_opl];
}
}
unSubscribe_px (Subscriber_opl, Message_spl) {
if (Message_spl in this.Subscriber_o) {
// Message bekannt, Liste der Subscriber untersuchen
let Entry_a = this.Subscriber_o[Message_spl];
let index_i = Entry_a.indexOf(Subscriber_opl);
if (index_i >= 0) {
// Eintrag entfernen
Entry_a[index_i] = null;
Entry_a = compact(Entry_a); // compact liefert Kopie!
if (Entry_a.length == 0) {
// keine Subscriber mehr, kann entfernt werden
delete this.Subscriber_o[Message_spl];
}
}
} else {
// Message nicht vorhanden, falsche Anforderung
}
}
publish_px (Message_spl, Data_opl) {
let data_s = "<null>";
if ((Data_opl != undefined) && (Data_opl != null)) {
data_s = Data_opl.toString();
}
console.info('es - publish ' + Message_spl + ' ' + data_s);
let that = this;
each(this.Subscriber_o, function (value_apl, key_spl) {
// geliefert wird jeweils ein Wert, hier ein Array, und der Key
if (key_spl == Message_spl) {
// an alle Subscriber weitergeben
each(value_apl, function (entry_opl, index_ipl) {
// geliefert wird hier das Element und der Index
that.queue_o.push([entry_opl, Message_spl, Data_opl]);
let d_o = new Date();
window.location.hash = d_o.getTime();
}, this
);
}
}, this
)
}
}
// EOF

9210
Praktikum3/lit-x/static/js/jquery.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,143 @@
// ----------------------------------------------
// Beispiel lit-x
// list.js
// ----------------------------------------------
// ----------------------------------------------
APP.ListView_cl = class {
// ----------------------------------------------
constructor (name_spl, path_spl, template_spl) {
this.name_s = name_spl;
this.path_s = path_spl;
this.template_s = template_spl;
}
canClose_px () {
return true;
}
close_px () {
this.exitHandler_p();
}
render_px (data_opl) {
// Parameter data_opl wird hier nicht benötigt
// Anforderung an den Server senden
$.ajax({
dataType: "json",
url: this.path_s,
type: 'GET',
context: this
})
.done(function (data_opl) {
this.doRender_p(data_opl);
this.initHandler_p();
this.initList_p();
})
.fail(function(jqXHR_opl, textStatus_spl) {
alert( "[Liste] Fehler bei Anforderung: " + textStatus_spl );
});
}
doRender_p (data_opl) {
// json-Daten bereits in js-Objekte umgesetzt
var markup_s = APP.tm_o.execute_px(this.template_s, data_opl);
$("#idContentOuter").html(markup_s);
}
initList_p () {
this.rowId_s = ""; // id der selektierten Zeile
// Buttons teilweise deaktivieren, bis eine Zeile ausgewählt wurde
this.disableButtons_p();
}
initHandler_p () {
// Ereignisverarbeitung einrichten
// Ereignisverarbeitung für die Tabelle einrichten
// man beachte: für jquery muss man CSS-Selektoren angeben, also #idList statt einfach nur idList !
$("#idList").on("click", "td", $.proxy(this.onClickList_p, this));
// Ereignisverarbeitung für die Schalter einrichten
$("#idListContent #idButtonArea").on("click", "button", $.proxy(this.onClickButtons_p, this));
}
exitHandler_p () {
// Ereignisverarbeitung aufheben
$("#idList").off("click", "td", $.proxy(this.onClickList_p, this));
$("#idListContent #idButtonArea").off("click", "button", $.proxy(this.onClickButtons_p, this));
}
onClickList_p (event_opl) {
// hier werden nur click-Events auf td-Elemente geliefert
if (this.rowId_s != "") {
$("#"+this.rowId_s).removeClass("clSelected"); // Achtung: jetzt ist nur die Bezeichnung der CSS-Klasse gemeint!
}
this.rowId_s = $(event_opl.target).parent().attr('id');
$("#"+this.rowId_s).addClass("clSelected");
this.enableButtons_p();
}
onClickButtons_p (event_opl) {
var action_s = $(event_opl.target).attr("data-action");
switch (action_s) {
case 'add':
// weiterleiten
APP.es_o.publish_px('app', [this.name_s+'.'+action_s, null]);
break;
case 'edit':
if (this.rowId_s != "") {
// Weiterleiten
APP.es_o.publish_px('app', [this.name_s+'.'+action_s, this.rowId_s]);
} else {
alert("Wählen Sie bitte einen Eintrag in der Tabelle aus!");
}
break;
case 'delete':
if (this.rowId_s != "") {
if (confirm("Soll der Datensatz gelöscht werden?")) {
// Id der selektierten Tabellenzeile anhängen
var path_s = this.path_s + this.rowId_s;
$.ajax({
context: this,
dataType: "json",
url: path_s,
type: 'DELETE'
})
.done(function (data_opl) {
// Auswertung der Rückmeldung
// der umständliche Weg:
// - Liste neu darstellen, hier vereinfacht durch neue Anforderung
//APP.es_o.publish_px('list', ['refresh', null]);
// einfacher mit direktem Entfernen der Zeile aus der Tabelle
// (id des gelöschten Eintrags wird in der Antwort geliefert)
$('#'+data_opl['id']).remove();
this.initList_p();
})
.fail(function(jqXHR_opl, textStatus_spl) {
alert( "[Liste] Fehler bei Anforderung: " + textStatus_spl );
});
}
} else {
alert("Wählen Sie bitte einen Eintrag in der Tabelle aus!");
}
break;
}
// Weiterleitung und Standardbearbeitung unterbinden
event_opl.stopPropagation();
event_opl.preventDefault();
}
// stärkere Einschränkung mit #idListContent notwendig, damit nicht die Buttons auf dem
// Formular ebenfalls geändert werden
enableButtons_p () {
$("#idListContent #idButtonArea button").each(function () {
if ($(this).attr("data-action") != "add") {
$(this).prop("disabled", false);
}
});
}
disableButtons_p () {
$("#idListContent #idButtonArea button").each(function () {
if ($(this).attr("data-action") != "add") {
$(this).prop("disabled", true);
}
});
}
}
// EOF

View File

@ -0,0 +1,44 @@
// ----------------------------------------------
// Beispiel lit-x
// nav.js
// ----------------------------------------------
// ----------------------------------------------
APP.Nav_cl = class {
// ----------------------------------------------
constructor () {
}
render_px () {
// zur Vereinfachung hier direkt den Inhalt des
// Navigationsbereichs anzeigen und die Ereignisverarbeitung einrichten
$.ajax({
dataType: "json",
url: '/navigation',
type: 'GET',
context: this
})
.done(function (data_opl) {
this.doRender_p(data_opl);
this.initHandler_p();
})
.fail(function(jqXHR_opl, textStatus_spl) {
alert( "[Nav_cl] Fehler bei Anforderung: " + textStatus_spl );
});
}
doRender_p (data_opl) {
let markup_s = APP.tm_o.execute_px('nav.tpl', data_opl);
$('#idNav').html(markup_s);
}
initHandler_p () {
// Ereignisverarbeitung für die Schalter einrichten
$("#idNav").on("click", "a", function (event_opl) {
var action_s = $(event_opl.target).attr('data-action');
// Weiterleitung! Das Nav-Objekt ist nicht für die Bearbeitung direkt verantwortlich
APP.es_o.publish_px('app', [action_s, null]);
// Weiterleitung und Standardbearbeitung unterbinden
event_opl.stopPropagation();
event_opl.preventDefault();
});
}
}
// EOF

266
Praktikum3/lit-x/static/js/te.js Executable file
View File

@ -0,0 +1,266 @@
//------------------------------------------------------------------------------
// Template-Engine
//------------------------------------------------------------------------------
'use strict'
class Generator_cl {
constructor () {
this.reset_px();
}
reset_px () {
this.code_a = ['var result_a = [];\n'];
}
write_px (text_spl) {
// Escape-Zeichen bei Strings ergänzen
var text_s = text_spl.replace(/"/g, '\\"');
text_s = text_s.replace(/'/g, "\\'");
this.code_a.push('result_a.push("' + text_s + '");\n');
}
code_px (code_spl) {
if (code_spl.startsWith('if')) {
this.code_a.push('if (' + code_spl.substr(2) + ') {\n');
} else if (code_spl.startsWith('else')) {
this.code_a.push('} else {\n');
} else if (code_spl.startsWith('endif')) {
this.code_a.push('}\n');
} else if (code_spl.startsWith('for')) {
this.code_a.push('for (' + code_spl.substr(3) + ') {\n');
} else if (code_spl.startsWith('endfor')) {
this.code_a.push('}\n');
} else {
this.code_a.push(code_spl + '\n');
}
}
substitute_px (subst_spl) {
let value_s = subst_spl == null ? '' : String(subst_spl);
this.code_a.push('result_a.push(' + value_s + ');\n');
}
generate_px () {
var result_s = this.code_a.join('') + ' return result_a.join("");';
var f_o = new Function ('context', result_s);
return f_o;
}
}
class TemplateCompiler_cl {
constructor () {
this.gen_o = new Generator_cl();
this.reset_px();
}
reset_px () {
this.gen_o.reset_px();
this.preservePreWS_b = false;
this.preservePostWS_b = false;
}
setPreWS_px (on_bpl) {
if ((on_bpl == undefined) || (on_bpl == false)) {
this.preservePreWS_b = false;
} else {
this.preservePreWS_b = true;
}
}
setPostWS_px (on_bpl) {
if ((on_bpl == undefined) || (on_bpl == false)) {
this.preservePostWS_b = false;
} else {
this.preservePostWS_b = true;
}
}
compile_px (source_spl) {
var state_i = 0;
var pos_i = 0;
var len_i = source_spl.length;
var text_s = '';
var code_s = '';
var subst_s = '';
this.gen_o.reset_px();
var doubletest_f = function (char_spl) {
var status_b = false;
if ((pos_i + 1) < len_i) {
if ((source_spl[pos_i] == char_spl) && (source_spl[pos_i+1] == char_spl)) {
status_b = true;
}
}
return status_b;
}
while (pos_i < len_i) {
switch (state_i) {
case 0:
// outside
if (source_spl[pos_i] == '@') { // ECMA 5!
if (doubletest_f('@') == false) {
state_i = 2;
code_s = '';
} else {
// als normales Zeichen verarbeiten, ein Zeichen überlesen
state_i = 1;
text_s = '@';
pos_i++;
}
} else if (source_spl[pos_i] == '#') {
if (doubletest_f('#') == false) {
state_i = 3;
subst_s = '';
} else {
// als normales Zeichen verarbeiten, ein Zeichen überlesen
state_i = 1;
text_s = '#';
pos_i++;
}
} else if ((source_spl[pos_i] == ' ') || (source_spl[pos_i] == '\t')) {
state_i = 4;
text_s = '';
pos_i--; // Zeichen erneut verarbeiten
} else {
state_i = 1;
text_s = '';
pos_i--; // Zeichen erneut verarbeiten
}
break;
case 1:
// inText
if (source_spl[pos_i] == '@') {
if (doubletest_f('@') == false) {
// Textende, neuer Code
state_i = 0;
this.gen_o.write_px(text_s);
pos_i--; // Zeichen erneut verarbeiten
} else {
// als normales Zeichen verarbeiten, ein Zeichen überlesen
text_s += '@';
pos_i++;
}
} else if (source_spl[pos_i] == '#') {
if (doubletest_f('#') == false) {
// Textende, neue Substitution
state_i = 0;
this.gen_o.write_px(text_s);
pos_i--; // Zeichen erneut verarbeiten
// Textende, neuer Code
} else {
// als normales Zeichen verarbeiten, ein Zeichen überlesen
text_s += '#';
pos_i++;
}
} else if ((source_spl[pos_i] == ' ') || (source_spl[pos_i] == '\t')) {
// Textende
state_i = 0;
this.gen_o.write_px(text_s);
pos_i--; // Zeichen erneut verarbeiten
} else {
// sammeln
if ((source_spl[pos_i] != '\n') && (source_spl[pos_i] != '\r')) {
text_s += source_spl[pos_i];
} else if (source_spl[pos_i] == '\n') {
text_s += '\\n';
} else {
text_s += '\\r';
}
}
break;
case 2:
// inCode
if (source_spl[pos_i] == '@') {
if (doubletest_f('@') == false) {
// zu Ende, erzeugen
this.gen_o.code_px(code_s);
state_i = 5; //0
} else {
// als normales Zeichen verarbeiten, ein Zeichen überlesen
code_s += '@';
pos_i++;
}
} else {
// sammeln
code_s += source_spl[pos_i];
}
break;
case 3:
// inSubst
// Verdopplung # hier nicht zulässig!
if (source_spl[pos_i] == '#') {
// zu Ende, erzeugen
this.gen_o.substitute_px(subst_s);
state_i = 0;
} else {
// sammeln
subst_s += source_spl[pos_i];
}
break;
case 4:
// pre-code-whitespace
switch (source_spl[pos_i]) {
case ' ':
case '\t':
// sammeln
text_s += source_spl[pos_i];
break;
default:
state_i = 0;
if (source_spl[pos_i] != '@') {
this.gen_o.write_px(text_s);
} else {
if (doubletest_f('@') == false) {
// Whitespace vor Code-Beginn erkannt
if (this.preservePreWS_b == true) {
// trotzdem ausgeben
this.gen_o.write_px(text_s);
}
} // ansonsten wie normales Zeichen behandeln
}
pos_i--; // Zeichen erneut verarbeiten
}
break;
case 5:
// post-code-whitespace
switch (source_spl[pos_i]) {
case '\n':
text_s += '\\n';
state_i = 0;
break;
case '\r':
text_s += '\\r';
state_i = 0;
break;
case ' ':
case '\t':
// ggf. sammeln
text_s += source_spl[pos_i];
break;
default:
// Whitespace nach Code erkannt
if (this.preservePostWS_b == true) {
// trotzdem ausgeben
this.gen_o.write_px(text_s);
}
state_i = 0;
pos_i--; // Zeichen erneut verarbeiten
}
break;
}
pos_i++;
}
// welcher Zustand liegt abschließend vor?
if (state_i == 1) {
this.gen_o.write_px(text_s);
} else if (state_i == 2) {
this.gen_o.code_px(code_s);
} else if (state_i == 3) {
this.gen_o.substitute_px(subst_s);
} else if (state_i == 4) {
if (this.preservePreWS_b == true) {
this.gen_o.write_px(text_s);
}
} else if (state_i == 5) {
if (this.preservePostWS_b == true) {
this.gen_o.write_px(text_s);
}
}
return this.gen_o.generate_px();
}
}
// EOF

View File

@ -0,0 +1,61 @@
//------------------------------------------------------------------------------
// Template-Manager
// - Laden und Bereitstellen von Template-Quellen
//------------------------------------------------------------------------------
// depends-on:
// jquery
// te
//------------------------------------------------------------------------------
'use strict'
class TemplateManager_cl {
constructor () {
this.templates_o = {};
this.compiled_o = {};
this.teCompiler_o = new TemplateCompiler_cl();
// Templates als Ressource anfordern und speichern
var path_s = "/templates/";
$.ajax({
dataType: "json",
url: path_s,
type: 'GET',
context: this
})
.done(function (data_opl) {
this.templates_o = data_opl['templates'];
// Benachrichtigung senden
APP.es_o.publish_px('app', ['templates.loaded', null]);
})
.fail(function(jqXHR_opl, textStatus_spl) {
alert( "[tm] Fehler bei Anforderung: " + textStatus_spl );
});
}
get_px (name_spl) {
if (name_spl in this.templates_o) {
return this.templates_o[name_spl];
} else {
return null;
}
}
execute_px (name_spl, data_opl) {
var compiled_o = null;
if (name_spl in this.compiled_o) {
compiled_o = this.compiled_o[name_spl];
} else {
// Übersetzen und ausführen
if (name_spl in this.templates_o) {
this.teCompiler_o.reset_px();
compiled_o = this.teCompiler_o.compile_px(this.templates_o[name_spl]);
this.compiled_o[name_spl] = compiled_o;
}
}
if (compiled_o != null) {
return compiled_o(data_opl);
} else {
return null;
}
}
}
// EOF

View File

@ -0,0 +1,28 @@
<!-- Template -->
<form id="idForm" class="clContent">
<h2 id="idContentHeader" class="clContentHeader">
Literaturauswertung / Formular
</h2>
<div id="idContentArea" class="clContentArea">
<input type="hidden" value="#context.data.id#" id="id_s" name="id_s" />
<div class="clFormRow">
<label for="title_s">Titel <span class="clRequired"></span></label>
<input type="text" value="#context.data.title#" id="title_s" name="title_s" autofocus required />
</div>
<div class="clFormRow">
<label for="author_s">Autor <span class="clRequired"></span></label>
<input type="text" value="#context.data.author#" id="author_s" name="author_s" required />
</div>
<div class="clFormRow">
<label for="evalyear_s">Jahr de Auswertung</label>
<input type="url" value="#context.data.evalyear#" id="evalyear_s" name="evalyear_s" />
</div>
</div>
<div id="idButtonArea" class="clButtonArea">
<button data-action="back" class="clButton">Zurück</button>
<button data-action="save" class="clButton">Speichern</button>
</div>
</form>
<!-- EOF -->

View File

@ -0,0 +1,26 @@
<!-- Template -->
<!-- evaluated -->
<div id="idListContent" class="clContent">
<h2 id="idContentHeader" class="clContentHeader">
Literaturauswertung
</h2>
<div id="idContentArea" class="clContentArea">
<table id="idList">
<tr class="listheader"><th>Titel</th><th>Autor</th><th>Auswertungsjahr</th></tr>
@var rows_o = context['data'];@
@for var key_s in rows_o@
<tr id='#key_s#'>
@var row_o = rows_o[key_s];@
<td>#row_o['title']#</td><td>#row_o['author']#</td><td>#row_o['evalyear']#</td>
</tr>
@endfor@
</table>
</div>
<div id="idButtonArea" class="clButtonArea">
<button data-action="add" class="clButton">Hinzufügen</button>
<button data-action="edit" class="clButton">Bearbeiten</button>
<button data-action="delete" class="clButton">Löschen</button>
</div>
</div>
<!-- EOF -->

View File

@ -0,0 +1,6 @@
<!-- Template -->
@let loop_i;@
@for loop_i=0; loop_i < context.length; loop_i++@
<a href="##" data-action="#context[loop_i].action#">#context[loop_i].text#</a>
@endfor@

View File

@ -0,0 +1,28 @@
<!-- Template -->
<form id="idForm" class="clContent">
<h2 id="idContentHeader" class="clContentHeader">
Literatur- / Quellenverzeichnis / Formular
</h2>
<div id="idContentArea" class="clContentArea">
<input type="hidden" value="#context.data.id#" id="id_s" name="id_s" />
<div class="clFormRow">
<label for="name_s">Name <span class="clRequired"></span></label>
<input type="text" value="#context.data.name#" id="name_s" name="name_s" autofocus required />
</div>
<div class="clFormRow">
<label for="typ_s">Typ <span class="clRequired"></span></label>
<input type="text" value="#context.data.typ#" id="typ_s" name="typ_s" required />
</div>
<div class="clFormRow">
<label for="referenz_s">Referenz</label>
<input type="url" value="#context.data.referenz#" id="referenz_s" name="referenz_s" />
</div>
</div>
<div id="idButtonArea" class="clButtonArea">
<button data-action="back" class="clButton">Zurück</button>
<button data-action="save" class="clButton">Speichern</button>
</div>
</form>
<!-- EOF -->

View File

@ -0,0 +1,26 @@
<!-- Template -->
<!-- sources -->
<div id="idListContent" class="clContent">
<h2 id="idContentHeader" class="clContentHeader">
Literatur- / Quellenverzeichnis
</h2>
<div id="idContentArea" class="clContentArea">
<table id="idList">
<tr class="listheader"><th>Name</th><th>Typ</th><th>Referenz</th></tr>
@var rows_o = context['data'];@
@for var key_s in rows_o@
<tr id='#key_s#'>
@var row_o = rows_o[key_s];@
<td>#row_o['name']#</td><td>#row_o['typ']#</td><td>#row_o['referenz']#</td>
</tr>
@endfor@
</table>
</div>
<div id="idButtonArea" class="clButtonArea">
<button data-action="add" class="clButton">Hinzufügen</button>
<button data-action="edit" class="clButton">Bearbeiten</button>
<button data-action="delete" class="clButton">Löschen</button>
</div>
</div>
<!-- EOF -->