Grid
Das Grid-Modul ermöglicht es dir, Inhalte präzise in einem von dir festgelegten Raster in Zeilen und Spalten anzuordnen. Da es auf dem CSS-Grid-System basiert, hast du die volle Kontrolle über die Strukturierung. Außerdem haben wir wie immer auf hohe Performance und Flexibilität Wert gelegt.
Unterscheidung zwischen Grid und Layout
Das Layout-Modul nutzt seinen flexiblen Charakter, um Elemente dynamisch entlang einer einzigen Achse – also entweder in einer Reihe oder in einer Spalte – zu verteilen und auszurichten. Das Grid-Modul hingegen spannt ein festes Raster mit Zeilen und Spalten auf, das eine präzise, zweidimensionale Positionierung von Elementen erlaubt.
Funktion und Aussehen
Der Aufbau des Grids besteht aus dem äußeren Grid-Raster und Blocks, also Blöcken, die du in diesem Grid-Raster anordnest. Der Inhalt der Blocks ist frei gestaltbar und kann beispielsweise Text, andere GIP-Module sowie HTML-Elemente enthalten.
Für die Anordnung deiner Blöcke im Grid legst du zunächst die Hauptrichtung fest (horizontal / vertical), in der die Blöcke automatisch angeordnet werden.
- direction: "horizontal": Deine Blöcke werden in horizontalen Zeilen angeordnet und fließen von links nach rechts.
- direction: "vertical": Deine Blöcke werden in vertikalen Spalten angeordnet und fließen von oben nach unten.
Sobald das Ende einer Zeile / Spalte erreicht ist, brechen die Blöcke in die nächste Zeile / Spalte um. Damit das Grid weiß, wie viele Block-Elemente in eine Zeile / Spalte passen, definierst du sogenannte Tracks.
- direction: "horizontal": Dein Track sind die Spalten des Grids.
- direction: "vertical": Dein Track sind die Zeilen des Grids.
Für Tracks legst du mindestens eine der folgenden Dimensionen fest: Anzahl der Tracks und Weite jedes Tracks. Du kannst eine feste Anzahl von Tracks mit einer variablen Breite festlegen. Oder du legst eine variable Anzahl von Tracks mit festen Breiten fest. Auch eine feste Anzahl mit festen Breiten ist natürlich möglich. Du kannst aber nicht beides (Anzahl und Weite) variabel machen, da dem Grid dann die Info fehlt, wann es umbrechen soll.
In den folgenden Schaubildern haben wir dasselbe Array mit Tiernamen (["Koala", "Panda", "Tiger", "Zebra", "Bison"]) verwendet um drei verschiedene Darstellungen zu erzielen. Sie unterscheiden sich nur in der Hauptausrichtung und der Track-Anzahl.
Variante 1: Ein horizontales Grid mit 3 Tracks
{
direction: "horizontal",
gridTrackCount: 3,
block: animalArray.[{
value: animal
}]
}Variante 2: Ein horizontales Grid mit 4 Tracks
{
direction: "horizontal",
gridTrackCount: 4,
block: animalArray.[{
value: animal
}]
}Variante 3: Ein vertikales Grid mit 3 Tracks
{
direction: "vertical",
gridTrackCount: 3,
block: animalArray.[{
value: animal
}]
}Du hast außerdem die Möglichkeit, dein Grid und die Blocks unabhängig voneinander zu stylen. Nutzer dafür einfach die style- und class-Keys. Ein besonderer Vorteil des Grids ist außerdem die Möglichkeit, mit einfachen Styling-Angaben einzelne Blöcke über mehrere Zeilen oder mehrere Spalten zu spannen.
Nutzer-Interaktion
Sowohl dem Grid selbst als auch den Blocks kannst du eine beliebige Anzahl an Actions zuweisen. Jede Action besteht aus einem auslösenden Trigger und einem Array von Scripts, die bei Auslösen des Triggers ausgeführt werden. Als Trigger können Klick, Mausbewegung und alle weiteren HTML Mouse Events eingesetzt werden. Je nach Anwendung können auch andere HTML Events funktionieren. Eine Liste solcher Events findest du beispielsweise hier. Weitere Infos zu Actions findest du in diesem Teil unserer Doku.
Modulcode
GIP_grid({
uniqueId: "myUniqueName" + Nr,
embedded: true,
gridTrackCount: "auto-fit",
gridTrackSize: "100px",
direction: "horizontal",
blocks: [{
value: *any*,
}],
})GIP_grid({
uniqueId: "myUniqueName" + Nr,
embedded: true,
gridTrackCount: "auto-fit",
gridTrackSize: "100px",
direction: "horizontal",
style: "",
blocks: [{
style: "",
value: *any*,
}],
})GIP_grid({
uniqueId: "myUniqueName" + Nr,
embedded: true,
gridTrackCount: "auto-fit",
gridTrackSize: "100px",
direction: "horizontal",
class: "",
style: "",
blocks: [{
class: "",
style: "",
value: *any*,
actions: [{
trigger: "click",
scripts: [{
type: "",
}],
}],
}],
actions: [{
trigger: "click",
scripts: [{
type: "",
}],
}],
})GIP_grid({
uniqueId: "",
embedded: {
height: "",
styleHtml: "",
styleStri: "",
},
gridTrackCount: "" / 0,
gridTrackSize: "",
direction: "",
class: "",
style: "",
blocks: [{
class: "",
style: "",
value: *any*,
actions: [{
trigger: "",
scripts: [{
type: "",
}],
}],
}],
actions: [{
trigger: "",
scripts: [{
type: "",
}],
}],
})html( raw(GIP_master({})) +
raw(GIP_grid({
uniqueId: "",
embedded: {
height: "",
styleHtml: "",
styleStri: "",
},
gridTrackCount: "" / 0,
gridTrackSize: "",
direction: "",
class: "",
style: "",
blocks: [{
class: "",
style: "",
value: *any*,
actions: [{
trigger: "",
scripts: [{
type: "",
}],
}],
}],
actions: [{
trigger: "",
scripts: [{
type: "",
}],
}],
})
))Key-Table
Beispiele
Grid: Basis Horizontal
Beschreibung
Wir möchten ein horizontales Grid erstellen, dass eine Track-Breite, also eine Spaltenbreite, von mindestens 150px und maximal 1fr hat. Das Grid soll automatisch so viele Tracks wie möglich pro Zeile erzeugen und automatisch in die nächste Zeile umbrechen.
Für diese Anforderung können wir größtenteils die Default-Einstellungen des Grids nutzen. Dort ist für die Tracks, also Spalten des Grids, eine Breite zwischen 150px und 1fr festgelegt durch minmax(150px, 1fr). Zusätzlich sagt repeat(auto-fit, ...), dass wir so viele Spalten wie möglich mit dieser Spaltenbreite pro Zeile erzeugen wollen. Für die Zeilen ist mit auto-flow definiert, dass diese automatisch erzeugt werden sollen.
grid: auto-flow / repeat(auto-fit, minmax(150px, 1fr));Wir vergeben zusätzliche eine Höhe für das Grid. Wenn mehr Zeilen erzeugt werden, als in diese Höhe passen, wird das Grid scrollbar. Als Inhalt der Grid-Blöcke wird hier beispielhaft das GIP-Card-Modul verwendet.
Code des Beispiels
let speedTestData := ((select '4. Data for Speedtest' where number(Nr) < 20) order by -number(Nr));
let data := {
uniqueId: "Basis Horizontal" + Nr,
embedded: false,
direction: "horizontal",
class: "",
style: "height: 900px;",
blocks: speedTestData.{
class: "",
style: "",
value: GIP_card({
uniqueId: "Basis Horizontal" + Nr,
embedded: true,
direction: "vertical",
class: "",
style: "",
blocks: [{
style: "",
value: GIP_materialSymbols({
icon: "spa"
})
}, {
style: "",
value: 'Input Text 1'
}],
actions: [{
trigger: "click",
scripts: [{
type: "popupRecord",
recordId: raw(Nr)
}]
}]
})
}
};
html(raw(GIP_master({})) + raw(GIP_grid(data)))let speedTestData := ((select '4. Data for Speedtest' where number(Nr) < 20) order by -number(Nr));
GIP_grid({
uniqueId: "Basis Horizontal" + Nr,
embedded: true,
direction: "horizontal",
class: "",
style: "height: 900px;",
blocks: speedTestData.{
class: "",
style: "",
value: GIP_card({
uniqueId: "Basis Horizontal" + Nr,
embedded: true,
direction: "vertical",
class: "",
style: "",
blocks: [{
style: "",
value: GIP_materialSymbols({
icon: "spa"
})
}, {
style: "",
value: 'Input Text 1'
}],
actions: [{
trigger: "click",
scripts: [{
type: "popupRecord",
recordId: raw(Nr)
}]
}]
})
}
})Grid: Basis Vertikal
Beschreibung
Wir möchten ein vertikales Grid erstellen, dass eine Track-Weite, also eine Zeilenhöhe, von mindestens 150px und maximal 1fr hat. Das Grid soll automatisch so viele Tracks wie möglich pro Spalte erzeugen und automatisch in die nächste Spalte umbrechen.
Für diese Anforderung können wir größtenteils die Default-Einstellungen des Grids nutzen. Dort ist für die Tracks, also Zeilen des Grids, eine Höhe zwischen 150px und 1fr festgelegt durch minmax(150px, 1fr). Zusätzlich sagt repeat(auto-fit, ...), dass wir so viele Zeilen wie möglich mit dieser Zeilenhöhe pro Spalte erzeugen wollen. Für die Spalten ist mit auto-flow definiert, dass diese automatisch erzeugt werden sollen.
grid: repeat(auto-fit, minmax(150px, 1fr)) / auto-flow;Die Höhe des Ninox-Formelfeldes schrumpft durch embedded: false in diesem Beispiel immer auf die Track-Höhe zusammen. In einem Praxisbeispiel würde man entweder eine feste Höhe für das Grid vergeben oder es in einem Container einbetten, dessen Höhe variiert werden kann. Als Inhalt der Grid-Blöcke wird hier beispielhaft das GIP-Card-Modul verwendet.
Code des Beispiels
let speedTestData := ((select '4. Data for Speedtest' where number(Nr) < 20) order by -number(Nr));
let data := {
uniqueId: "Basis Horizontal" + Nr,
embedded: false,
direction: "vertical",
class: "",
style: "",
blocks: speedTestData.{
class: "",
style: "",
value: GIP_card({
uniqueId: "Basis Horizontal" + Nr,
embedded: true,
direction: "vertical",
class: "",
style: "height: 100%; width: 150px;",
blocks: [{
style: "",
value: GIP_materialSymbols({
icon: "spa"
})
}, {
style: "",
value: 'Input Text 1'
}],
actions: [{
trigger: "click",
scripts: [{
type: "popupRecord",
recordId: raw(Nr)
}]
}]
})
}
};
html(raw(GIP_master({})) + raw(GIP_grid(data)))let speedTestData := ((select '4. Data for Speedtest' where number(Nr) < 20) order by -number(Nr));
GIP_grid({
uniqueId: "Basis Horizontal" + Nr,
embedded: true,
direction: "vertical",
class: "",
style: "",
blocks: speedTestData.{
class: "",
style: "",
value: GIP_card({
uniqueId: "Basis Horizontal" + Nr,
embedded: true,
direction: "vertical",
class: "",
style: "height: 100%; width: 150px;",
blocks: [{
style: "",
value: GIP_materialSymbols({
icon: "spa"
})
}, {
style: "",
value: 'Input Text 1'
}],
actions: [{
trigger: "click",
scripts: [{
type: "popupRecord",
recordId: raw(Nr)
}]
}]
})
}
})Grid: Track-Anzahl und -Weite
Beschreibung
Wir möchten ein Grid erstellen, in dem wir 5 Tracks mit jeweils einer Breite von 200px haben.
Die Anzahl der Tracks übergeben wir mit gridTrackCount: 5 und die Breite (Weite) der Tracks mit gridTrackSize: "200px". Diese Angaben werden vom GIP-Grid zu folgendem CSS-Ausdruck umgebaut:
grid: auto-flow / repeat(5, 200px);Mit diesem Styling platziert das Grid in jeder Zeile 5 Blöcke mit je 200px Breite. Sollte die zur Verfügung stehende Breite nicht ausreichen, wird das Grid scrollbar.
Code des Beispiels
let speedTestData := ((select '4. Data for Speedtest' where number(Nr) < 20) order by -number(Nr));
let data := {
uniqueId: "Fixed Tracks" + Nr,
embedded: false,
direction: "horizontal",
gridTrackCount: 5,
gridTrackSize: "200px",
class: "",
style: "height: 900px;",
blocks: speedTestData.{
class: "",
style: "",
value: GIP_card({
uniqueId: "Fixed Tracks" + Nr,
embedded: true,
direction: "vertical",
class: "",
style: "",
blocks: [{
style: "",
value: GIP_materialSymbols({
icon: "spa"
})
}, {
style: "",
value: 'Input Text 1'
}],
actions: [{
trigger: "click",
scripts: [{
type: "popupRecord",
recordId: raw(Nr)
}]
}]
})
}
};
html(raw(GIP_master({})) + raw(GIP_grid(data)))let speedTestData := ((select '4. Data for Speedtest' where number(Nr) < 20) order by -number(Nr));
GIP_grid({
uniqueId: "Fixed Tracks" + Nr,
embedded: true,
direction: "horizontal",
gridTrackCount: 5,
gridTrackSize: "200px",
class: "",
style: "height: 900px;",
blocks: speedTestData.{
class: "",
style: "",
value: GIP_card({
uniqueId: "Fixed Tracks" + Nr,
embedded: true,
direction: "vertical",
class: "",
style: "",
blocks: [{
style: "",
value: GIP_materialSymbols({
icon: "spa"
})
}, {
style: "",
value: 'Input Text 1'
}],
actions: [{
trigger: "click",
scripts: [{
type: "popupRecord",
recordId: raw(Nr)
}]
}]
})
}
})Table: Pagination Bar
Beschreibung
Bei großen Datenmengen in Tabellen möchten wir die Einträge auf mehrere Seiten aufteilen, um die Performance zu verbessern und die Übersichtlichkeit zu erhöhen. Dafür paginieren wir die Tabellen-Einträge und nutzen eine Pagination Bar zur Navigation.
Um die Einträge aufzuteilen, benötigen wir die Informationen über die aktuelle Seite und die Anzahl der Einträge pro Seite. Diese Werte lesen wir aus Ninox-Zahlfeldern aus, welche später über die Pagination Bar aktualisiert werden. Die Filterung der Daten erfolgt performant über die Ninox-Funktion slice():
let projectTasks := (select Aufgaben);
let currentPage := 'Pagination: Aktuelle Seite';
if not currentPage then currentPage := 1 end; // Falls das Feld nicht gsetzt ist, wird Seite 1 als Default übergeben
let itemsPerPage := 'Pagination: Einträge pro Seite';
if not itemsPerPage then itemsPerPage := 10 end; // Falls das Feld nicht gsetzt ist, wird 10 als Default übergeben. Kann angepasst werden.
[...]
let projectTasksCurrentPage := slice(projectTasks, (currentPage - 1) * itemsPerPage, currentPage * itemsPerPage);Die Pagination Bar wird mit GIP-Modulen aufgebaut, wodurch Design und Funktionsumfang flexibel anpassbar sind. In diesem Beispiel ist sie in eine Ninox-Funktion ausgelagert. Dies ermöglicht eine einfache Wiederverwendung in verschiedenen Tabellen oder die zentrale Hinterlegung im globalen Code.
Ein Kernbestandteil ist die Berechnung der anzuzeigenden Seitenzahlen. Um bei vielen Seiten die Übersicht zu wahren, werden nur die erste und letzte Seite sowie die aktuelle Seite und deren Nachbarn angezeigt. Dazwischen dienen Punkte als Platzhalter. Diese Logik basiert auf der berechneten maximalen Seitenzahl und der aktuellen Seite:
let pageNavigationItems := switch true do
// case 1: Es gibt mehr als 5 Seiten. Die aktuelle Seite ist kleiner / gleich 3. Die akltuelle Seite ist nicht größer / gleich max. Seitenzahl - 2.
case maxPageNumber > 5 and currentPage <= 3 and not currentPage >= maxPageNumber - 2:
[1, 2, 3, 4, 5, maxPageNumber]
// case 2: Es gibt mehr als 5 Seiten. Die aktuelle Seite ist größer als 3. Die akltuelle Seite ist größer / gleich max. Seitenzahl - 2.
case maxPageNumber > 5 and not currentPage <= 3 and currentPage >= maxPageNumber - 2:
[1, maxPageNumber - 4, maxPageNumber - 3, maxPageNumber - 2, maxPageNumber - 1, maxPageNumber]
// case 3: Es gibt mehr als 5 Seiten. Die aktuelle Seite ist größer 3. Die akltuelle Seite ist nicht größer / gleich max. Seitenzahl - 2.
case maxPageNumber > 5 and not currentPage <= 3 and not currentPage >= maxPageNumber - 2:
[1, currentPage - 1, currentPage, currentPage + 1, maxPageNumber]
// case 4: Die max. Seitenzahl ist kleiner / gleich 1.
case maxPageNumber <= 1:
[1]
// default case: Es gibt nicht mehr als 5 Seiten. Alle Seitenzahlen werden angezeigt.
default:
array(array([1], for pageNumber in range(2, maxPageNumber) do
pageNumber
end), [maxPageNumber])
end;
pageNavigationItems := sort(unique(pageNavigationItems));In der Pagination Bar sorgt ein GIP_select für die Schnellauswahl der Einträge pro Seite. Bei einer Änderung wird die aktuelle Seite automatisch auf 1 zurückgesetzt. Die Seitennavigation wird über ein GIP_grid realisiert, wobei jeder Block eine Action zum Update der Seitenzahl enthält.
Die Integration erfolgt im footer der Tabelle. Mithilfe des CSS-Styles grid-column: 1 / -1 erstreckt sich die Bar über die gesamte Tabellenbreite:
footerColumns: [{
style: "grid-column: 1 / -1;",
value: gipPaginationBar({
myRecordId: Nr,
currentPage: currentPage,
fieldId_currentPage: fieldId(this, "Pagination: Aktuelle Seite"),
itemsPerPage_options: itemsPerPage_options,
itemsPerPage: itemsPerPage,
fieldId_itemsPerPage: fieldId(this, "Pagination: Einträge pro Seite"),
cntAllEntries: cnt(projectTasks)
})
}]Durch die Auslagerung in von gipPaginationBar() in den globalen Ninox-Code kann die Pagination Bar einfach in verschiedenen Tabellen wiederverwendet werden. Sie muss im globalen Code nach den GIP-Modulen definiert werden.
Code des Beispiels
---
################# Pagination Bar als Ninox-Funktion; Kann auch in den globalen Code ausgelagert werden. Dabei ist zu beachten, dass diese Funktion NACH den GIP-Modulen stehen muss #################
---;
function gipPaginationBar(data : any) do
---
An die Funktion übergebene Daten
---;
let myRecordId := data.myRecordId;
let currentPage := data.number(currentPage);
let fieldId_currentPage := data.fieldId_currentPage;
let itemsPerPage_options := data.parseJSON(text(itemsPerPage_options));
let itemsPerPage := data.number(itemsPerPage);
let fieldId_itemsPerPage := data.fieldId_itemsPerPage;
let cntAllEntries := data.number(cntAllEntries);
---
Berechnung der verfügbaren Seitenzahlen
---;
let maxPageNumber := ceil(cntAllEntries / itemsPerPage);
let pageNavigationItems := switch true do
case maxPageNumber > 5 and currentPage <= 3 and not currentPage >= maxPageNumber - 2:
[1, 2, 3, 4, 5, maxPageNumber]
case maxPageNumber > 5 and not currentPage <= 3 and currentPage >= maxPageNumber - 2:
[1, maxPageNumber - 4, maxPageNumber - 3, maxPageNumber - 2, maxPageNumber - 1, maxPageNumber]
case maxPageNumber > 5 and not currentPage <= 3 and not currentPage >= maxPageNumber - 2:
[1, currentPage - 1, currentPage, currentPage + 1, maxPageNumber]
case maxPageNumber <= 1:
[1]
default:
array(array([1], for pageNumber in range(2, maxPageNumber) do
pageNumber
end), [maxPageNumber])
end;
pageNavigationItems := sort(unique(pageNavigationItems));
let lastActivePageNumber := 0;
---
Bau der Pagination Bar mit GIP-Modulen. Design und Inhalte können nach Belieben angepasst werden.
---;
html("<style>
.GIPPaginationBar[data-direction='horizontal'] {
grid-template-columns: 200px 1fr 200px;
grid-template-rows: 1fr;
place-items: center;
}
.GIPPaginationBarPageItems[data-direction='horizontal'] {
grid: none / repeat(auto-fit, 32px);
justify-content: center;
gap: 4px;
height: 34px;
place-content: center;
}
.GIPPaginationBarPageItems[data-direction='horizontal'] .GIPGridBlock {
align-content: center;
text-align: center;
height: 32px;
font-size: 14px;
}
.GIPPaginationBarCurrentPage {
font-weight: 700;
border: 1px solid #737B91;
border-radius: 3px;
}
.GIPPaginationBarAdditionalInfo {
align-items: center;
align-content: center;
font-size: 13px;
font-weight: 500;
color: #6b7280;
justify-content: flex-end;
}
</style>" +
raw(GIP_grid({
uniqueId: "PaginationBar" + myRecordId,
embedded: true,
direction: "horizontal",
class: "GIPPaginationBar",
blocks: [{
value: GIP_select({
uniqueId: "ItemsPerPage" + myRecordId,
embedded: true,
selectType: "single",
recordId: myRecordId,
fieldId: fieldId_itemsPerPage,
label: {
style: "color: #6b7280; font-size: 13px; font-weight: 500;",
value: "Anzahl Einträge",
orientation: "left"
},
combobox: {
style: "width: 80px; height: 34px;",
expandOnAll: true,
collapseAfterSelect: true,
placeholder: false
},
selectContainer: {
style: "text-align: right;",
itemGroup: {
iconSelected: false,
iconUnselected: false,
selectOnAll: true,
selectedItems: itemsPerPage,
selectableItems: for i in itemsPerPage_options do
i
end[!= number(itemsPerPage)],
items: for itemsPerPageOption in itemsPerPage_options do
{
itemId: itemsPerPageOption,
value: itemsPerPageOption,
style: "padding-right: 20px;",
actions: [{
trigger: "click",
scripts: [{
type: "update",
recordId: myRecordId,
fieldId: fieldId_currentPage,
value: 1
}]
}]
}
end
}
}
})
}, {
style: "align-content: center;",
value: GIP_grid({
uniqueId: "PageItems" + myRecordId,
embedded: true,
class: "GIPPaginationBarPageItems",
direction: "horizontal",
blocks: let arrowsLeft := [{
value: GIP_materialSymbols({
embedded: true,
icon: "keyboard_double_arrow_left"
}),
actions: [{
trigger: "click",
scripts: [{
type: "update",
recordId: myRecordId,
fieldId: fieldId_currentPage,
value: 1
}]
}]
}, {
value: GIP_materialSymbols({
embedded: true,
icon: "keyboard_arrow_left"
}),
actions: if currentPage > 1 then
[{
trigger: "click",
scripts: [{
type: "update",
recordId: myRecordId,
fieldId: fieldId_currentPage,
value: currentPage - 1
}]
}]
end
}];
let pageNavigation := slice([{}], 0, 0);
for pageNumber in pageNavigationItems do
if pageNumber > lastActivePageNumber + 1 then
pageNavigation := array(pageNavigation, [{
value: "..."
}])
end;
pageNavigation := array(pageNavigation, [{
class: if pageNumber = currentPage then
"GIPPaginationBarCurrentPage"
end,
value: pageNumber,
actions: [{
trigger: "click",
scripts: [{
type: "update",
recordId: myRecordId,
fieldId: fieldId_currentPage,
value: pageNumber
}]
}]
}]);
lastActivePageNumber := pageNumber
end;
let arrowsRight := [{
value: GIP_materialSymbols({
embedded: true,
icon: "keyboard_arrow_right"
}),
actions: if currentPage < maxPageNumber then
[{
trigger: "click",
scripts: [{
type: "update",
recordId: myRecordId,
fieldId: fieldId_currentPage,
value: if currentPage < maxPageNumber then
currentPage + 1
end
}]
}]
end
}, {
value: GIP_materialSymbols({
embedded: true,
icon: "keyboard_double_arrow_right"
}),
actions: [{
trigger: "click",
scripts: [{
type: "update",
recordId: myRecordId,
fieldId: fieldId_currentPage,
value: maxPageNumber
}]
}]
}];
array(array(arrowsLeft, pageNavigation), arrowsRight)
})
}, {
class: "GIPPaginationBarAdditionalInfo",
value: let lastItemNo := currentPage * itemsPerPage;
join([text((currentPage - 1) * itemsPerPage + 1), "-", if lastItemNo < cntAllEntries then
text(currentPage * itemsPerPage)
else
text(cntAllEntries)
end, "von", text(cntAllEntries), "Einträgen"], " ")
}]
})))
end;
---
################################## Einträge Filtern und Tabelle bauen ##################################
---;
let projectTasks := (select Aufgaben);
let currentPage := 'Pagination: Aktuelle Seite';
if not currentPage then currentPage := 1 end;
let itemsPerPage := 'Pagination: Einträge pro Seite';
if not itemsPerPage then itemsPerPage := 10 end;
let itemsPerPage_options := [10, 20, 30];
let projectTasksCurrentPage := slice(projectTasks, (currentPage - 1) * itemsPerPage, currentPage * itemsPerPage);
html(raw(GIP_master({})) +
raw(GIP_table({
uniqueId: "MitPaginationBar" + Nr,
embedded: false,
class: "",
style: "height: 500px;",
header: {
class: "",
style: "",
headerColumns: [{
style: "",
columnWidth: "3fr",
value: "Kunde"
}, {
style: "",
columnWidth: "2fr",
value: "Projekt"
}, {
style: "",
columnWidth: "2fr",
value: "Aufgabe"
}, {
style: "",
columnWidth: "1fr",
value: "Verantwortlicher"
}, {
style: "",
columnWidth: "1fr",
value: "Priorität"
}, {
style: "",
columnWidth: "1fr",
value: "Status"
}]
},
body: {
class: "",
style: "",
rowHeight: "40px",
placeholder: {
style: "",
value: "Es wurden keine Daten gefunden, die auf die aktuellen Filter zutreffen."
},
rows: for task in projectTasksCurrentPage do
task.{
style: "",
columns: [{
style: "",
hoverElement: if Projekte.Kunde then
{
class: "",
style: "",
value: "Kunde öffnen",
actions: [{
trigger: "click",
scripts: [{
type: "popup",
recordId: Projekte.Kunde.Nr
}]
}]
}
end,
value: Projekte.Kunde.Kundenname
}, {
style: "",
hoverElement: if Projekte then
{
class: "",
style: "",
value: "Projekt öffnen",
actions: [{
trigger: "click",
scripts: [{
type: "popup",
recordId: Projekte.Nr
}]
}]
}
end,
value: Projekte.Projektname
}, {
style: "",
hoverElement: {
class: "",
style: "",
value: "Aufgabe öffnen",
actions: [{
trigger: "click",
scripts: [{
type: "popup",
recordId: Nr
}]
}]
},
value: Bezeichnung
}, {
style: "",
value: Verantwortlicher.Name
}, {
style: "",
value: text('Priorität')
}, {
style: "",
value: Status.Bezeichnung
}]
}
end
},
footer: {
class: "",
style: "border-bottom: 0;",
footerColumns: [{
style: "grid-column: 1 / -1;",
value: gipPaginationBar({
myRecordId: Nr,
currentPage: currentPage,
fieldId_currentPage: fieldId(this, "Pagination: Aktuelle Seite"),
itemsPerPage_options: itemsPerPage_options,
itemsPerPage: itemsPerPage,
fieldId_itemsPerPage: fieldId(this, "Pagination: Einträge pro Seite"),
cntAllEntries: cnt(projectTasks)
})
}]
}
})
))---
################# Pagination Bar als Ninox-Funktion; Kann auch in den globalen Code ausgelagert werden. Dabei ist zu beachten, dass diese Funktion NACH den GIP-Modulen stehen muss #################
---;
function gipPaginationBar(data : any) do
---
An die Funktion übergebene Daten
---;
let myRecordId := data.myRecordId;
let currentPage := data.number(currentPage);
let fieldId_currentPage := data.fieldId_currentPage;
let itemsPerPage_options := data.parseJSON(text(itemsPerPage_options));
let itemsPerPage := data.number(itemsPerPage);
let fieldId_itemsPerPage := data.fieldId_itemsPerPage;
let cntAllEntries := data.number(cntAllEntries);
---
Berechnung der verfügbaren Seitenzahlen
---;
let maxPageNumber := ceil(cntAllEntries / itemsPerPage);
let pageNavigationItems := switch true do
case maxPageNumber > 5 and currentPage <= 3 and not currentPage >= maxPageNumber - 2:
[1, 2, 3, 4, 5, maxPageNumber]
case maxPageNumber > 5 and not currentPage <= 3 and currentPage >= maxPageNumber - 2:
[1, maxPageNumber - 4, maxPageNumber - 3, maxPageNumber - 2, maxPageNumber - 1, maxPageNumber]
case maxPageNumber > 5 and not currentPage <= 3 and not currentPage >= maxPageNumber - 2:
[1, currentPage - 1, currentPage, currentPage + 1, maxPageNumber]
case maxPageNumber <= 1:
[1]
default:
array(array([1], for pageNumber in range(2, maxPageNumber) do
pageNumber
end), [maxPageNumber])
end;
pageNavigationItems := sort(unique(pageNavigationItems));
let lastActivePageNumber := 0;
---
Bau der Pagination Bar mit GIP-Modulen. Design und Inhalte können nach Belieben angepasst werden.
---;
html("<style>
.GIPPaginationBar[data-direction='horizontal'] {
grid-template-columns: 200px 1fr 200px;
grid-template-rows: 1fr;
place-items: center;
}
.GIPPaginationBarPageItems[data-direction='horizontal'] {
grid: none / repeat(auto-fit, 32px);
justify-content: center;
gap: 4px;
height: 34px;
place-content: center;
}
.GIPPaginationBarPageItems[data-direction='horizontal'] .GIPGridBlock {
align-content: center;
text-align: center;
height: 32px;
font-size: 14px;
}
.GIPPaginationBarCurrentPage {
font-weight: 700;
border: 1px solid #737B91;
border-radius: 3px;
}
.GIPPaginationBarAdditionalInfo {
align-items: center;
align-content: center;
font-size: 13px;
font-weight: 500;
color: #6b7280;
justify-content: flex-end;
}
</style>" +
raw(GIP_grid({
uniqueId: "PaginationBar" + myRecordId,
embedded: true,
direction: "horizontal",
class: "GIPPaginationBar",
blocks: [{
value: GIP_select({
uniqueId: "ItemsPerPage" + myRecordId,
embedded: true,
selectType: "single",
recordId: myRecordId,
fieldId: fieldId_itemsPerPage,
label: {
style: "color: #6b7280; font-size: 13px; font-weight: 500;",
value: "Anzahl Einträge",
orientation: "left"
},
combobox: {
style: "width: 80px; height: 34px;",
expandOnAll: true,
collapseAfterSelect: true,
placeholder: false
},
selectContainer: {
style: "text-align: right;",
itemGroup: {
iconSelected: false,
iconUnselected: false,
selectOnAll: true,
selectedItems: itemsPerPage,
selectableItems: for i in itemsPerPage_options do
i
end[!= number(itemsPerPage)],
items: for itemsPerPageOption in itemsPerPage_options do
{
itemId: itemsPerPageOption,
value: itemsPerPageOption,
style: "padding-right: 20px;",
actions: [{
trigger: "click",
scripts: [{
type: "update",
recordId: myRecordId,
fieldId: fieldId_currentPage,
value: 1
}]
}]
}
end
}
}
})
}, {
style: "align-content: center;",
value: GIP_grid({
uniqueId: "PageItems" + myRecordId,
embedded: true,
class: "GIPPaginationBarPageItems",
direction: "horizontal",
blocks: let arrowsLeft := [{
value: GIP_materialSymbols({
embedded: true,
icon: "keyboard_double_arrow_left"
}),
actions: [{
trigger: "click",
scripts: [{
type: "update",
recordId: myRecordId,
fieldId: fieldId_currentPage,
value: 1
}]
}]
}, {
value: GIP_materialSymbols({
embedded: true,
icon: "keyboard_arrow_left"
}),
actions: if currentPage > 1 then
[{
trigger: "click",
scripts: [{
type: "update",
recordId: myRecordId,
fieldId: fieldId_currentPage,
value: currentPage - 1
}]
}]
end
}];
let pageNavigation := slice([{}], 0, 0);
for pageNumber in pageNavigationItems do
if pageNumber > lastActivePageNumber + 1 then
pageNavigation := array(pageNavigation, [{
value: "..."
}])
end;
pageNavigation := array(pageNavigation, [{
class: if pageNumber = currentPage then
"GIPPaginationBarCurrentPage"
end,
value: pageNumber,
actions: [{
trigger: "click",
scripts: [{
type: "update",
recordId: myRecordId,
fieldId: fieldId_currentPage,
value: pageNumber
}]
}]
}]);
lastActivePageNumber := pageNumber
end;
let arrowsRight := [{
value: GIP_materialSymbols({
embedded: true,
icon: "keyboard_arrow_right"
}),
actions: if currentPage < maxPageNumber then
[{
trigger: "click",
scripts: [{
type: "update",
recordId: myRecordId,
fieldId: fieldId_currentPage,
value: if currentPage < maxPageNumber then
currentPage + 1
end
}]
}]
end
}, {
value: GIP_materialSymbols({
embedded: true,
icon: "keyboard_double_arrow_right"
}),
actions: [{
trigger: "click",
scripts: [{
type: "update",
recordId: myRecordId,
fieldId: fieldId_currentPage,
value: maxPageNumber
}]
}]
}];
array(array(arrowsLeft, pageNavigation), arrowsRight)
})
}, {
class: "GIPPaginationBarAdditionalInfo",
value: let lastItemNo := currentPage * itemsPerPage;
join([text((currentPage - 1) * itemsPerPage + 1), "-", if lastItemNo < cntAllEntries then
text(currentPage * itemsPerPage)
else
text(cntAllEntries)
end, "von", text(cntAllEntries), "Einträgen"], " ")
}]
})))
end;
---
################################## Einträge Filtern und Tabelle bauen ##################################
---;
let projectTasks := (select Aufgaben);
let currentPage := 'Pagination: Aktuelle Seite';
if not currentPage then currentPage := 1 end;
let itemsPerPage := 'Pagination: Einträge pro Seite';
if not itemsPerPage then itemsPerPage := 10 end;
let itemsPerPage_options := [10, 20, 30];
let projectTasksCurrentPage := slice(projectTasks, (currentPage - 1) * itemsPerPage, currentPage * itemsPerPage);
GIP_table({
uniqueId: "MitPaginationBar" + Nr,
embedded: true,
class: "",
style: "height: 500px;",
header: {
class: "",
style: "",
headerColumns: [{
style: "",
columnWidth: "3fr",
value: "Kunde"
}, {
style: "",
columnWidth: "2fr",
value: "Projekt"
}, {
style: "",
columnWidth: "2fr",
value: "Aufgabe"
}, {
style: "",
columnWidth: "1fr",
value: "Verantwortlicher"
}, {
style: "",
columnWidth: "1fr",
value: "Priorität"
}, {
style: "",
columnWidth: "1fr",
value: "Status"
}]
},
body: {
class: "",
style: "",
rowHeight: "40px",
placeholder: {
style: "",
value: "Es wurden keine Daten gefunden, die auf die aktuellen Filter zutreffen."
},
rows: for task in projectTasksCurrentPage do
task.{
style: "",
columns: [{
style: "",
hoverElement: if Projekte.Kunde then
{
class: "",
style: "",
value: "Kunde öffnen",
actions: [{
trigger: "click",
scripts: [{
type: "popup",
recordId: Projekte.Kunde.Nr
}]
}]
}
end,
value: Projekte.Kunde.Kundenname
}, {
style: "",
hoverElement: if Projekte then
{
class: "",
style: "",
value: "Projekt öffnen",
actions: [{
trigger: "click",
scripts: [{
type: "popup",
recordId: Projekte.Nr
}]
}]
}
end,
value: Projekte.Projektname
}, {
style: "",
hoverElement: {
class: "",
style: "",
value: "Aufgabe öffnen",
actions: [{
trigger: "click",
scripts: [{
type: "popup",
recordId: Nr
}]
}]
},
value: Bezeichnung
}, {
style: "",
value: Verantwortlicher.Name
}, {
style: "",
value: text('Priorität')
}, {
style: "",
value: Status.Bezeichnung
}]
}
end
},
footer: {
class: "",
style: "border-bottom: 0;",
footerColumns: [{
style: "grid-column: 1 / -1;",
value: gipPaginationBar({
myRecordId: Nr,
currentPage: currentPage,
fieldId_currentPage: fieldId(this, "Pagination: Aktuelle Seite"),
itemsPerPage_options: itemsPerPage_options,
itemsPerPage: itemsPerPage,
fieldId_itemsPerPage: fieldId(this, "Pagination: Einträge pro Seite"),
cntAllEntries: cnt(projectTasks)
})
}]
}
})