[data-reveal]{opacity:1!important;transform:none!important}
AntelopeJS DMS
DocumentationTable row actions

Table row actions

The row actions of a table view, archive mode, and the guards that run before each write.

These are the options of a table view that act on its rows: row actions and their extras, archive mode, and the guards its routes run before each write.

Row actions

TableViewRowActionOptions<T> configures the row menu. Every built-in action takes true, false, or a RowActionConfig<T> carrying a rule.

OptionTypeDescription
addboolean | RowActionConfig<T>Create a row.
editboolean | RowActionConfig<T>Edit a row.
detailsboolean | RowActionConfig<T>Open the read-only view.
duplicateboolean | RowActionConfig<T>Open the new form pre-filled from the row.
deleteboolean | RowActionConfig<T>Delete a row.
archiveboolean | RowActionConfig<T>Archive a row.
restoreboolean | RowActionConfig<T>Restore an archived row.
copyLinkboolean | RowActionConfig<T>Copy a link to the row.
hasSelectionbooleanEnable row selection.
customCustomRowAction<T>[]Extra actions pointing at an ActionTarget.
typescript
static table = TableView(memberDataAPI, {
  rowActions: {
    add: false,
    edit: { isEnabled: true, isVisible: true },
    delete: { isEnabled: true, rule: { field: "isTenantOwner", notEquals: true } },
    hasSelection: true,
    custom: [
      {
        label: "Validate email",
        icon: "i-ph-check-circle",
        target: {
          type: "api",
          url: "/settings/workspace/members/{{row._id}}/validate-email",
          method: "POST",
          successMessage: "Email validated",
        },
        rule: { field: "isValidated", notEquals: true },
      },
    ],
  },
});

A double-click on a row opens edit, or details when edit is off. It is a single click on a table declaring rowClick: "single" (TableViewRowClick), which leaves a click on a control of the row (a button, a link, a checkbox) or one ending a text selection to them. A custom action flagged isDefault takes that slot instead. The first flagged action whose rule accepts the row wins, and the click opens its ActionTarget like the menu entry does. This is how a table whose detail view is a custom drawer keeps the row affordance: without a click target the row is inert and no longer lights up on hover.

typescript
custom: [
  {
    label: "Details",
    icon: "i-ph-info",
    isDefault: true,
    target: { type: "drawer", component: CustomComponent("SendDetailDrawer"), title: "Details" },
  },
],

Rules are enforced on both sides. A data controller has one writing table view: a TableView enabling at least one write action (add, duplicate, edit, delete, archive or restore) that the controller offers. Its rowActions (archive-mode defaults included) become the controller's controllerRowActionRules, along with its rowIdKey and strictRuleValidation. The edit, delete, archive and restore routes validate the affected ids against those rules before running. Rows that fail the rule are dropped from the operation; with strictRuleValidation the whole request is rejected instead.

A second writing table view over the same controller fails the registration of its page with a WritingTableViewConflictError (a PageDeclarationConflictError, rethrown even from a nested component) naming both table views. Read-only table views, with every write action set to false and no archiveMode, may share the controller: they open no new / edit form and leave its rules and options alone. A page re-registering (a hot reload) re-claims the controller; a page that went away releases it. To give the same data a second writing screen, derive a controller (see Show the same data in two writing screens).

Permissions are applied on top. The table view registers list and select as component actions always, add, edit and view when the matching generated form exists, delete when the controller declares the delete endpoint, archive, restore and viewArchived under archiveMode, and export when the three export endpoints are registered. Each action carries a permission id of the form <component-permission>.<action>. When the viewer lacks the permission the corresponding row action is forced to false and the corresponding generated form is withheld. The rule algebra itself (leaf operators, and, or, not, and RowActionConfig) is documented in Shared types.

Several pages may each mount their own TableView over one data controller, and they all share its data routes. A read action (list, select, view, export, viewArchived) accepts the permission from any table view a page mounts on that controller; a write action (add, edit, delete, archive, restore) accepts only the writing table view's, so a user granted add on a read-only table view cannot create rows. A read-only table view still declares the write actions the controller offers: while no table view writes through the controller, they keep its write routes behind permission ids nobody is granted by default. The inline add button of a relation field pointing at the controller follows the same rule (addPermissionIds).

A new or edit ResourceForm block over the controller submits to the same routes, so the native files staged from its fields are accepted on save as well, alongside those of the writing table view. It adds no permission of its own to the union: the routes still require the action's permission from a table view of the controller when one is mounted, exactly as for the block's other fields. The new and edit forms a page-mode table view mounts on its form sub-pages count the same way: each is a component of its own sub-page, so the files staged from it are accepted on save, under the same table view permissions.

