Premium Widgets
Custom Table
Custom Table
AI Defaults
- Column widths
- Every column MUST have explicit `width`. At least one column MUST use `width: "fraction"`.
- Height
- Use `height: "100%"` (not `calc()`). Especially when `embedded: true`.
- uniqueListId
- Required, use `"my-table-" + Nr`.
- tableId
- Required (e.g. from `tableId("Tabellenname")`).
- recordId
- Always raw `Nr`, NEVER `number(Nr)`.
- Empty state
- Use `emptyTable: { title: "...", value: "..." }`, do NOT wrap table in if/else.
- theme.uniqueId
- Must match `uniqueListId`.
- Columns value
- Each column needs `recordId: Nr` inside the mapping.
- Row actions
- Use `actions` array with `type: "popup"` or `type: "update"`, recordId = `Nr`.
- Sort
- Client-side only; `header.columns[n].sort: { enabled: true, type: "text"|"number"|"date" }`; optional `columns[n].sort.value` for sort key when `value` is not plain text. Optional `header.sortIcons: { color, activeColor, hoverColor }` for caret colors (defaults: muted gray / black active / stronger on header hover).
- Hover
- `actions: [{ type: "hover", fontColor, backgroundColor, ... }]` on table root, row, or column; `type: "hover"` is not passed to click handlers. Legacy `rowHoverAction` still supported.
- Borders
- Live under `styles.borders`. Default is a rounded frame + row lines, **no vertical cell lines**. Hide the frame with `styles: { borders: { outer: false } }`. Opt into a grid with `columns: true`. Set `styles.borderColor` for dark backgrounds; a line may be `{ color, width }` instead of a boolean.
- styles hierarchy
- Same `styles` bag on table, header, row, and column — prefer `{ backgroundColor, fontColor, … }` over a CSS string. Table `styles.backgroundColor` is the default surface. A row or column overrides with its own `styles.backgroundColor`. Header uses `header.backgroundColor` / `header.styles`. Do not invent extra props like `rowBackgroundColor`.
Custom Table
El widget Custom Table te permite representar tablas dinámicas e interactivas directamente en tu superficie de Ninox, adaptadas a tus necesidades. Es ideal para mostrar datos estructurados de forma clara, darles formato individual y vincularlos con acciones (p. ej. para editar, eliminar, abrir registros o llamadas a la API).
## Ejemplo:
Código de aplicación completo
A continuación ves un código de aplicación de ejemplo que define la base de tu Custom Table. Como el código puede llegar a ser muy extenso según el caso de uso, te guiamos paso a paso desde una base sencilla hasta variantes más complejas.
La estructura se divide en dos áreas centrales:
data– define el contenido y la estructura de tu tabla (filas y columnas)arcCustomTable()– es la función global que renderiza el widget. A ella le pasas losdata
let current := this;
let projectList := Artikel;
let data := {
uniqueListId: "beispiel" + Nr,
tableId: "",
height: "500px",
minHeight: "",
groupedBy: "",
groupsCollapsed: false,
theme: arcCustomThemeCleanWhite({
uniqueId: "beispiel"
}),
embedded: false,
rowHoverAction: {
hoverActive: true,
backgroundColor: "",
fontColor: ""
},
header: {
showHeader: true,
height: "",
fontColor: "",
backgroundColor: "",
fontSize: "",
columns: [{
title: "Prio",
width: ""
}]
},
emptyTable: {
title: "Keine Ergebnisse",
backgroundColor: "",
value: ""
},
scrollBar: {
showScrollBar: false,
height: "",
backgroundColor: "",
handle: {
height: "",
borderRadius: "",
backgroundColor: ""
}
},
table: projectList.[{
recordId: Nr,
rowColor: "",
rowHeight: "auto",
rowPaddingY: "10px",
groupRowColor: "",
groupRowSettings: {
height: "",
},
columns: [{
field: "",
title: "",
value: "",
color: "",
backgroundColor: "",
width: "",
align: "left",
paddingX: "",
paddingY: "",
groupExpandAction: false,
groupValue: arcCustomIcon({
name: "caret-down"
}),
groupByValue: "",
actions: [{
type: "popup",
showPopupButton: false,
recordId: Nr
}]
}]
}],
footer: {
showFooter: false,
showActionButton: true,
backgroundColor: "",
actionButtonTitle: "",
leftSideContent: "",
rightSideContent: "Gesamt: " + cnt(projectList) + " Projekte"
}
};
arcCustomTable(data)
uniqueListId
El parámetro uniqueListId es la denominación individual de tu tabla. Se encarga de que tu tabla se identifique de forma única internamente, especialmente cuando se muestran varias tablas a la vez en una página.
¿Por qué es importante?
- Los ajustes de estilo (p. ej. colores, tipografías, efectos hover) se aplican de forma específica solo a la tabla con la
uniqueListIdindicada. - Con varias tablas en una vista, sin esto pueden surgir conflictos de clases CSS o de estados.
✅ Buena práctica:
Usa nombres descriptivos y únicos – idealmente en camelCase o con guiones bajos.
uniqueListId: "Projektliste Offen", // Text
tableId
Con tableId indicas el ID interno de la tabla en Ninox al que hace referencia el widget. Este ID se necesita, por ejemplo, para:
- crear nuevos registros mediante el botón «+» en el footer (
create) - poder abrir o editar registros existentes (
popup,openFullscreen,update) - referenciar correctamente las acciones a los registros de esta tabla
tableId: "AB", // Die ID als Textform
tableId: tableId("Kontakte"), // Ninox Funktion zum Herausfinden der Table ID deiner darzustellenden Tabelle
height
Con height determinas la altura del widget de tabla en píxeles. Así puedes adaptar la representación de forma flexible al layout de tu página, sin importar cuántos datos contenga.
💡 Valores
- El valor se indica en píxeles, p. ej.
"300px","500px"o"800px". "auto": la altura se adapta automáticamente al contenido. Perfecto para contenidos dinámicos, pero ten en cuenta posibles saltos en el layout.- Combinado con
maxHeightpuedes limitar la altura automática.
height: "500px", // Pixel Werte
minWidth
Con minWidth fijas el ancho mínimo de tu tabla, independientemente de cuántas columnas o datos contenga. Así te aseguras de que la tabla no se muestre demasiado estrecha, por ejemplo en contenedores estrechos, pestañas o vistas incrustadas.
🔍 Formato
- El valor se pasa como texto con unidad, p. ej.
"600px" - Sin especificarlo, la tabla puede colapsar y volverse estrecha si hay poco contenido, lo que puede afectar el layout.
💡 Nota:
minWidth afecta al contenedor exterior de la tabla, no a los anchos de columna internos. Para las columnas existen opciones separadas como width y minWidth directamente en el bloque columns.
groupedBy
Con groupedBy defines según qué campo (clave de columna o field) debe agruparse tu tabla. Los grupos se muestran visualmente como filas separadas con formato propio — las entradas subyacentes se pueden expandir y contraer.
🔍 Comportamiento
- Aquí indicas el nombre de columna (
field) definido en tu bloquecolumns. - Si el campo está vacío (
") o falta por completo, no se produce agrupación. - La agrupación también puede basarse en valores complejos (p. ej.
Typ,Kategorie,Verantwortlicher, etc.). - Los grupos se pueden expandir y contraer dinámicamente haciendo clic en la fila de grupo; este estado se guarda localmente.
groupedBy: "", // default: keine Gruppierung ausgewählt
groupedby: "Status", // Text des Feldtitels, nach dem gruppiert werden soll
groupedBy: Gruppierung, // Ninox Feld, das angesprochen wird. In diesem Fall: Ein Auswahlfeld, in dem die Optionen den Titel verschiedener Ninox-felder tragen, nach denen gruppiert werden kann. (z.B. Status, Mitarbeiter, Aufgabe)
### groupsCollapsed
Con groupsCollapsed defines si las filas agrupadas se muestran contraídas o expandidas por defecto. El encabezado de grupo (groupRow) siempre es visible; solo las entradas asociadas (valueRows) se muestran u ocultan según sea necesario.
Este parámetro solo actúa cuando groupedBy está establecido.
true = los grupos están contraídos al cargar (vista compacta). false = los grupos están completamente abiertos al cargar.
El estado (expandido/contraído) se guarda localmente, es decir, al volver a abrir, el widget recuerda el último estado por usuario. Valor por defecto: false (los grupos están abiertos) — si no se indica, la tabla muestra todas las entradas de grupo por completo.
groupscollapsed: "", // default: false
groupsCollapsed: true,
embedded
Con embedded activas el modo incrustado de la tabla. En este modo, la tabla se adapta de forma óptima a las estructuras de layout existentes, p. ej. en un contenedor, una pestaña o un área de UI flexible.
Valor por defecto: false — entonces la tabla ocupa su propio espacio fijo en la página.
Si se establece embedded: true, el widget se posiciona de forma relativa en el contenedor en lugar de absoluta y adopta su ancho y, en su caso, otras indicaciones de estilo. Combinado con parámetros como height y minWidth, esto proporciona un aspecto limpio e integrado. embedded es especialmente útil en dashboards, vistas de pestañas o ventanas modales.
embedded: false,
embedded: true,
embedded: "", // default: false
styles.borders
Los bordes y las líneas de cuadrícula viven en styles — junto con el fondo, el radio y el color de línea.
Por defecto: marco exterior redondeado, líneas de fila finas, sin líneas verticales de celda.
Cada campo de línea es true / false o { color, width }.
| Prop | Default | Significado |
|---|---|---|
styles.borderColor | #e4e4e7 | Color para todas las líneas activadas |
styles.borderRadius | 12px | Radio de esquina (solo si outer está activo) |
styles.borders.outer | true | Marco exterior. false = a ras en el layout |
styles.borders.columns | false | Líneas verticales |
styles.borders.rows | true | Líneas horizontales |
styles.borders.color / width | — | Línea común, sobrescribe borderColor / grosor por defecto |
styles: {
borderRadius: "12px",
borderColor: "#e4e4e7",
borders: {
outer: true,
columns: false,
rows: true
}
}
// In einem Layout / Card: styles: { borders: { outer: false } }
// Dunkler Hintergrund — Fläche auf styles, Kopf auf header: styles: { backgroundColor: "#2a2822", fontColor: "#f5f0e6", borderColor: "#3f3c34" }, header: { backgroundColor: "#1c1b16", fontColor: "#f5f0e6" }
// Klassisches Gitter: styles: { borders: { columns: true } }</code></pre>
Variantes ya preparadas (Default, Card, Gitter, flush, Dark): <a href="/documentation/custom-table-look">Custom Table Look</a>.
rowHoverAction
Con el bloque rowHoverAction determinas si el comportamiento hover en las filas de la tabla debe estar activo y, en caso afirmativo, qué colores se usan al pasar el ratón por encima.
hoverActive: true activa el efecto; de otro modo, la fila permanece sin cambios al hacer hover.backgroundColor define el color de fondo de la fila en el estado hover,fontColor establece el color de texto durante el hover.
El valor por defecto de hoverActive es true si el bloque está definido. Sin rowHoverAction, el comportamiento es neutro: sin efectos hover.
Consejo: usa colores discretos para mantener el efecto sutil y agradable para la experiencia de usuario.
rowHoverAction: { hoverActive: true, // wenn hovern an sein soll, und false, wenn nicht
backgroundColor: "#9ca4a9", // default: #f4f6ff
fontColor: "#fff" } // default: #000
Recomendación: para proyectos nuevos usa actions: [{ type: "hover", ... }] (ver más abajo) — rowHoverAction se mantiene por compatibilidad con versiones anteriores.
actions con type: "hover"
Estilo hover y acciones hover opcionales a través del mismo array actions que update / popup. Una entrada con type: "hover" no se pasa a las acciones de clic, sino que solo la evalúa el widget.
Orden de fusión: rowHoverAction (legacy) → actions en la raíz de la tabla → row.actions → columns[n].actions.
Sub-Prop Significado enabledfalse = sin resaltado hover para esta fila/celda.backgroundColorColor de fondo en hover. fontColorColor de texto en hover. stylesCSS adicional (string) para la celda cuando la fila está en hover. actionsOpcional: en mouseenter hacia el manejador de acciones (p. ej. update). leaveActionsOpcional: en mouseleave.
A nivel de fila: actions: [{ type: 'hover', backgroundColor: '#eff6ff', fontColor: '#0f172a' }] en el objeto de fila.
A nivel de columna: p. ej. { type: 'hover', enabled: false } para excluir una columna individual del resaltado hover. row.actions (sin hover) — las entradas adicionales update/popup se aplican a toda la fila (al hacer clic en una celda se ejecutan juntas las acciones de fila y de celda).
header
Con el bloque header controlas la representación de tu fila de encabezado de tabla (títulos de columna).
Comportamiento por defecto: si no se indica showHeader, la fila de encabezado se muestra. Sin más ajustes, se aplican valores por defecto para tamaño, color y fondo.
Consejo: si estableces showHeader: false, el significado de los datos debería seguir siendo claro (posición, esquema de colores de las columnas).
showHeader
Muestra u oculta toda la fila de encabezado.
header: {
showHeader: false, // default: true
}
height
Altura de la fila de encabezado (p. ej. "48px" o "auto").
header: {
height: "70px", // default: 36px
}
fontColor
Color de fuente de los títulos de columna.
header: {
fontColor: "#fff", // default: #000
}
fontSize
Tamaño de fuente de los títulos.
header: {
fontSize: "17px", // default: 12px
}
backgroundColor
Fondo de la fila de encabezado.
header: {
backgroundColor: "#3a4a54", // default: #f8f9fc
}
styles
String CSS para dar estilo libre al contenedor del encabezado (sobrescribe fontColor, backgroundColor, etc.).
header: {
styles: "border-bottom: 1px solid #eee;",
}
Ejemplo completo:
header: {
showHeader: false, // default: true
height: "70px", // default: 36px
fontColor: "#fff", // default: #52525b
backgroundColor: "#3a4a54", // default: #fafafa
fontSize: "17px", // default: 12px
}
sort (Client-Side)
La ordenación se realiza completamente en el navegador (sin actualización de Ninox, sin campo auxiliar). El estado (columna activa, dirección) se gestiona internamente y se persiste, como el scroll/los grupos, en localStorage.
Columna de encabezado — activa sort y establece el tipo:
sort.enabled: true — el encabezado de columna es clicable y muestra iconos de ordenación.sort.type: 'text' (por defecto), 'number' o 'date'.
Celda de fila — valor bruto opcional para la comparación:
sort.value: valor por el que se ordena. Si no se establece, se usa el value de la celda (no adecuado si value contiene widgets/HTML — en ese caso, establece siempre sort.value).
Ciclo de clic: primer clic → ascendente, segundo → descendente, tercero → ordenación desactivada.
Iconos de ordenación (chevrons de contorno): colores opcionales mediante header.sortIcons (se establecen como variables CSS en el encabezado):
Prop Significado Por defecto (CSS-fallback) colorColor de los carets inactivos rgba(0,0,0,0.35)activeColorColor del caret activo (dirección de ordenación actual) #000000hoverColorColor de ambos carets en hover sobre el encabezado ordenable (carets inactivos; el caret activo mantiene activeColor) rgba(0,0,0,0.55)
Con agrupación (groupedBy), la ordenación se aplica solo dentro de cada grupo, no entre grupos.
header: {
showHeader: true,
sortIcons: {
color: 'rgba(0,0,0,0.4)',
activeColor: '#000000',
hoverColor: 'rgba(0,0,0,0.6)'
},
columns: [{
title: 'Name',
width: 'fraction',
sort: { enabled: true, type: 'text' }
}, {
title: 'Betrag',
width: '120px',
sort: { enabled: true, type: 'number' }
}]
},
table: myList.[{
recordId: Nr,
columns: [{
field: 'Name',
value: Name,
width: 'fraction'
}, {
field: 'Betrag',
value: text(Betrag) + ' €',
sort: { value: number(Betrag) },
width: '120px'
}]
}]
emptyTable
Con el bloque emptyTable defines qué se muestra cuando no hay datos disponibles, es decir, cuando data: [] está vacío. Puedes adaptar el texto y el diseño del estado vacío, mejorando así notablemente la experiencia de usuario.
title: el texto principal que se muestra (normalmente destacado, p. ej. «No se encontraron entradas»).value: texto adicional opcional o elementos HTML dentro del área vacía.backgroundColor: color de fondo del área vacía, p. ej. "white" o "#f4f6ff".
Comportamiento por defecto: Sin emptyTable se muestra un texto simple «Sin resultados».
💡 Consejo profesional: Los estados vacíos no son errores, son tu escenario. Úsalos para explicaciones, mensajes motivadores o un call-to-action claro («Crear ahora una nueva entrada»). Así tus usuarios no se quedan colgados en el vacío — en el sentido más literal.
emptyTable: {
title: "", // Text der bei einer leeren Tabelle in einem abgerundeten Badge dargestellt wird.
backgroundColor: "", // Hintergrundfarbe in HEX angeben
value: "" // Hier kannst du eigene Widgets oder komplexe Ansichten einfügen, die den Title überschreiben.
},
scrollBar
Con el bloque scrollBar configuras la barra de desplazamiento horizontal de tu tabla. Se muestra cuando tu tabla contiene más columnas de las que caben en el área disponible.
showScrollBar: true o false — determina si la barra de desplazamiento se muestra.height: altura del contenedor de la barra de desplazamiento (p. ej. "10px").backgroundColor: color de fondo del contenedor.handle.height: altura del control deslizante («handle») dentro de la barra.handle.backgroundColor: color del handle de desplazamiento.handle.borderRadius: esquinas redondeadas del control deslizante (p. ej. "5px" o "50%").
Comportamiento por defecto: Sin scrollBar no se muestra ninguna barra de desplazamiento visible, incluso si el contenido desborda horizontalmente.
💡 Consejo profesional: Sobre todo con muchas columnas, una barra de desplazamiento discreta y bien visible ayuda a orientarse. Usa colores suaves y una forma de handle redondeada para que la UI no parezca «técnica» — especialmente en dispositivos táctiles esto vale la pena.
scrollBar: { showScrollbar: true, // default: false (Scrollbar ist standardmäßig ausgeblendet)
height: 40px, // Höhe des Containers. default: 10px
backgroundColor: "#3a4a54" // default: #f0f0f0
handle: { height: "20px", // Höhe des Schiebers. default: 90%
borderRadius: 20px // default: 20px
backgroundColor: "#fff", } // default: #ccc
theme
Con el parámetro theme puedes activar una plantilla de diseño predefinida para tu tabla. El tema influye en el aspecto visual completo — desde los colores hasta las tipografías y el layout. Actualmente disponibles:
"clean-white""naked"
Importante: Para que el tema surta efecto correctamente, el uniqueId del theme debe usar el mismo uniqueListId que tu tabla. Solo así el tema sobrescribe de forma específica las reglas de estilo correctas mediante CSS.
Comportamiento por defecto: Si no se establece ningún theme, se usa el diseño estándar del widget arcCustomTable (con sombra ligera, alto contraste).
theme: arcCustomThemeCleanWhite({
uniqueId: "Projektliste Demo 1"
}),
styles
La misma bolsa styles en cada nivel. Preferiblemente un objeto con nombres similares a CSS (backgroundColor, fontColor, paddingX). También funciona un string CSS.
Nivel Prop Afecta a Tabla styles: { backgroundColor, borders, … }Superficie + borde. Por defecto para todas las filas Header header: { backgroundColor } o header.stylesFila de encabezado Celda de header header.columns[n].stylesUna celda de encabezado Fila table: [{ styles: { backgroundColor } }]Todas las celdas de esta fila Columna / celda table: [{ columns: [{ styles: { backgroundColor } }] }]Una celda Footer footer.stylesFooter
La columna sobrescribe a la fila, la fila sobrescribe a la tabla. styles prevalece sobre rowColor / backgroundColor plano.
styles: {
backgroundColor: "#ffffff",
borderColor: "#e4e4e7",
borders: { outer: true, columns: false, rows: true }
},
header: {
backgroundColor: "#fafafa"
},
table: myList.[{
recordId: Nr,
styles: {
backgroundColor: "#f4f4f5"
},
columns: [{
value: Name,
width: "fraction",
styles: {
fontWeight: "600"
}
}]
}]
footer
Con el bloque footer puedes mostrar un área inferior debajo de la tabla, p. ej. para botones o información. Es ideal para zonas de acción como «Crear nueva entrada» o para mostrar totales, filtros o información de estado.
showFooter: true o false — activa o desactiva el footer.showActionButton: muestra el botón estándar *«Crear nuevo registro»*.actionButtonTitle: texto individual para el botón de acción.backgroundColor: color de fondo del footer.leftSideContent: contenido libre (p. ej. texto, HTML, iconos) para el lado izquierdo.rightSideContent: contenido para el lado derecho, p. ej. indicadores de estado, totales o información.
Comportamiento por defecto: Sin el bloque footer no se muestra ningún footer. Si se establece showFooter: true sin más opciones, aparece un área de footer vacía.
footer: {
showFooter: true,
showActionButton: true,
backgroundColor: "",
actionButtonTitle: "Neue Aufgabe hinzufügen",
leftSideContent: "",
rightSideContent: "Gesamt: " + cnt(filteredList) + " Aufgaben"
}
Así se ve el footer definido arriba:
table
En el área de detalle table defines todas las columnas (columns) y su representación, contenido y comportamiento. Aquí determinas qué campos de datos se muestran, cómo se ven y qué ocurre al hacer clic.
- Cada columna se puede formatear de forma individual (p. ej. color, alineación, padding, acciones).
- Los valores establecidos aquí sobrescriben, en su caso, los ajustes globales del bloque
settings (p. ej. fontColor, backgroundColor, align, etc.). - Aquí también puedes controlar columnas fijas, agrupaciones, interacciones y edición inline.
Parámetros de fila y grupo
projectList.[{
recordId: Nr,
rowColor: "",
rowHeight: "auto",
rowPaddingY: "10px",
groupRowColor: "",
groupRowSettings: {
height: "",
},
recordId:
rowColor:rowHeight:rowPaddingYgroupRowColor:groupRowSettings.heightstyles: string CSS para estilo libre — se aplica a cada celda de la fila. Sustituto a largo plazo de rowColor y rowHeight.
💡 Consejo profesional: Puedes usar colores calculados dinámicamente para hacer visible de inmediato la retroalimentación de estado, p. ej. verde para «todo completado», gris para «en curso»:
groupRowColor: if cnt(Firma.Projekte) = cnt(Firma.Projekte[Abgeschlossen != null]) then
"#F0FFF1"
else
"#eee"
end,
Filas anidadas (items[])
Las tablas jerárquicas usan la misma forma de fila que las table[] planas, más hijos anidados opcionales en cada fila.
Entrada de nivel superior (una u otra)
Forma Caso de uso ** table: [...] Clásico — proyectos existentes, plano + anidado items: [...]**Nuevo — mismos objetos de fila, raíz estilo Timeline
No establezcas table y items en la raíz al mismo tiempo. Si ambos están presentes, table gana (aviso de desarrollo).
Cada fila puede contener items: [...] para hijos (profundidad ilimitada). Contraer/expandir es del lado del cliente (persistido en localStorage por uniqueListId).
Ejemplo — table[] con hijos anidados
arcCustomTable({
uniqueListId: "tasks-" + Nr,
header: {
columns: [
{ title: " ", width: "40px" },
{ title: "Aufgabe", width: "fraction" },
{ title: "Status", width: "120px" }
]
},
collapsible: {
enabled: true,
expandColumn: 0,
indentColumn: 1,
indentSize: "16px"
},
table: [{
id: "p1",
recordId: Nr,
columns: [
{ value: "" },
{ value: Bezeichnung },
{ value: Status }
],
items: Unteraufgaben.[{
id: format(number(Nr), "0000"),
recordId: Nr,
columns: [ /* same column layout */ ],
items: /* deeper levels */
}]
}]
})
Ejemplo — items[] en la raíz
Mismos objetos de fila; sustituye table: por items: en la raíz.
collapsible
Propiedad Significado enabledtrue: las filas padre con hijos muestran un control de expansióndefaultCollapsedtrue: todas las filas padre empiezan contraídasexpandColumnÍndice de columna para el control (por defecto 0) indentColumnÍndice de columna para el padding de profundidad (por defecto 1) indentSizePadding por nivel, p. ej. "16px" iconSizeTamaño de caret por defecto sin iconos propios — p. ej. "16px" (por defecto) iconExpand / iconCollapseDescriptores de widget anidados opcionales (p. ej. arcCustomIcon) — sustituye al caret por defecto
No se puede combinar con groupedBy en la misma configuración de tabla.
La ordenación del lado del cliente se desactiva cuando hay filas anidadas — el orden viene de Ninox (estructura table / items).
span / colspan en celdas
Todas las filas comparten el mismo número de columnas de encabezado. Para mostrar una fila padre con una celda ancha que abarque varias columnas, usa span (alias colspan) en la celda:
// Header: 4 columns (Expand | Name | Status | Date)
columns: [
{ value: "" },
{ value: "Team Nord — 3 members", span: 3 } // 1 + 3 = 4
]
// Child row: four normal cells (span defaults to 1)
columns: [
{ value: "" },
{ value: "Anna M." },
{ value: badgeWidget },
{ value: "14.06.2026" }
]
No válido: una fila padre con 2 entradas en columns y una fila hija con 4 entradas sin span — las columnas quedarán desalineadas.
Los valores opcionales width / minWidth en una celda siguen sobrescribiendo el ancho mínimo de esa celda; las pistas de la cuadrícula provienen de header.columns.
columns
En el array columns defines las columnas individuales de tu tabla — es decir, cómo se ven, cómo se llaman y qué se muestra en ellas. Cada columna se describe mediante su propio objeto dentro de columns.
Aspectos básicos que puedes configurar:
field: el campo de datos que se muestra — debe coincidir con los valores en data.table.columns.title: el nombre visible de la columna en el encabezado.width: el ancho de la columna — fijo, p. ej. "200px" / "10%". Para columna flexible: "fraction", "auto" o "" (vacío) — misma lógica de grid (minmax(30px, 1fr), también combinada con columnas fijas).align: alineación del contenido — "left", "center" o "right".backgroundColor / color: color de fondo y de texto de la columna.paddingX / paddingY: espaciados internos horizontal y vertical.truncate: si el texto debe recortarse y añadirse «…».fixed: "left" o "right", para fijar una columna.styles: string CSS para estilo libre de la celda (celda de encabezado o de cuerpo). Sobrescribe todas las demás props de estilo.actions: acciones como update, popup, change, openFullscreen, delete.
columns: [{
field: "",
title: "",
value: "",
width: "",
align: "",
fixed: "",
paddingX: "",
paddingY: "",
groupExpandAction: "",
groupValue: "",
actions: [{
recordId: "",
type: "",
field: "",
value: ""
}]
}
Expandir grupos con groupExpandAction
Con groupExpandAction puedes determinar en qué columnas debe activarse la función de expandir. Esto solo funciona si has configurado una agrupación en tu tabla. Si la activas en al menos una columna (con groupExpandAction:true), se desactiva automáticamente en las demás columnas, salvo que también la actives ahí en los parámetros.
groupExpandAction: true, // Aktiviert das Ausklappen der Tabellenzeilen durch Klick auf die Spalte.
groupExpandAction: false, // Deaktiviert das Ausklappen der Tabellenzeilen durch Klick auf die Spalte.
groupExpandAction: "", // default: true
### actions
Con el parámetro actions puedes definir qué debe ocurrir cuando un usuario hace clic en una celda o la edita. Las acciones se establecen a nivel de celda dentro de columns (en data.table) y hacen que tu tabla sea interactiva.
💡 Nota: puedes combinar varias acciones insertándolas como array. Ejemplo:
actions: [{
recordId: Nr,
type: "popup"
}, {
recordId: Nr,
type: "change",
field: "A"
}],
Acción: popup
Con type: "popup" defines una acción que, al hacer clic en la celda, abre el registro vinculado como popup directamente dentro de Ninox. Así los usuarios pueden ver o editar detalles sin abandonar la vista actual.
recordId debe ser el Nr del registro correspondiente.- Esta acción se aplica siempre a toda la celda, no solo al texto.
- Con
showPopupButton: true puedes mostrar además un botón que dispara explícitamente el popup — útil si quieres separar visualmente editar y abrir.
actions: [{
recordId: Nr,
type: "popup", // Text. Muss genau so geschrieben werden.
showPopupButton: true, // true oder false zum Ein- oder Ausblenden des Open-Buttons
popupButton: arcCustomButton({
uniqueId: "ButtonProjekt" + Nr,
icon: "",
title: "Open",
fontSize: "13px",
fontColor: "",
iconColor: "",
backgroundColor: "",
borderColor: ""
})
}]
En lugar de hacer clicable toda la celda, también puedes mostrar un botón individual dentro de la celda — p. ej. con el widget arcCustomButton.
Para ello usas:
showPopupButton: true — activa el botón.popupButton — contiene el elemento de botón renderizado, p. ej. mediante arcCustomButton().
Acción: delete
Con type: "delete" puedes eliminar el registro asociado al hacer clic en una celda. La acción se aplica, como en popup, a toda la celda.
recordId debe apuntar al Nr del registro que se va a eliminar.- No se incorpora ningún diálogo de confirmación adicional — la eliminación se produce directamente.
💡 Nota: esta acción debe usarse solo con precaución y claramente visualizada — p. ej. mediante una columna de icono especial o un botón marcado en rojo dentro de la celda.
actions: [{
recordId: Nr,
type: "delete" // Text. Muss genau so geschrieben werden.
}]
Acción: update
Con type: "update" puedes cambiar directamente un campo del registro vinculado al hacer clic en una celda — sin popup, sin modo de edición.
recordId: el Nr del registro que se va a modificar.field: el campo que se actualiza (como texto).value: el nuevo valor que se va a establecer (texto, número, booleano, etc.).
actions: [{
recordId: Nr,
type: "update",
field: "G", // Gibt die genaue Field ID des Feldes an, auf das die Aktion angewendet wird.
value: if erledigt=true then null else true end, // Gibt den Wert an, der bei dem referenzierten Feld eingesetzt werden soll.
}]
💡 Consejo profesional: Si no quieres realizar el borrado directamente, sino solo tras una confirmación de seguridad, puedes usar un pequeño rodeo mediante un campo auxiliar + trigger:
- Crea un campo Sí/No llamado, p. ej.,
trigger_delete. - Añade en la tabla una acción
update que establezca trigger_delete en true. - En el trigger de ese campo usas el siguiente diálogo:
if dialog("Eintrag Löschen", "Soll der Eintrag wirklich gelöscht werden?", ["Ja, löschen!", "Abbrechen"]) = "Ja, löschen!" then
delete this
end;
trigger_delete := false
Acción: openFullscreen
Con type: "openFullscreen" abres el registro vinculado directamente en modo pantalla completa — es decir, como si se abriera de forma clásica y completa en Ninox.
recordId: el Nr del registro que se va a abrir.
Escenario de uso: Si tienes registros complejos con muchas pestañas o subtablas, openFullscreen es mejor opción que popup.
actions: [{
recordId: Nr,
type: "openFullscreen", // Text. Muss genau so geschrieben werden.
}]
Acción: openRecord
Con type: "openRecord" abres el formulario del registro indicado junto con la tabla correspondiente.
recordId: el Nr del registro que se va a abrir.
actions: [{
recordId: Nr,
type: "openRecord",
showPopupButton: true
}]
groupValue
Con groupValue defines el contenido de la fila de agrupación en una columna determinada — es decir, lo que se muestra en la fila que, por ejemplo, agrupa varios registros de un proyecto, cliente o estado.
- Puedes usar
groupValue en cualquier columna — incluso varias veces por grupo si lo deseas. - Puedes incorporar texto simple, HTML o incluso mini-widgets (p. ej. botones, layouts o indicadores de estado).
- La fila de agrupación se muestra automáticamente cuando
groupedBy está establecido en el bloque settings.
groupValue: Projekte.Bezeichnung // Ninox-Felder & -Schreibweisen möglich
groupValue: "Projekt:" + Projekte.Bezeichnung // Text + Ninox-Feld
groupValue: arcCustomProgressBar({
uniqueId: "",
width: "",
fontSize: "",
fontColor: "",
backgroundColor: "",
progressColor: "",
valueTotal: "",
valueProgress: "",
valueText: ""
}) // Andere Mini-Widgets
groupByValue
El parámetro groupByValue permite agrupar tu tabla según un valor distinto al value mostrado. Así puedes controlar la visualización de la celda independientemente de la lógica de agrupación.
value: lo que el usuario ve en la celda (p. ej. un icono, una etiqueta, un elemento HTML).groupByValue: la fila se agrupa según este valor.
Fallback: Si value o groupByValue está vacío, la fila se asigna automáticamente al grupo arc-no-value. Este grupo se representa con normalidad, pero también se puede seleccionar u ocultar de forma específica.
groupByValue: Projekte.Nr //
groupByValue: Typ // Ninox-Felder
groupByValue: "" // default: es wird nach value gruppiert
Ejemplos
A continuación encuentras algunos ejemplos prácticos que ilustran los diferentes usos de la Custom Table.
Custom Table Simple
let current := this;
let projectList := (select Projekte);
let data := {
uniqueListId: "Projektliste Offen",
tableId: tableId(first(projectList)),
height: "auto",
groupedBy: "",
popupButtonTitle: "Open",
table: projectList.[{
recordId: Nr,
rowColor: "",
groupRowColor: "#eee",
columns: [{
field: "Feldname für Feld-ID",
title: "Titel",
value: "Ninox Wert",
width: "10%"
}, {
field: "Feldname für Feld-ID",
title: "Titel",
value: "Ninox Wert",
width: "15%"
}, {
field: "Feldname für Feld-ID",
title: "Titel",
value: "Ninox Wert",
width: "10%"
}, {
field: "Firma.Name",
title: "Firma",
value: Firma.Name,
width: "10%"
}, {
field: "Bezeichnung",
title: "Projekt",
value: Bezeichnung,
width: "15%",
actions: [{
type: "popup",
recordId: Nr
}, {
type: "change",
recordId: Nr,
field: "A"
}]
}, {
field: "Umsatz",
title: "Umsatz",
value: text(Umsatz + 1000),
width: "20%"
}, {
field: "Aufgaben",
title: "Status Aufgaben",
value: "<h1>",
width: "100px"
}, {
field: "Abgeschlossen",
title: "Abgeschlossen am",
value: text(Abgeschlossen),
width: "100px"
}]
}],
footer: {
showFooter: false,
actionButtonTitle: "",
rightSideContent: "Gesamt: " + cnt(projectList) + " Projekte"
}
};
arcCustomTable(data)
Custom Table Complex
let current := this;
arcCustomTable(data); let list := do as transaction select Aufgaben end; let filteredList := list[if current.Suche != null then testx(Mitarbeiter.'First Name' + "," + text(Mitarbeiter.'Last Name') + "," + text(Aufgabe) + "," + text(Projekte.Bezeichnung) + "," + ", "(?:" + current.Suche + ")\.*[^]", "gi") else true end and if current.'Erledigte einblenden' = true then true else Erledigt != true end]; let data := { uniqueListId: "Projektliste A", tableId: tableId(first(list)), height: "500px", theme: "", groupedBy: text('Gruppieren nach'), embedded: false, table: filteredList.[{ recordId: Nr, rowColor: "", rowHeight: "60px", groupRowColor: let currentRecord := this; if cnt(filteredList[Projekte.Bezeichnung = currentRecord.Projekte.Bezeichnung and Erledigt = true]) = cnt(filteredList[Projekte.Bezeichnung = currentRecord.Projekte.Bezeichnung]) then "#dcf2de" else "#eee" end, columns: [{ field: "Erledigt", title: arcCheckBox({ uniqueId: "checkbox all check", value: Erledigt, embedded: true, clickAction: { recordId: Nr, fieldId: "G", value: if cnt(list[Erledigt = true]) != cnt(list) then false else if cnt(list[Erledigt = null]) = cnt(list) then null else if cnt(list[Erledigt = true]) = cnt(list) then true end end end } }), value: arcCheckBox({ uniqueId: "checkbox single check", value: Erledigt, embedded: true, clickAction: { recordId: Nr, fieldId: "G", value: if Erledigt = true then null else true end } }), width: "100px", align: "center", fixed: "left", groupValue: arcCustomIcon({ name: "caret-down" }) }, { field: "Bild", title: "Logo", width: "150px", value:", actions: [{ recordId: Projekte.Nr, type: "popup" }], groupValue: "", }, { field: "Projekt", title: "Projekt", width: "", value: Projekte.Bezeichnung, actions: [{ recordId: Projekte.Nr, type: "popup" }], groupValue: Projekte.Bezeichnung, }, { field: "Aufgaben", title: "Aufgabe", value: Aufgabe, align: "left", width: "200px", actions: [{ recordId: Nr, type: "popup" }, { recordId: Nr, type: "change", field: "A" }], groupValue: if current.'Gruppieren nach' = 3 then html(--- { Aufgabe } ---) end }, { field: "Mitarbeiter", title: "Mitarbeiter", value:", actions: [{ recordId: Mitarbeiter.Nr, type: "popup" }] }, { field: "delete", title: "", width: "40px", align: "center", value: "", actions: [{ recordId: Nr, type: "delete" }] }] }], footer: { showFooter: true, showActionButton: true, actionButtonTitle: "Neue Aufgabe hinzufügen", rightSideContent: "Gesamt: " + cnt(filteredList) + " Aufgaben" } }; arcCustomTable(data)</code></pre>