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
+89
-27
No files matched your search
@@ -256,9 +256,13 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
||||
return {value: p.id, label: p.projektname}
|
||||
})
|
||||
// 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(Form, {showButtons: true, onsubmit: (null), oncancel: (null)} as iForm, [
|
||||
// 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("label", "Kunde",
|
||||
m(Select, {
|
||||
name: "kundenId",
|
||||
@@ -303,7 +307,8 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
||||
m("label", "Notiz", m(Textarea, {name: "notiz", value: currentLog.notiz, rows: 4} as iTextarea)),
|
||||
m("label", "Wiedervorlage Datum", m(Input, {name: "wiedervorlageDatum", value: currentLog.wiedervorlageDatum, type: "date"} as iInput)),
|
||||
m("label", m(Input, {name: "erledigt", type: "checkbox", value: currentLog.erledigt} as iInput), "Erledigt")
|
||||
])
|
||||
])
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,15 +51,17 @@ export const kunde: m.ClosureComponent = (vnode) => {
|
||||
const cancel = (e: Event) => {
|
||||
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(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
||||
m("div", {style: {flex: '1', 'min-height': '0'}},
|
||||
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", "Straße", m(Input, {name: "strasse", value: currentKunde.strasse, type: "text"} as iInput)),
|
||||
m("label", "PLZ", m(Input, {name: "plz", value: currentKunde.plz, type: "text"} as iInput)),
|
||||
m("label", "Ort", m(Input, {name: "ort", value: currentKunde.ort, type: "text"} as iInput)),
|
||||
m("label", "Land", m(Input, {name: "land", value: currentKunde.land, type: "text"} as iInput))
|
||||
])
|
||||
])
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,9 +217,10 @@ export const Projekt: m.ClosureComponent = (vnode) => {
|
||||
const kundenOptionen: iSelectOption[] = getKunden().map((k: iKunde) => {
|
||||
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(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
||||
m("div", {style: {flex: '1', 'min-height': '0'}},
|
||||
m(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
||||
m("label", "Kunde",
|
||||
m(Select, {
|
||||
name: "kundenId",
|
||||
@@ -232,7 +233,8 @@ export const Projekt: m.ClosureComponent = (vnode) => {
|
||||
m("label", "Zweck", m(Input, {name: "zweck", value: currentProjekt.zweck, type: "text"} as iInput)),
|
||||
m("label", "Verantwortlicher", m(Input, {name: "verantwortlicher", value: currentProjekt.verantwortlicher, type: "text"} as iInput)),
|
||||
m("label", "Startdatum", m(Input, {name: "startdatum", value: currentProjekt.startdatum, type: "date"} as iInput))
|
||||
])
|
||||
])
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+12
-10
@@ -526,29 +526,30 @@ export const Tag: m.ClosureComponent = (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(Form, {showButtons: true, onsubmit: (null), oncancel: (null)} as iForm, [
|
||||
m("div", {style: {flex: '1', 'min-height': '0'}},
|
||||
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", "Beschreibung", m(Input, {name: "description", value: currentTag.description, type: "text"} as iInput)),
|
||||
|
||||
|
||||
// Eltern-Tag-Auswahl
|
||||
m("label", "Übergeordneter Tag (optional)",
|
||||
m("select.w3-select", {name: "parentId", value: currentTag.parentId || ""},
|
||||
m("option", {value: ""}, "-- Kein übergeordneter Tag --"),
|
||||
allTags.map(tag =>
|
||||
allTags.map(tag =>
|
||||
m("option", {value: tag.id, selected: currentTag.parentId === tag.id}, tag.name)
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
|
||||
m("label", "Farbe"),
|
||||
m("div", {style: {display: 'flex', flexWrap: 'wrap', gap: '8px', marginBottom: '12px'}},
|
||||
colorPalette.map(color =>
|
||||
m("div", {style: {display: 'flex', flexWrap: 'wrap', gap: '8px', marginBottom: '12px'}},
|
||||
colorPalette.map(color =>
|
||||
m("button", {
|
||||
style: {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
backgroundColor: color,
|
||||
border: currentTag.color === color ? '2px solid black' : '1px solid #ccc',
|
||||
borderRadius: '4px',
|
||||
@@ -561,7 +562,8 @@ export const Tag: m.ClosureComponent = (vnode) => {
|
||||
m(Input, {name: "color", value: currentTag.color, 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)),
|
||||
])
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,6 +6,10 @@ export interface iForm {
|
||||
onsubmit: (form:HTMLFormElement) => boolean
|
||||
oncancel: (e:Event) => void
|
||||
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 */
|
||||
onformcreate?: (form:HTMLFormElement) => void
|
||||
}
|
||||
@@ -14,6 +18,8 @@ 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
|
||||
// Default: Buttons bleiben fixiert am unteren Rand (stickyButtons = true).
|
||||
let stickyButtons: boolean = vnode.attrs.stickyButtons == undefined ? true : vnode.attrs.stickyButtons
|
||||
function canCloseForm(typ: string = "") {
|
||||
if (!isDirty) {
|
||||
return true
|
||||
@@ -74,6 +80,48 @@ export const Form:m.ClosureComponent<iForm> = (vnode) => {
|
||||
console.log(message)
|
||||
},
|
||||
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", {
|
||||
onreset: () => {console.log("reset")},
|
||||
onsubmit: submit,
|
||||
@@ -88,13 +136,7 @@ export const Form:m.ClosureComponent<iForm> = (vnode) => {
|
||||
}
|
||||
},
|
||||
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
|
||||
buttons
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user