Form-Widget stickyButtons und einheitliches Form-View-Layout
Form-Widget um stickyButtons-Attribut erweitert (default true): nur der Formularinhalt scrollt, die Buttons bleiben fixiert am unteren Rand. Neue CSS-Klasse .form-view-container (flex-Column, volle Höhe) für die umschließenden View-Container. Form-Views (kunde, projekt, call_log, tags) auf w3-auto.form-view-container umgestellt; Tag-Form von w3-twothird auf w3-auto korrigiert. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
1 parent
3accb8f690
commit
28f858f6da
6 files changed
+73
-11
No files matched your search
@@ -256,8 +256,12 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
return {value: p.id, label: p.projektname}
|
return {value: p.id, label: p.projektname}
|
||||||
})
|
})
|
||||||
// Einheit der Dauer-Anzeige im Format mm:ss.
|
// Einheit der Dauer-Anzeige im Format mm:ss.
|
||||||
return m("div.w3-container.w3-auto",
|
return m("div.w3-container.w3-auto.form-view-container",
|
||||||
m("h1", currentLog.id === -1 ? "Neue Gesprächsnotiz anlegen" : "Gesprächsnotiz"),
|
m("h1", currentLog.id === -1 ? "Neue Gesprächsnotiz anlegen" : "Gesprächsnotiz"),
|
||||||
|
// Form füllt den verbleibenden Platz; das Form-Widget selbst
|
||||||
|
// sorgt (mit stickyButtons, Default) dafür, dass nur der
|
||||||
|
// Inhalt scrollt und die Buttons fixiert bleiben.
|
||||||
|
m("div", {style: {flex: '1', 'min-height': '0'}},
|
||||||
m(Form, {showButtons: true, onsubmit: (null), oncancel: (null)} as iForm, [
|
m(Form, {showButtons: true, onsubmit: (null), oncancel: (null)} as iForm, [
|
||||||
m("label", "Kunde",
|
m("label", "Kunde",
|
||||||
m(Select, {
|
m(Select, {
|
||||||
@@ -305,6 +309,7 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
m("label", m(Input, {name: "erledigt", type: "checkbox", value: currentLog.erledigt} as iInput), "Erledigt")
|
m("label", m(Input, {name: "erledigt", type: "checkbox", value: currentLog.erledigt} as iInput), "Erledigt")
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,8 +51,9 @@ export const kunde: m.ClosureComponent = (vnode) => {
|
|||||||
const cancel = (e: Event) => {
|
const cancel = (e: Event) => {
|
||||||
m.route.set("/kundenliste")
|
m.route.set("/kundenliste")
|
||||||
}
|
}
|
||||||
return m("div.w3-container.w3-auto",
|
return m("div.w3-container.w3-auto.form-view-container",
|
||||||
m("h1", currentKunde.id === -1 ? "Neuen Kunden anlegen" : "Kundenstammblatt"),
|
m("h1", currentKunde.id === -1 ? "Neuen Kunden anlegen" : "Kundenstammblatt"),
|
||||||
|
m("div", {style: {flex: '1', 'min-height': '0'}},
|
||||||
m(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
m(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
||||||
m("label", "Kundenname", m(Input, {name: "kundenname", value: currentKunde.name, type: "text", required: true} as iInput)),
|
m("label", "Kundenname", m(Input, {name: "kundenname", value: currentKunde.name, type: "text", required: true} as iInput)),
|
||||||
m("label", "Straße", m(Input, {name: "strasse", value: currentKunde.strasse, type: "text"} as iInput)),
|
m("label", "Straße", m(Input, {name: "strasse", value: currentKunde.strasse, type: "text"} as iInput)),
|
||||||
@@ -61,6 +62,7 @@ export const kunde: m.ClosureComponent = (vnode) => {
|
|||||||
m("label", "Land", m(Input, {name: "land", value: currentKunde.land, type: "text"} as iInput))
|
m("label", "Land", m(Input, {name: "land", value: currentKunde.land, type: "text"} as iInput))
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,8 +217,9 @@ export const Projekt: m.ClosureComponent = (vnode) => {
|
|||||||
const kundenOptionen: iSelectOption[] = getKunden().map((k: iKunde) => {
|
const kundenOptionen: iSelectOption[] = getKunden().map((k: iKunde) => {
|
||||||
return {value: k.id, label: k.name}
|
return {value: k.id, label: k.name}
|
||||||
})
|
})
|
||||||
return m("div.w3-container.w3-auto",
|
return m("div.w3-container.w3-auto.form-view-container",
|
||||||
m("h1", currentProjekt.id === -1 ? "Neues Projekt anlegen" : "Projekt-Stammblatt"),
|
m("h1", currentProjekt.id === -1 ? "Neues Projekt anlegen" : "Projekt-Stammblatt"),
|
||||||
|
m("div", {style: {flex: '1', 'min-height': '0'}},
|
||||||
m(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
m(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
||||||
m("label", "Kunde",
|
m("label", "Kunde",
|
||||||
m(Select, {
|
m(Select, {
|
||||||
@@ -234,6 +235,7 @@ export const Projekt: m.ClosureComponent = (vnode) => {
|
|||||||
m("label", "Startdatum", m(Input, {name: "startdatum", value: currentProjekt.startdatum, type: "date"} as iInput))
|
m("label", "Startdatum", m(Input, {name: "startdatum", value: currentProjekt.startdatum, type: "date"} as iInput))
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -526,8 +526,9 @@ export const Tag: m.ClosureComponent = (vnode) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
view: (vnode) => {
|
view: (vnode) => {
|
||||||
return m("div.w3-container.w3-twothird",
|
return m("div.w3-container.w3-auto.form-view-container",
|
||||||
m("h1", currentTag.id === -1 ? "Neuen Tag erstellen" : "Tag bearbeiten"),
|
m("h1", currentTag.id === -1 ? "Neuen Tag erstellen" : "Tag bearbeiten"),
|
||||||
|
m("div", {style: {flex: '1', 'min-height': '0'}},
|
||||||
m(Form, {showButtons: true, onsubmit: (null), oncancel: (null)} as iForm, [
|
m(Form, {showButtons: true, onsubmit: (null), oncancel: (null)} as iForm, [
|
||||||
m("label", "Tag-Name", m(Input, {name: "name", value: currentTag.name, type: "text"} as iInput)),
|
m("label", "Tag-Name", m(Input, {name: "name", value: currentTag.name, type: "text"} as iInput)),
|
||||||
m("label", "Beschreibung", m(Input, {name: "description", value: currentTag.description, type: "text"} as iInput)),
|
m("label", "Beschreibung", m(Input, {name: "description", value: currentTag.description, type: "text"} as iInput)),
|
||||||
@@ -562,6 +563,7 @@ export const Tag: m.ClosureComponent = (vnode) => {
|
|||||||
m("label", "Icon (optional)", m(Input, {name: "icon", value: currentTag.icon || "", type: "text"} as iInput)),
|
m("label", "Icon (optional)", m(Input, {name: "icon", value: currentTag.icon || "", type: "text"} as iInput)),
|
||||||
m("label", "Sortierreihenfolge (optional)", m(Input, {name: "order", value: currentTag.order?.toString() || "", type: "number"} as iInput)),
|
m("label", "Sortierreihenfolge (optional)", m(Input, {name: "order", value: currentTag.order?.toString() || "", type: "number"} as iInput)),
|
||||||
])
|
])
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ export interface iForm {
|
|||||||
onsubmit: (form:HTMLFormElement) => boolean
|
onsubmit: (form:HTMLFormElement) => boolean
|
||||||
oncancel: (e:Event) => void
|
oncancel: (e:Event) => void
|
||||||
showbuttons?: boolean
|
showbuttons?: boolean
|
||||||
|
/** Wenn true (Default), bleiben die Buttons am unteren Rand fixiert,
|
||||||
|
* während der Formularinhalt scrollt. Bei false scrollen die Buttons
|
||||||
|
* mit dem Formular. */
|
||||||
|
stickyButtons?: boolean
|
||||||
/** Die Funktion gibt das HTMLFormElement zurück, welches in der Form-Componente angelegt wird */
|
/** Die Funktion gibt das HTMLFormElement zurück, welches in der Form-Componente angelegt wird */
|
||||||
onformcreate?: (form:HTMLFormElement) => void
|
onformcreate?: (form:HTMLFormElement) => void
|
||||||
}
|
}
|
||||||
@@ -14,6 +18,8 @@ export const Form:m.ClosureComponent<iForm> = (vnode) => {
|
|||||||
let form: HTMLFormElement
|
let form: HTMLFormElement
|
||||||
let isDirty: boolean = false
|
let isDirty: boolean = false
|
||||||
let showButtons: boolean = vnode.attrs.showbuttons == undefined ? true : vnode.attrs.showbuttons
|
let showButtons: boolean = vnode.attrs.showbuttons == undefined ? true : vnode.attrs.showbuttons
|
||||||
|
// Default: Buttons bleiben fixiert am unteren Rand (stickyButtons = true).
|
||||||
|
let stickyButtons: boolean = vnode.attrs.stickyButtons == undefined ? true : vnode.attrs.stickyButtons
|
||||||
function canCloseForm(typ: string = "") {
|
function canCloseForm(typ: string = "") {
|
||||||
if (!isDirty) {
|
if (!isDirty) {
|
||||||
return true
|
return true
|
||||||
@@ -74,6 +80,48 @@ export const Form:m.ClosureComponent<iForm> = (vnode) => {
|
|||||||
console.log(message)
|
console.log(message)
|
||||||
},
|
},
|
||||||
view: (vnode) => {
|
view: (vnode) => {
|
||||||
|
const buttons = showButtons == true
|
||||||
|
? m("div.w3-bar.w3-padding-16",
|
||||||
|
{
|
||||||
|
style: stickyButtons
|
||||||
|
? {flex: '0 0 auto', position: 'sticky', bottom: '0', 'background-color': 'white'}
|
||||||
|
: {}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
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
|
||||||
|
|
||||||
|
// Bei stickyButtons liegt der Formularinhalt in einem scrollbaren
|
||||||
|
// Container, die Buttons bleiben am unteren Rand fixiert. Das
|
||||||
|
// umschließende form-Element wird zum flex-Container, damit das
|
||||||
|
// Layout auch bei Fenstergrößenänderung stabil bleibt.
|
||||||
|
if (stickyButtons) {
|
||||||
|
return m("form", {
|
||||||
|
style: {display: 'flex', 'flex-direction': 'column', height: '100%'},
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Scrollbarer Formular-Inhalt.
|
||||||
|
m("div", {style: {flex: '1', 'overflow-y': 'auto', 'padding-right': '8px'}},
|
||||||
|
vnode.children
|
||||||
|
),
|
||||||
|
// Fixierter Button-Bereich.
|
||||||
|
buttons
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Ohne stickyButtons: bisheriges Verhalten (Buttons scrollen mit).
|
||||||
return m("form", {
|
return m("form", {
|
||||||
onreset: () => {console.log("reset")},
|
onreset: () => {console.log("reset")},
|
||||||
onsubmit: submit,
|
onsubmit: submit,
|
||||||
@@ -88,13 +136,7 @@ export const Form:m.ClosureComponent<iForm> = (vnode) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
vnode.children,
|
vnode.children,
|
||||||
showButtons == true ?
|
buttons
|
||||||
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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,3 +20,12 @@
|
|||||||
.w3-hover-theme:hover {color:#fff !important; background-color:#9e9e9e !important}
|
.w3-hover-theme:hover {color:#fff !important; background-color:#9e9e9e !important}
|
||||||
.w3-hover-text-theme:hover {color:#9e9e9e !important}
|
.w3-hover-text-theme:hover {color:#9e9e9e !important}
|
||||||
.w3-hover-border-theme:hover {border-color:#9e9e9e !important}
|
.w3-hover-border-theme:hover {border-color:#9e9e9e !important}
|
||||||
|
|
||||||
|
/* Container für Views, die ein Form-Widget mit stickyButtons enthalten:
|
||||||
|
voller Höhe, flex-Column, damit das Form-Widget den Inhalt scrollbar
|
||||||
|
hält und die Buttons fixiert am unteren Rand bleiben. */
|
||||||
|
.form-view-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user