# @memberjunction/ng-entity-viewer
Angular components for viewing MemberJunction entity data in multiple formats -- grid, card, and timeline views -- with filtering, selection, toolbar actions, and a comprehensive Before/After cancelable event system.
## Overview
The `@memberjunction/ng-entity-viewer` package provides a suite of data presentation components built on AG Grid. The primary component, `EntityDataGridComponent`, offers infinite scroll, configurable toolbars with custom buttons, server-side sorting, grid state persistence, and a rich event system where "before" events can be canceled. The `EntityViewerComponent` wraps the grid alongside card and timeline views with a view-mode toggle.
```mermaid
flowchart TD
subgraph Composite["EntityViewerComponent"]
TOGGLE[View Mode Toggle] --> GRID[Grid View]
TOGGLE --> CARDS[Card View]
TOGGLE --> TIMELINE[Timeline View]
end
subgraph Grid["EntityDataGridComponent"]
TB[Configurable Toolbar] --> AG[AG Grid]
AG --> INF[Infinite Scroll]
AG --> SEL[Selection Modes]
AG --> STATE[State Persistence]
AG --> EVENTS[Before/After Events]
end
subgraph Data["Data Sources"]
RV[RunView Params] --> Grid
PRE[Pre-loaded Data] --> Grid
ENT[Entity Name + Filter] --> Grid
end
GRID --> Grid
style Composite fill:#2d6a9f,stroke:#1a4971,color:#fff
style Grid fill:#7c5295,stroke:#563a6b,color:#fff
style Data fill:#2d8659,stroke:#1a5c3a,color:#fff
```
## Installation
```bash
npm install @memberjunction/ng-entity-viewer ag-grid-angular ag-grid-community
```
## Usage
### Import the Module
```typescript
import { EntityViewerModule } from '@memberjunction/ng-entity-viewer';
@NgModule({
imports: [EntityViewerModule]
})
export class MyModule { }
```
### Composite Viewer
```html
```
### Standalone Data Grid
```html
```
### Cancelable Events
```typescript
onBeforeRowSelect(event: BeforeRowSelectEventArgs) {
// Prevent selecting locked records
if (event.row.Get('Status') === 'Locked') {
event.cancel = true;
event.cancelReason = 'Cannot select locked records';
}
}
```
## API Reference
### EntityDataGridComponent (`mj-entity-data-grid`)
AG Grid-based data grid with rich event system and configurable toolbar.
#### Data Source Inputs
| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `Params` | `RunViewParams` | - | Primary data source (stored views + dynamic views) |
| `entityName` | `string` | - | Entity name for dynamic views |
| `extraFilter` | `string` | - | Additional WHERE clause filter |
| `searchString` | `string` | - | User search string |
| `orderBy` | `string` | - | ORDER BY clause |
| `maxRows` | `number` | `0` | Max rows to fetch (0 = no limit) |
| `data` | `BaseEntity[]` | - | Pre-loaded data (bypasses RunView) |
| `AllowLoad` | `boolean` | `true` | Enable/disable data loading |
| `AutoRefreshOnParamsChange` | `boolean` | `true` | Auto-refresh when Params changes |
#### Pagination Inputs
| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `PaginationMode` | `'client' \| 'infinite'` | `'client'` | Pagination strategy |
| `PageSize` | `number` | `100` | Rows per page (infinite mode) |
| `CacheBlockSize` | `number` | `100` | Cache block size (infinite mode) |
| `MaxBlocksInCache` | `number` | `10` | Max cached blocks |
#### Display Inputs
| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `showToolbar` | `boolean` | `true` | Show the toolbar |
| `toolbarConfig` | `GridToolbarConfig` | - | Toolbar configuration |
| `selectionMode` | `'none' \| 'single' \| 'multiple' \| 'checkbox'` | `'single'` | Row selection mode |
| `height` | `number \| 'auto' \| 'fit-content'` | `'auto'` | Grid height |
| `gridState` | `ViewGridStateConfig` | - | Column/sort state from User View |
| `allowSorting` | `boolean` | `true` | Enable column sorting |
| `allowColumnReorder` | `boolean` | `true` | Enable column reordering |
| `allowColumnResize` | `boolean` | `true` | Enable column resizing |
| `serverSideSorting` | `boolean` | `true` | Sort triggers server reload |
#### Before/After Events
The grid uses a cancelable event pattern. Before events can be canceled by setting `event.cancel = true`.
**Row Selection Events:**
| Event | Args Type | Description |
|-------|-----------|-------------|
| `beforeRowSelect` | `BeforeRowSelectEventArgs` | Before row is selected |
| `afterRowSelect` | `AfterRowSelectEventArgs` | After row is selected |
| `beforeRowDeselect` | `BeforeRowDeselectEventArgs` | Before row is deselected |
| `afterRowDeselect` | `AfterRowDeselectEventArgs` | After row is deselected |
**Row Click Events:**
| Event | Args Type | Description |
|-------|-----------|-------------|
| `beforeRowClick` | `BeforeRowClickEventArgs` | Before row click processes |
| `afterRowClick` | `AfterRowClickEventArgs` | After row click |
| `beforeRowDoubleClick` | `BeforeRowDoubleClickEventArgs` | Before double-click |
| `afterRowDoubleClick` | `AfterRowDoubleClickEventArgs` | After double-click |
**Data Events:**
| Event | Args Type | Description |
|-------|-----------|-------------|
| `beforeDataLoad` | `BeforeDataLoadEventArgs` | Before data loads |
| `afterDataLoad` | `AfterDataLoadEventArgs` | After data loads |
| `beforeDataRefresh` | `BeforeDataRefreshEventArgs` | Before refresh |
| `afterDataRefresh` | `AfterDataRefreshEventArgs` | After refresh |
**Sorting/Column Events:**
| Event | Args Type | Description |
|-------|-----------|-------------|
| `beforeSort` | `BeforeSortEventArgs` | Before sort changes |
| `afterSort` | `AfterSortEventArgs` | After sort changes |
| `beforeColumnReorder` | `BeforeColumnReorderEventArgs` | Before column move |
| `afterColumnReorder` | `AfterColumnReorderEventArgs` | After column move |
| `gridStateChanged` | `GridStateChangedEvent` | Column state changed |
**Toolbar Button Events:**
| Event | Args Type | Description |
|-------|-----------|-------------|
| `newButtonClick` | `void` | Add/New button clicked |
| `refreshButtonClick` | `void` | Refresh button clicked |
| `deleteButtonClick` | `BaseEntity[]` | Delete button clicked |
| `exportButtonClick` | `void` | Export button clicked |
| `compareButtonClick` | `BaseEntity[]` | Compare button clicked |
| `mergeButtonClick` | `BaseEntity[]` | Merge button clicked |
| `addToListButtonClick` | `BaseEntity[]` | Add to List clicked |
### GridToolbarConfig
Configure toolbar buttons and behavior:
```typescript
const toolbarConfig: GridToolbarConfig = {
showSearch: true,
searchPlaceholder: 'Search records...',
searchDebounce: 300,
showRefresh: true,
showAdd: true,
showDelete: true,
showExport: true,
showColumnChooser: true,
showFilterToggle: false,
exportFormats: ['excel', 'csv', 'json'],
showRowCount: true,
showSelectionCount: true,
position: 'top',
customButtons: [
{
id: 'myButton',
text: 'My Action',
icon: 'fa-solid fa-star',
tooltip: 'Do something custom',
position: 'right',
onClick: () => console.log('Clicked!')
}
]
};
```
### EntityViewerComponent (`mj-entity-viewer`)
Composite component combining grid, cards, and timeline views.
#### Inputs
| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `entity` | `EntityInfo` | - | Entity metadata |
| `records` | `BaseEntity[]` | - | Pre-loaded records |
| `viewEntity` | `UserViewEntityExtended` | - | User View for filtering/sorting |
| `viewMode` | `'grid' \| 'cards' \| 'timeline'` | `'grid'` | Current view mode |
| `filterText` | `string` | - | Filter text |
| `sortState` | `SortState` | - | Sort state |
| `gridState` | `ViewGridStateConfig` | - | Grid column state |
| `showGridToolbar` | `boolean` | `true` | Show grid toolbar |
| `gridToolbarConfig` | `GridToolbarConfig` | - | Toolbar configuration |
| `gridSelectionMode` | `GridSelectionMode` | `'single'` | Selection mode |
#### Outputs
| Output | Event Type | Description |
|--------|------------|-------------|
| `recordSelected` | `RecordSelectedEvent` | Record clicked |
| `recordOpened` | `RecordOpenedEvent` | Record double-clicked |
| `dataLoaded` | `DataLoadedEvent` | Data finished loading |
| `viewModeChange` | `EntityViewMode` | View mode changed |
| `filterTextChange` | `string` | Filter text changed |
| `sortChanged` | `SortChangedEvent` | Sort changed |
| `gridStateChanged` | `GridStateChangedEvent` | Grid state changed |
| `addRequested` | `void` | Add button clicked |
| `deleteRequested` | `{ records }` | Delete button clicked |
| `refreshRequested` | `void` | Refresh button clicked |
| `exportRequested` | `{ format }` | Export button clicked |
### RecycleBinComponent (`mj-recycle-bin`) and RecycleBinChipComponent (`mj-recycle-bin-chip`)
Slide-in panel that lists hard-deleted records for a single entity and lets a user with `Delete` permission re-create any of them from its historical RecordChange snapshot. The accompanying chip component is a tiny composite that renders a count-badge button and hosts the panel — drop it into any toolbar in three lines.
#### When to use
The chip is **already embedded** in `EntityViewerComponent` and `EntityDataGridComponent` — both expose a `[ShowRecycleBin]` input (default `true`) that you can flip off if you don't want the chip. Use the standalone components only when building a custom entity viewer.
#### Permission model
The chip and panel are gated on `entity.UserPermissions.CanDelete`. Rationale: there is no native "undelete" permission in MemberJunction, but if a user has the higher-trust permission to *delete* records of an entity, restoring deleted ones is well within scope. The actual re-create action additionally requires `CanCreate`; without it the Restore button on each card disables with a tooltip.
The chip auto-hides when:
- `EntityName` is null/empty
- The entity has `TrackRecordChanges = false`
- The user lacks `CanDelete` permission
- The deleted-record count is zero
#### Soft vs hard deletes
This component only surfaces *hard*-deleted records. Soft-deletes (`IsDeleted` flags, `Status='Inactive'`, etc.) leave the record visible in normal entity views, so the standard Record Changes panel + restore preview already handles them — no Recycle Bin needed.
#### Cancelable Before/After events
Every meaningful action emits a paired `before*` / `after*` event. The `before*` event carries `cancel: boolean` so consumers can intercept — useful for custom approval workflows, audit logging, or to take over the actual restore execution.
```typescript
onBeforeRecordRestore(e: BeforeRecordRestoreEventArgs) {
if (!hasComplianceApproval(e.entry)) {
e.cancel = true;
e.cancelReason = 'Awaiting compliance approval';
}
}
```
#### `RecycleBinChipComponent` Inputs
| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `EntityName` | `string \| null` | `null` | Entity whose deleted records will be listed. Chip hides when null. |
| `ContextUser` | `UserInfo \| null` | `null` | Optional context user. Falls back to `Metadata.Provider.CurrentUser`. |
#### `RecycleBinChipComponent` / `RecycleBinComponent` Outputs
| Output | Args Type | Cancelable | Description |
|--------|-----------|------------|-------------|
| `BeforeRecycleBinOpen` | `BeforeRecycleBinOpenEventArgs` | ✓ | Fires before the deleted-record query runs. |
| `AfterRecycleBinOpen` | `AfterRecycleBinOpenEventArgs` | | Fires after the query completes; carries `deletedRecordCount`. |
| `BeforeRecordRestore` | `BeforeRecordRestoreEventArgs` | ✓ | Fires when the user clicks Restore on a card, before the preview opens. |
| `AfterRecordRestore` | `AfterRecordRestoreEventArgs` | | Fires after the user closes the preview; carries `success`. |
| `BeforeRestoreCommit` | `BeforeRestoreCommitEventArgs` | ✓ | Fires after the user confirms in the preview but before the insert runs. |
| `AfterRestoreCommit` | `AfterRestoreCommitEventArgs` | | Fires after the insert; carries `success`, `newRecordID`, `errorMessage`. |
#### `RecycleBinComponent` Inputs (when used directly)
| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `Visible` | `boolean` | `false` | Controls panel visibility. Setting true triggers a load. |
| `EntityName` | `string \| null` | `null` | **Required.** The entity whose deleted records to list. |
| `ContextUser` | `UserInfo \| null` | `null` | Optional context user. |
| `MaxRecords` | `number` | `200` | Max number of cards to load. |
#### Embedded chip control
`EntityViewerComponent` and `EntityDataGridComponent` both expose:
| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `ShowRecycleBin` | `boolean` | `true` | Renders the Recycle Bin chip in the toolbar/header. Auto-hides when entity isn't a candidate (no tracking / no permission / no deleted records). |
```html
```
---
### EntityCardsComponent (`mj-entity-cards`)
Card-based view with auto-generated layout.
```html
```
### PillComponent (`mj-pill`)
Semantic color pill for categorical values. Colors are auto-detected based on value:
| Color | Values |
|-------|--------|
| success (green) | active, approved, complete, success |
| warning (yellow) | pending, in progress, draft, waiting |
| danger (red) | failed, error, rejected, cancelled |
| info (blue) | new, info, created, open |
| neutral (gray) | default |
```html
```
## Advanced Usage
### Infinite Scroll Pagination
```html
```
### State Persistence with User Views
```html
```
## Type Exports
```typescript
// Types
import {
GridToolbarConfig,
GridToolbarButton,
GridSelectionMode,
GridColumnConfig,
ViewGridStateConfig,
DataGridSortState,
RecordSelectedEvent,
RecordOpenedEvent
} from '@memberjunction/ng-entity-viewer';
// Event Args
import {
BeforeRowSelectEventArgs,
AfterRowSelectEventArgs,
BeforeRowClickEventArgs,
AfterRowClickEventArgs,
BeforeDataLoadEventArgs,
AfterDataLoadEventArgs,
AfterSortEventArgs
} from '@memberjunction/ng-entity-viewer';
// Recycle Bin
import {
RecycleBinComponent,
RecycleBinChipComponent,
RecycleBinEntry,
BeforeRecycleBinOpenEventArgs,
AfterRecycleBinOpenEventArgs,
BeforeRecordRestoreEventArgs,
AfterRecordRestoreEventArgs,
BeforeRestoreCommitEventArgs,
AfterRestoreCommitEventArgs
} from '@memberjunction/ng-entity-viewer';
```
## Dependencies
### Runtime Dependencies
| Package | Description |
|---------|-------------|
| `@memberjunction/core` | Core framework |
| `@memberjunction/core-entities` | Entity type definitions |
| `@memberjunction/global` | Global utilities |
| `@memberjunction/export-engine` | Data export engine |
| `@memberjunction/ng-shared-generic` | Shared generic components |
| `@memberjunction/ng-timeline` | Timeline view component |
| `@memberjunction/ng-filter-builder` | Filter builder component |
| `@memberjunction/ng-export-service` | Export service and dialog |
| `@memberjunction/ng-record-changes` | Reusable restore preview panel for the Recycle Bin |
| `@memberjunction/ng-ui-components` | Provides `mj-slide-panel` for the Recycle Bin slide-in |
### Peer Dependencies
- `@angular/common` ^21.x
- `@angular/core` ^21.x
- `@angular/forms` ^21.x
- `@angular/animations` ^21.x
- `ag-grid-angular` ^35.x
- `ag-grid-community` ^35.x
## Build
```bash
cd packages/Angular/Generic/entity-viewer
npm run build
```
## License
ISC