Initial Commit für das Grundgerüst der Anwendung, bestehend aus der Dev-Container Konfiguration, der Docker und docker-Compose Definitionen sowie der Verzeichnisse App_Server für die Backend-Programme sowie Client für die browserseitigen GUI-Komponenten
This commit is contained in:
1 parent
10549934ea
commit
1ba78992a6
63 files changed
+5410
No files matched your search
@@ -0,0 +1,71 @@
|
||||
import m from "mithril"
|
||||
import {BaseView} from "./base"
|
||||
|
||||
export enum ModalType {
|
||||
'MESSAGE',
|
||||
'ERROR'
|
||||
}
|
||||
|
||||
export interface iModalView {
|
||||
title: string
|
||||
subtitle: string
|
||||
topposition: string
|
||||
width: string
|
||||
type: ModalType
|
||||
}
|
||||
export class ModalView extends BaseView implements iModalView{
|
||||
title:string
|
||||
subtitle:string
|
||||
topposition:string
|
||||
width:string
|
||||
type:ModalType
|
||||
|
||||
constructor (vnode) {
|
||||
super()
|
||||
this.title = super.getattr(vnode, "title", "Modal")
|
||||
this.subtitle = super.getattr(vnode, "subtitle", "")
|
||||
this.topposition = super.getattr(vnode, "topposition", "10%")
|
||||
this.width = super.getattr(vnode, "width", 'max-content')
|
||||
this.type = super.getattr(vnode, 'type', ModalType.MESSAGE)
|
||||
}
|
||||
view (vnode) {
|
||||
return m("div.w3-modal", {style: {display:'block'}},
|
||||
m("div.w3-modal-content.w3-card-4", {style: {width: this.width, top: this.topposition}},
|
||||
[
|
||||
m(modalHeader, {type: this.type},
|
||||
[
|
||||
m(title, this.title),
|
||||
(this.subtitle ? m(subtitle, this.subtitle) : null)
|
||||
]
|
||||
),
|
||||
m(modalContent, vnode.children)
|
||||
]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const title = {
|
||||
view: (vnode) => {
|
||||
return m("h1", vnode.children)
|
||||
}
|
||||
}
|
||||
const subtitle = {
|
||||
view: (vnode) => {
|
||||
return m("h3", vnode.children)
|
||||
}
|
||||
}
|
||||
const modalHeader = {
|
||||
view: (vnode) => {
|
||||
let color = ".w3-theme"
|
||||
if (vnode.attrs["type"] == ModalType.ERROR) {
|
||||
color = ".w3-red"
|
||||
}
|
||||
return m("header.w3-container" + color, vnode.children)
|
||||
}
|
||||
}
|
||||
const modalContent = {
|
||||
view: (vnode) => {
|
||||
return m("div.w3-container.w3-theme-l1", vnode.children)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
import m from "mithril"
|
||||
|
||||
export interface iTreenode {
|
||||
nodevalue: any
|
||||
children: iTreenode[]
|
||||
expand: boolean
|
||||
hasChildren: boolean
|
||||
isChecked?: boolean
|
||||
}
|
||||
|
||||
export class Treenode implements iTreenode {
|
||||
nodevalue:any = null
|
||||
children: iTreenode[] = []
|
||||
expand: boolean = true
|
||||
isChecked?: boolean = false
|
||||
constructor(nodevalue: any = null, expand: boolean = false, children: iTreenode[] = [], isChecked: boolean = false) {
|
||||
this.nodevalue = nodevalue
|
||||
this.children = children
|
||||
this.expand = expand
|
||||
this.isChecked = isChecked
|
||||
}
|
||||
get hasChildren() {
|
||||
return (this.children.length > 0)
|
||||
}
|
||||
}
|
||||
|
||||
export interface Columnmapper {
|
||||
(row:iTreenode) : any[]
|
||||
}
|
||||
|
||||
export interface iTreetable {
|
||||
treenodes: iTreenode[]
|
||||
columnmapper: Columnmapper
|
||||
onRowMarked?: (node: iTreenode) => void
|
||||
filter?: (row:iTreenode, searchvalue:string) => boolean
|
||||
}
|
||||
|
||||
export const Treetable: m.ClosureComponent<iTreetable> = (vnode) => {
|
||||
let onRowMarked = vnode.attrs.onRowMarked === undefined ? (node: iTreenode) => {} : vnode.attrs.onRowMarked
|
||||
let expandtoggle: boolean = true
|
||||
let searchvalue:string = ""
|
||||
function setSearchvalue(v:string) {
|
||||
searchvalue = v
|
||||
}
|
||||
return {
|
||||
view: (vnode) => {
|
||||
return m("div.w3-border-theme", {style: {height: '99%', overflow: 'hidden', 'border-style': 'solid', 'border-width': '1px'}},
|
||||
m("div.w3-bar.w3-theme",
|
||||
m(TopbarButton, {
|
||||
onclick: () => {
|
||||
expandtoggle = true; changeTreenodes(vnode.attrs.treenodes, expandtoggle)
|
||||
}},
|
||||
m("i.material-icons", "unfold_more")
|
||||
),
|
||||
m(TopbarButton, {
|
||||
onclick: () => {
|
||||
expandtoggle = false; changeTreenodes(vnode.attrs.treenodes, expandtoggle)
|
||||
}},
|
||||
m("i.material-icons", "unfold_less")
|
||||
),
|
||||
vnode.children,
|
||||
(vnode.attrs.filter != undefined) ? m(SearchField, {setSearchvalue: setSearchvalue} as iSearchfield) : null
|
||||
),
|
||||
m("div", {style: {height: '100%'}},
|
||||
m("div", {style: {height: 'calc(100% - 32px', overflow: 'auto', width: '100%'}},
|
||||
m("table.w3-table", {style: {'overflow-y': 'auto'}},
|
||||
vnode.attrs.treenodes.map((tn:iTreenode, z) => {
|
||||
if (vnode.attrs.filter == undefined || (vnode.attrs.filter != undefined && vnode.attrs.filter(tn, searchvalue) == true)) {
|
||||
return m(Treerow, {
|
||||
// ACHTUNG: Änderungen hier müssen unten beim rekursiven Aufruf der Treerow-Funktion ebenfalls eingebaut werden
|
||||
treenode: tn,
|
||||
level: 0,
|
||||
columnmapper1: vnode.attrs.columnmapper,
|
||||
onRowMarked: onRowMarked,
|
||||
isEven: ((z % 2) == 0)} as iTreerow)
|
||||
}
|
||||
else {
|
||||
return null
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface iTreerow {
|
||||
treenode: iTreenode
|
||||
level: number
|
||||
columnmapper1: Columnmapper
|
||||
onRowMarked: (node: iTreenode) => void
|
||||
isEven?: boolean
|
||||
}
|
||||
let Treerow: m.ClosureComponent<iTreerow> = (vnode) => {
|
||||
|
||||
return {
|
||||
view: (vnode) => {
|
||||
return [
|
||||
m("tr.w3-border-theme", {onmouseup: () => vnode.attrs.onRowMarked(vnode.attrs.treenode),
|
||||
onkeyup: () => vnode.attrs.onRowMarked(vnode.attrs.treenode),
|
||||
class: (vnode.attrs.isEven ? "w3-tablerow-even" : "w3-tablerow-odd"),
|
||||
style: {'border-top-style': 'solid', 'border-top-width': '1px'}
|
||||
},
|
||||
m("td", // Zunächst wird ein td-Element für die erste Spalte der Tabelle erzeugt, damit dort die
|
||||
// Einrückung in Abhängigkeit vom evtl. Vorhandensein von Kind-Elementen erledigt wird
|
||||
m("div", {style: {width: 'max-content', 'margin-left': `${(vnode.attrs.level * 25)}px`}} ,
|
||||
vnode.attrs.treenode.hasChildren
|
||||
// Wenn der Treenode Kinder hat, dann soll der "expand_more/expand_less"-Button
|
||||
// angezeigt werden
|
||||
? m("span.w3-button", {
|
||||
onclick: () => {changeTreenode(vnode.attrs.treenode, !vnode.attrs.treenode.expand)},
|
||||
style: {padding: '0px',
|
||||
'margin-right': '8px',
|
||||
width: '24px',
|
||||
height: '24px'}},
|
||||
m("i.material-icons", vnode.attrs.treenode.expand?"expand_more":"navigate_next")
|
||||
)
|
||||
// Wenn der Trennode keine Kinder hat, dann soll kein "expand_more/expand_less"-Button
|
||||
// angezeigt werden, allerdings soll durch 'margin-left' der entsprechende Platz des
|
||||
// Buttons eingerückt werden, damit die Einrückungs-Level auch bei "kinderlosen" Treenodes
|
||||
// denen der Trennodes mit Kindern entsprechen
|
||||
: vnode.attrs.level == 0
|
||||
? null // Wenn der kinderlos Treenode allerdings sirekt auf Level 0 ist, soll nocht
|
||||
// eingerückt werden, da es bei Level 0 keine Eltern-Nodes mit Kindern und
|
||||
// demzufolge mit "expand_more/expand_less"-Button gibt.
|
||||
: m(
|
||||
"span", {
|
||||
style: {'margin-left': '24px',
|
||||
'margin-right': '8px'}
|
||||
}
|
||||
),
|
||||
// Nachdem die einrückung auf Grund der "hasChildren"-Abfrage erledigt ist, wird der Inhalt
|
||||
// der ersten Spalte des Column-Mappers angezeigt
|
||||
vnode.attrs.columnmapper1(vnode.attrs.treenode).splice(0,1)
|
||||
)
|
||||
),
|
||||
// Dann werden die übrigen Spalten ganz normal als td-Elemente eingefügt
|
||||
vnode.attrs.columnmapper1(vnode.attrs.treenode).splice(1).map((column) => {
|
||||
return m("td", column)
|
||||
})
|
||||
),
|
||||
// Falls der Trennode Kinder hat und expandiert werden soll, wird rekursiv die Trennode-Komponente für
|
||||
// die Child-Nodes aufgerufen, ansonsten wir null zurückgegeben und die Einrückung hört auf
|
||||
vnode.attrs.treenode.hasChildren && vnode.attrs.treenode.expand
|
||||
? vnode.attrs.treenode.children.map((tn: iTreenode, z) => {
|
||||
return m(Treerow, {
|
||||
treenode: tn,
|
||||
level: vnode.attrs.level + 1,
|
||||
columnmapper1: vnode.attrs.columnmapper1,
|
||||
onRowMarked: vnode.attrs.onRowMarked,
|
||||
isEven: ((z % 2) == 0)
|
||||
} as iTreerow)
|
||||
})
|
||||
: null
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface iTopbarButton {
|
||||
onclick: Function
|
||||
tooltip?: string
|
||||
}
|
||||
export const TopbarButton: m.ClosureComponent<iTopbarButton> = () => {
|
||||
return {
|
||||
view: (vnode) => {
|
||||
return m("button.w3-button.w3-bar-item", {
|
||||
style: {'min-width': '32px', height: '32px', padding: '4px'},
|
||||
onclick: vnode.attrs.onclick,
|
||||
title: vnode.attrs.tooltip?vnode.attrs.tooltip:null
|
||||
},
|
||||
vnode.children
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Die Funktion kann aufgerufen werden, wenn eine Treenode-Struktur hinsichtlich
|
||||
* "Expand more/less" geändert werden soll.
|
||||
*/
|
||||
export function changeTreenodes(treenodes: Treenode[], expand: boolean) {
|
||||
treenodes.forEach((treenode: Treenode) => {
|
||||
changeTreenode(treenode, expand)
|
||||
if (treenode.hasChildren) {
|
||||
changeTreenodes(treenode.children, expand)
|
||||
}
|
||||
})
|
||||
}
|
||||
function changeTreenode(treenode: Treenode, expand: boolean) {
|
||||
treenode.expand = expand
|
||||
}
|
||||
|
||||
interface iSearchfield {
|
||||
setSearchvalue: (v:string) => void
|
||||
}
|
||||
const SearchField:m.ClosureComponent<iSearchfield> = (vnode) => {
|
||||
let inputfield: HTMLInputElement|null = null
|
||||
function oninput(e:Event) {
|
||||
if (inputfield) { // ✅ Null-Check
|
||||
let v:string = inputfield.value
|
||||
vnode.attrs.setSearchvalue(v)
|
||||
}
|
||||
}
|
||||
return {
|
||||
view: (vnode) => {
|
||||
return m("div", {style: {display: "flex", border: "1px solid black", margin: '1px'}},
|
||||
m("input.ggg-inputfocus", {
|
||||
oncreate: (vnode) => {
|
||||
inputfield = vnode.dom as HTMLInputElement
|
||||
inputfield.focus()
|
||||
},
|
||||
type:"text",
|
||||
style: {flex: '1', border: 'none'}, oninput: oninput
|
||||
}),
|
||||
m("button.w3-button", {
|
||||
style: {'min-width': '28px', height: '28px', padding: '4px', border: 'none'},
|
||||
onclick: () => {
|
||||
if (inputfield) { // ✅ Null-Check
|
||||
inputfield.value = ""
|
||||
vnode.attrs.setSearchvalue("")
|
||||
inputfield.focus()
|
||||
}
|
||||
}},
|
||||
m("i.material-icons", {style: {'font-size': 'medium'}}, "clear")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export class BaseView {
|
||||
|
||||
getattr(vnode: any, attrname: string, defaultvalue: any) {
|
||||
return vnode.attrs[attrname] === undefined ? defaultvalue : vnode.attrs[attrname]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import m from "mithril"
|
||||
|
||||
export interface iForm {
|
||||
|
||||
onchanged?: (e:Event) => void
|
||||
onsubmit: (form:HTMLFormElement) => boolean
|
||||
oncancel: (e:Event) => void
|
||||
showbuttons?: boolean
|
||||
/** Die Funktion gibt das HTMLFormElement zurück, welches in der Form-Componente angelegt wird */
|
||||
onformcreate?: (form:HTMLFormElement) => void
|
||||
}
|
||||
|
||||
export const Form:m.ClosureComponent<iForm> = (vnode) => {
|
||||
let form: HTMLFormElement
|
||||
let isDirty: boolean = false
|
||||
let showButtons: boolean = vnode.attrs.showbuttons == undefined ? true : vnode.attrs.showbuttons
|
||||
function canCloseForm(typ: string = "") {
|
||||
if (!isDirty) {
|
||||
return true
|
||||
}
|
||||
let text = (typ == "reset") ? "zurücksetzen" : "beenden"
|
||||
return confirm (`Das Formular enthält noch ungespeicherte Eingaben. Wollen Sie das Formular trotzdem ${text}?`)
|
||||
}
|
||||
function onchanged (e:Event) {
|
||||
isDirty = true
|
||||
if (vnode.attrs.onchanged != undefined) {
|
||||
vnode.attrs.onchanged(e)
|
||||
}
|
||||
}
|
||||
function submit (e:Event) {
|
||||
e.preventDefault()
|
||||
if (vnode.attrs.onsubmit(form) == true) {
|
||||
isDirty = false
|
||||
}
|
||||
}
|
||||
function registerFormElement (f:HTMLFormElement) {
|
||||
if (vnode.attrs.onformcreate != undefined) {
|
||||
vnode.attrs.onformcreate(f)
|
||||
}
|
||||
}
|
||||
function cancel(e:Event) {
|
||||
e.preventDefault()
|
||||
if (canCloseForm() == true) {
|
||||
isDirty = false
|
||||
if (vnode.attrs.oncancel != undefined) {
|
||||
vnode.attrs.oncancel(e)
|
||||
}
|
||||
else {
|
||||
history.back()
|
||||
}
|
||||
}
|
||||
else {
|
||||
e['redraw'] = false
|
||||
}
|
||||
}
|
||||
function reset(e:Event) {
|
||||
if (canCloseForm("reset")) {
|
||||
isDirty = false
|
||||
// Hier passiert nichts.
|
||||
// Da mithril nach jedem User-Event (wie z.B. einem 'Click') ein redraw() ausführt, wird hier die Form
|
||||
// neu gezeichnet und zwar mit den ursprünglichen Daten aus den zu grundeliegenden Datenmodel, das ja
|
||||
// ohne submit noch nicht geändert wurde.
|
||||
return
|
||||
}
|
||||
e['redraw'] = false
|
||||
return
|
||||
}
|
||||
return {
|
||||
onremove: (vnode) => {
|
||||
let message = "remove"
|
||||
if (isDirty) {
|
||||
message += " isDirty"
|
||||
}
|
||||
console.log(message)
|
||||
},
|
||||
view: (vnode) => {
|
||||
return m("form", {
|
||||
onreset: () => {console.log("reset")},
|
||||
onsubmit: submit,
|
||||
oncreate: (vnode) => {
|
||||
form = vnode.dom as HTMLFormElement
|
||||
let elements = form.querySelectorAll("input,textarea")
|
||||
for (let index = 0; index < elements.length; index++) {
|
||||
const element = elements[index];
|
||||
element.addEventListener("change", onchanged)
|
||||
}
|
||||
registerFormElement(form)
|
||||
}
|
||||
},
|
||||
vnode.children,
|
||||
showButtons == true ?
|
||||
m("div.w3-bar.w3-padding-16",[
|
||||
m("button.w3-button.w3-theme.w3-right.w3-mobile", {style: {margin: '0px 0px 2px 4px'}, type: "submit"}, "Speichern"),
|
||||
m("button.w3-button.w3-theme.w3-right.w3-mobile", {style: {margin: '0px 0px 2px 4px'}, onclick: cancel, type: "button"}, "Abbrechen"),
|
||||
m("button.w3-button.w3-theme.w3-right.w3-mobile", {onclick: reset, type: "button"}, "Zurücksetzen")
|
||||
])
|
||||
: null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface iFormBorder {
|
||||
maxwidth: string
|
||||
}
|
||||
export const FormBorder: m.Component<iFormBorder> = {
|
||||
view: (vnode) => {
|
||||
let mw:string = vnode.attrs.maxwidth === undefined ? "600px" : vnode.attrs.maxwidth
|
||||
return m("div.w3-border.w3-border-theme.w3-container.w3-content.w3-margin-top",
|
||||
{style: {'max-width': mw, 'box-shadow': '3px 3px 3px darkgrey'}}, vnode.children)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import m from "mithril"
|
||||
|
||||
/**
|
||||
* Der Vnode der Komponente 'Input' akzeptiert die folgenden Parameter:
|
||||
* @property {string} name - Der Name des Feldes
|
||||
* @property {any} value - der darzustellende Wert
|
||||
* @property {string} type (optional) - der Type des Input Felds. Möglich sind alle Type des HTML-Elements 'Input'
|
||||
* @property {string|number} width (optional) - die Breite des Input Felds. Möglich sind alle Längeneinheiten, die bom Width-Attribut möglich sind
|
||||
* @property {number} min (optional) -
|
||||
* @property {number} max (optional) -
|
||||
* @property {number} step (optional) -
|
||||
* @property {boolean} autofocus (optional) -
|
||||
*/
|
||||
export interface iInput {
|
||||
name: string
|
||||
value: any
|
||||
type?: string
|
||||
width?: string
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
autofocus?: boolean
|
||||
readonly?: boolean
|
||||
required?:boolean
|
||||
onchanged?: Function
|
||||
}
|
||||
/**
|
||||
* Diese Komponente stellt ein Html-Input Element dar. Der darzustellende Wert wird einfach mit dem Attribut
|
||||
* 'value' übergeben. Der eingegebene Wert muss dann später explizit aus Value abgefragt werden. Es findet also kein
|
||||
* sutomatisches Binding statt.
|
||||
*/
|
||||
export const Input:m.Component<iInput> = {
|
||||
onupdate: (vnode) => {
|
||||
if (vnode.attrs.autofocus != undefined && vnode.attrs.autofocus == true) {
|
||||
let e: HTMLElement = vnode.dom as HTMLElement
|
||||
e.focus()
|
||||
}
|
||||
},
|
||||
view: (vnode) => {
|
||||
let attributes = {}
|
||||
let width:number|string = '100%'
|
||||
attributes['name'] = vnode.attrs.name
|
||||
attributes['type'] = vnode.attrs.type == undefined ? 'text' : vnode.attrs.type
|
||||
if (attributes['type'] == 'number') {
|
||||
if (vnode.attrs.min != undefined) {
|
||||
attributes['min'] = vnode.attrs.min
|
||||
}
|
||||
if (vnode.attrs.max != undefined) {
|
||||
attributes['max'] = vnode.attrs.max
|
||||
}
|
||||
if (vnode.attrs.step != undefined) {
|
||||
attributes['step'] = vnode.attrs.step
|
||||
}
|
||||
}
|
||||
if (attributes['type'] == 'date') {
|
||||
attributes['value'] = toDateInputValue(vnode.attrs.value)
|
||||
}
|
||||
else {
|
||||
attributes['value'] = vnode.attrs.value
|
||||
}
|
||||
if (vnode.attrs.required != undefined && vnode.attrs.required == true) {
|
||||
attributes['required'] = vnode.attrs.required
|
||||
}
|
||||
if (vnode.attrs.readonly != undefined && vnode.attrs.readonly == true) {
|
||||
attributes['readonly'] = "true"
|
||||
}
|
||||
if (vnode.attrs.width != undefined) {
|
||||
width = vnode.attrs.width
|
||||
}
|
||||
attributes['style'] = {'margin-bottom': '5px', 'width': width}
|
||||
if (vnode.attrs.onchanged != undefined) {
|
||||
attributes['onchange'] = vnode.attrs.onchanged
|
||||
}
|
||||
|
||||
|
||||
return m("input.w3-input.w3-border", attributes)
|
||||
}
|
||||
}
|
||||
|
||||
export interface iTextarea {
|
||||
name: string
|
||||
value: any
|
||||
cols?: number
|
||||
rows?: number
|
||||
autofocus?: boolean
|
||||
readonly?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese Komponente stellt ein Html-Textarea Element dar. Der darzustellende Wert wird einfach mit dem Attribut
|
||||
* 'value' übergeben. Der eingegebene Wert muss dann später explizit aus Value abgefragt werden. Es findet also kein
|
||||
* automatisches Binding statt.
|
||||
*/
|
||||
export const Textarea:m.Component<iTextarea> = {
|
||||
onupdate: (vnode) => {
|
||||
if (vnode.attrs.autofocus != undefined && vnode.attrs.autofocus == true) {
|
||||
let e: HTMLElement = vnode.dom as HTMLElement
|
||||
e.focus()
|
||||
}
|
||||
},
|
||||
view: (vnode) => {
|
||||
let attributes = {}
|
||||
attributes['name'] = vnode.attrs.name
|
||||
attributes['value'] = vnode.attrs.value
|
||||
if (vnode.attrs.cols != undefined) {
|
||||
attributes['cols'] = vnode.attrs.cols
|
||||
}
|
||||
if (vnode.attrs.rows != undefined) {
|
||||
attributes['rows'] = vnode.attrs.rows
|
||||
}
|
||||
if (vnode.attrs.readonly != undefined && vnode.attrs.readonly == true) {
|
||||
attributes['readonly'] = vnode.attrs.readonly
|
||||
}
|
||||
attributes['style'] = {'margin-bottom': '5px', "width": '100%'}
|
||||
|
||||
return m("textarea.w3-input.w3-border.w3-mobile", attributes)
|
||||
}
|
||||
}
|
||||
|
||||
export function toDateInputValue(date: Date): string {
|
||||
console.log(date, typeof(date), (date instanceof Date))
|
||||
let result: string = null
|
||||
if (date instanceof Date && date != null) {
|
||||
result = [date.getFullYear(), (date.getMonth() + 1).toString().padStart(2, "0"), date.getDate().toString().padStart(2, "0")].join("-")
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import m from "mithril"
|
||||
|
||||
|
||||
interface iTabDef {
|
||||
tabname: string
|
||||
tabview: m.ComponentTypes
|
||||
}
|
||||
|
||||
type TabDef = [string, m.ComponentTypes]
|
||||
|
||||
export interface iTabView {
|
||||
tabviews: TabDef[]
|
||||
}
|
||||
|
||||
export const TabView: m.ClosureComponent<iTabView> = (vnode) => {
|
||||
let tv = {}
|
||||
vnode.attrs.tabviews.forEach((item: TabDef) => {
|
||||
tv[item[0]] = item[1]
|
||||
})
|
||||
|
||||
let currTab = ""
|
||||
function changeview(e) {
|
||||
currTab = e.target.value
|
||||
}
|
||||
return {
|
||||
view: (vnode) => {
|
||||
return m("div", {style: {display: 'grid', 'grid-template-rows': "auto 1fr", height: '100%'}}, [
|
||||
m("div.w3-bar.w3-theme.w3-border-bottom.w3-border-theme.w3-theme-action.w3-small",
|
||||
Object.keys(tv).map((key) => {
|
||||
if (currTab == "") {currTab = key}
|
||||
let c = (key == currTab) ? ".w3-theme-light" : ""
|
||||
return m("button.w3-bar-item.w3-button" + c, {value: key, onclick: changeview}, key)
|
||||
})
|
||||
),
|
||||
m("div", m(tv[currTab]))
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user