TableViewRowActionOptionsSerialized is the transported shape: rules and flags untyped, custom as CustomRowActionSerialized[], plus a showArchived?: boolean field the builder fills from the viewArchived permission.

Row action extras

TableViewRowActionOptions also takes menuLabel: the accessible name of a row's ⋯ menu button, its {field} braces filled from the row (a $ key's translation receives the row's fields), "Actions for

RowActionConfig (built-in actions) also takes label, icon and confirm (an ActionConfirm: a ConfirmDialog, or { from }, a URL answering one for the row, on delete, archive and restore, for a single row); BulkRowActionConfig (delete, archive, restore) adds successMessage. CustomRowAction also takes confirm, permission (ButtonPermission: an action of the table by name, or an Action), permissionId (an absolute permission id, which wins over permission), color, variant, showLabel, bulk (true or { allMatching: true }: offered on the selection bar, see below) and deepLink (a drawer, modal or side panel it opens is kept in the URL as ?record=<id> (by default for a side panel, which false opts out of), or ?<tableId>.record=<id> on a page with several table views). An action the caller fails the permission for is stripped per request like a custom button; it is only hidden, so the route its target calls must check the permission itself.

A bulk custom action sends the selected ids as a repeated ids query parameter, or allMatching=true with the table's filters, search and archive view after "Select all N matching". resolveBulkRowIds(this, ctx, user), called from a route of the table's data controller, or resolveBulkRowIds({ model, tableView }, ctx, user) (BulkRowSource: the model the calling route is given, and the table's TableViewMeta) from any other route, a page's included, answers the ids the action covers: the selected ones, or every matching row read like the list reads them, refusing more than MAX_BULK_MATCHING_ROWS (413). Either way the caller needs the table's list permission (403 otherwise). BULK_ALL_MATCHING_KEY is the query flag. The api action target also takes body (the confirmation's field values merged in) and copy (response field copied to the clipboard). CustomButton takes placement ("toolbar", the default, or "header": drawn in the page header, which the server adds it to); rowActions.add takes it too (AddRowActionConfig).

When several table views share a data controller, each read action of its routes is guarded by the permission ids of every mounted table view, each write action by the writing table view's (TableViewMeta.actionPermissionIds(action); any one suffices). TableViewMeta.componentBuilders lists them, writingComponentBuilders the ones that write.

Archive mode

With archiveMode: true, the archive and restore actions replace deletion. The builder calls applyArchiveModeDefaultRules (see Helpers) to give every relevant action a default rule against the @ArchiveField column, so archived rows offer restore and delete while live rows offer edit, duplicate and archive. Actions you configured keep their other settings; every action not turned off receives the default rule, ANDed with its own rule when it has one.

typescript
static table = TableView(articleDataAPI, {
  archiveMode: true,
  rowActions: { edit: true, delete: true, archive: true, restore: true },
});

Guards

A guard runs before the mutating route and aborts it by throwing. Use assert from @antelopejs/interface-api-util to fail with an HTTP status and a message key. this inside a guard is the data controller instance.

typescript
import { assert } from "@antelopejs/interface-api-util";
import { getRequestTenantId } from "@antelopejs/interface-dms/request-tenant";
import { GetModel } from "@antelopejs/interface-database-decorators";
import { TenantMemberModel } from "@antelopejs/interface-dms/db";

static table = TableView(memberDataAPI, {
  guards: {
    edit: async (ctx, { current, body }) => {
      const demoting = current.isTenantOwner && body.isTenantOwner === false;
      if (!demoting) return;
      const model = GetModel(TenantMemberModel, getRequestTenantId(ctx));
      const remaining = await model.countOwnersExcluding([current.userId]);
      assert(remaining > 0, 409, "$page.settings.members.error.last_owner");
    },
    delete: async (ctx, { ids }) => {
      // ids is the full set of rows the request targets
    },
  },
});

The edit guard receives { id, body, current }, where current is the row as it stands in the database. The route loads it before calling the guard and answers 404 when it does not exist. The new guard receives { body }, parsed from the request body whether it arrived as a buffer, a string or an object. The delete, archive and restore guards receive { ids, values }: values is the answer of the action's confirmation fields, sent as the request body, and is unset when the request carried none (see Confirmations). Like the rules, guards are stored on the controller metadata the first time a table view declares them, so they apply to every table view on that controller. The argument interfaces are documented in Shared types.