2.48.0 • Published 4 months ago

@memberjunction/ng-entity-permissions v2.48.0

Weekly downloads
-
License
ISC
Repository
-
Last release
4 months ago

@memberjunction/ng-entity-permissions

The @memberjunction/ng-entity-permissions package provides Angular components for displaying and editing permissions for MemberJunction entities. It allows administrators to manage role-based access control (RBAC) for entities in a user-friendly grid interface.

Overview

This package is part of the MemberJunction platform and provides essential UI components for managing entity-level permissions. It offers two primary components:

  1. EntityPermissionsGridComponent - A grid that displays and allows editing of permissions for either a specific entity across all roles, or for a specific role across all entities
  2. EntityPermissionsSelectorWithGridComponent - A composite component that combines an entity dropdown selector with the permissions grid

Features

  • Dual-mode operation: View permissions by entity (all roles for one entity) or by role (all entities for one role)
  • Grid-based interface for intuitive permission management
  • Batch editing with transaction support for atomic updates
  • Visual feedback with dirty state indicators and row highlighting
  • Quick actions:
    • Flip all permissions of a specific type (Read/Create/Update/Delete)
    • Toggle all permissions in a row with intelligent logic
    • Revert individual row changes
  • Real-time permission change events with cancellation support
  • Automatic permission record creation for missing role/entity combinations
  • Responsive loading states with Kendo UI indicators

Installation

npm install @memberjunction/ng-entity-permissions

Requirements

Peer Dependencies

  • Angular 18.0.2+
  • @angular/common
  • @angular/core
  • @angular/forms
  • @angular/router

Runtime Dependencies

  • @memberjunction/core (v2.43.0+)
  • @memberjunction/core-entities (v2.43.0+)
  • @memberjunction/global (v2.43.0+)
  • @memberjunction/ng-container-directives (v2.43.0+)
  • @memberjunction/ng-shared (v2.43.0+)
  • @progress/kendo-angular-grid (v16.2.0)
  • @progress/kendo-angular-dropdowns (v16.2.0)
  • @progress/kendo-angular-buttons (v16.2.0)
  • @progress/kendo-angular-dialog (v16.2.0)
  • @progress/kendo-angular-indicators (v16.2.0)

Usage

Module Setup

First, import the EntityPermissionsModule in your Angular module:

import { EntityPermissionsModule } from '@memberjunction/ng-entity-permissions';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    // ... other imports
    EntityPermissionsModule
  ],
})
export class YourModule { }

Basic Usage Examples

Entity Permissions Grid - Entity Mode

Display and manage permissions for a specific entity across all roles:

import { Component } from '@angular/core';
import { EntityPermissionChangedEvent } from '@memberjunction/ng-entity-permissions';

@Component({
  selector: 'app-entity-permissions',
  template: `
    <mj-entity-permissions-grid
      [EntityName]="'Customers'"
      [Mode]="'Entity'"
      [BottomMargin]="20"
      (PermissionChanged)="onPermissionChanged($event)">
    </mj-entity-permissions-grid>
  `
})
export class EntityPermissionsComponent {
  onPermissionChanged(event: EntityPermissionChangedEvent) {
    console.log(`Permission ${event.PermissionTypeChanged} changed to ${event.Value} for role ${event.RoleID}`);
    
    // Optionally cancel the change based on business logic
    if (this.shouldPreventChange(event)) {
      event.Cancel = true;
    }
  }
  
  private shouldPreventChange(event: EntityPermissionChangedEvent): boolean {
    // Implement your business logic here
    return false;
  }
}

Entity Permissions Grid - Role Mode

Display and manage permissions for a specific role across all entities:

@Component({
  selector: 'app-role-permissions',
  template: `
    <mj-entity-permissions-grid
      [RoleName]="selectedRole"
      [Mode]="'Role'"
      (PermissionChanged)="onPermissionChanged($event)">
    </mj-entity-permissions-grid>
  `
})
export class RolePermissionsComponent {
  selectedRole = 'Administrator';
  
  onPermissionChanged(event: EntityPermissionChangedEvent) {
    // Handle permission changes for the role
    console.log(`Entity ${event.EntityName}: ${event.PermissionTypeChanged} = ${event.Value}`);
  }
}

Entity Selector with Permissions Grid

Provide a dropdown to select entities dynamically:

@Component({
  selector: 'app-dynamic-permissions',
  template: `
    <div class="permissions-container">
      <h2>Entity Permissions Manager</h2>
      <mj-entity-permissions-selector-with-grid
        [EntityName]="initialEntity"
        [BottomMargin]="30"
        (PermissionChanged)="handlePermissionChange($event)">
      </mj-entity-permissions-selector-with-grid>
    </div>
  `
})
export class DynamicPermissionsComponent {
  initialEntity = 'Customers'; // Optional initial selection
  
  handlePermissionChange(event: EntityPermissionChangedEvent) {
    // Process permission changes
    this.logPermissionChange(event);
  }
  
  private logPermissionChange(event: EntityPermissionChangedEvent) {
    console.log('Permission change:', {
      entity: event.EntityName,
      role: event.RoleID,
      type: event.PermissionTypeChanged,
      value: event.Value
    });
  }
}

Advanced Usage

Programmatic Grid Control

Access the grid component directly to perform operations:

import { ViewChild } from '@angular/core';
import { EntityPermissionsGridComponent } from '@memberjunction/ng-entity-permissions';

@Component({
  template: `
    <mj-entity-permissions-grid #permGrid
      [EntityName]="'Orders'"
      [Mode]="'Entity'">
    </mj-entity-permissions-grid>
    
    <button (click)="refreshPermissions()">Refresh</button>
    <button (click)="saveAllChanges()">Save All</button>
    <button (click)="flipReadPermissions()">Toggle All Read</button>
  `
})
export class AdvancedPermissionsComponent {
  @ViewChild('permGrid') permissionsGrid!: EntityPermissionsGridComponent;
  
  async refreshPermissions() {
    await this.permissionsGrid.Refresh();
  }
  
  async saveAllChanges() {
    if (this.permissionsGrid.NumDirtyPermissions > 0) {
      await this.permissionsGrid.savePermissions();
      console.log('All permissions saved successfully');
    }
  }
  
  flipReadPermissions() {
    this.permissionsGrid.flipAllPermissions('Read');
  }
}

Handling Transactions

The component automatically handles batch updates using MemberJunction's transaction system:

// The grid component internally uses transaction groups for atomic updates
// When savePermissions() is called, all dirty permissions are saved in a single transaction
// This ensures data consistency and allows for rollback if any permission update fails

API Reference

EntityPermissionsGridComponent

Selector: mj-entity-permissions-grid

Properties

Inputs
NameTypeDefaultDescription
Mode'Entity' \| 'Role''Entity'Determines whether to display permissions for a specific entity across all roles or for a specific role across all entities
EntityNamestringundefinedRequired when Mode is 'Entity'. The name of the entity to show permissions for
RoleNamestringundefinedRequired when Mode is 'Role'. The name of the role to show permissions for
BottomMarginnumber0Bottom margin in pixels to apply to the grid container
Outputs
NameTypeDescription
PermissionChangedEventEmitter<EntityPermissionChangedEvent>Emitted whenever a permission checkbox is toggled. The event can be cancelled by setting event.Cancel = true
Public Properties
NameTypeDescription
permissionsEntityPermissionEntity[]Array of permission records currently displayed in the grid
gridHeightnumberHeight of the grid in pixels (default: 750)
isLoadingbooleanLoading state indicator
NumDirtyPermissionsnumberCount of permissions that have been modified but not saved

Methods

MethodParametersReturn TypeDescription
Refresh()NonePromise<void>Reloads all permissions data from the database. Automatically creates missing permission records for display
savePermissions()NonePromise<void>Saves all modified permissions in a single transaction. Only saves permissions that are truly dirty
cancelEdit()NonePromise<void>Reverts all unsaved changes to their original values
flipAllPermissions(type)type: 'Read' \| 'Create' \| 'Update' \| 'Delete'voidIntelligently toggles all permissions of the specified type. If majority are ON, turns all OFF; otherwise turns all ON
flipRow(permission)permission: EntityPermissionEntityvoidToggles all permissions in a row. If 2+ are ON, turns all OFF; otherwise turns all ON
revertRow(event, permission)event: MouseEvent, permission: EntityPermissionEntityvoidReverts a single row to its original state

EntityPermissionsSelectorWithGridComponent

Selector: mj-entity-permissions-selector-with-grid

Properties

Inputs
NameTypeDefaultDescription
EntityNamestringundefinedOptional. Name of the initially selected entity
BottomMarginnumber0Bottom margin in pixels to apply to the component
CurrentEntityEntityInfo \| undefinedundefinedThe currently selected entity object. Can be used for two-way binding
Outputs
NameTypeDescription
PermissionChangedEventEmitter<EntityPermissionChangedEvent>Bubbles up permission change events from the embedded grid
Public Properties
NameTypeDescription
entityListEntityInfo[]Alphabetically sorted list of all entities available for selection

EntityPermissionChangedEvent Interface

export type EntityPermissionChangedEvent = {
  EntityName: string;                                          // Name of the entity whose permission changed
  RoleID: string;                                             // ID of the role whose permission changed
  PermissionTypeChanged: 'Read' | 'Create' | 'Update' | 'Delete'; // The specific permission type that was modified
  Value: boolean;                                             // The new value of the permission (true = granted, false = revoked)
  Cancel: boolean;                                            // Set to true in event handler to cancel the change
}

Internal Behavior Notes

  1. Automatic Record Creation: When loading permissions, the component automatically creates unsaved EntityPermission records for any missing role/entity combinations. These appear in the grid but are only saved if the user enables at least one permission.

  2. Dirty State Logic: A permission is considered "really dirty" if:

    • It's an existing saved record that has been modified, OR
    • It's a new record with at least one permission enabled
  3. Transaction Support: All save operations use MemberJunction's TransactionGroup to ensure atomic updates across multiple permission records.

  4. Event Handling: The grid uses a sophisticated event system where clicking on checkboxes or table cells properly toggles permissions while preventing event bubbling conflicts.

Styling

The components use standard HTML tables with custom CSS classes that can be overridden:

CSS Classes

ClassApplied ToDescription
.grid<table>Main permissions grid table
.permission-left-colFirst <td> in each rowLeft column containing entity/role names and revert icon
.dirty-row<tr>Applied to rows that have unsaved changes
.entity-selector<kendo-dropdownlist>Styles the entity selection dropdown
.inner-grid<mj-entity-permissions-grid>Applied to the grid within the selector component
.fa-arrow-rotate-left<span> iconRevert icon shown in dirty rows

Customization Example

/* Custom styling for your application */
::ng-deep {
  .mj-entity-permissions-grid {
    .grid {
      border: 1px solid #ddd;
      width: 100%;
    }
    
    .dirty-row {
      background-color: #fff3cd;
      
      .permission-left-col {
        font-weight: bold;
        
        .fa-arrow-rotate-left {
          color: #856404;
          cursor: pointer;
          margin-left: 10px;
          
          &:hover {
            color: #533f03;
          }
        }
      }
    }
    
    th {
      cursor: pointer;
      user-select: none;
      
      &:hover {
        background-color: #f0f0f0;
      }
    }
  }
}

Integration with MemberJunction

This package integrates seamlessly with the MemberJunction ecosystem:

Entity Permission System

  • Works with the EntityPermissionEntity from @memberjunction/core-entities
  • Respects the MemberJunction metadata system for entities and roles
  • Uses MemberJunction's transaction system for atomic updates

Metadata Integration

  • Automatically discovers all entities and roles from the Metadata provider
  • Validates entity and role names against the metadata
  • Creates properly typed entity objects using the metadata system

RunView Integration

  • Uses RunView for efficient data loading with proper filtering
  • Leverages ResultType: 'entity_object' for automatic entity instantiation
  • Implements proper ordering for consistent display

Build and Development

This package uses the Angular compiler (ngc) for building:

# Build the package
npm run build

# The built files will be in the dist/ directory

TypeScript Configuration

  • Targets ES2015 with ES2020 modules
  • Generates declaration files with source maps
  • Uses strict mode for type safety
  • Configured for Angular library compilation with strict templates

Performance Considerations

  1. Efficient Loading: The component loads all permissions in a single query and creates missing records in memory
  2. Batch Updates: Uses transaction groups to minimize database round trips
  3. Smart Dirty Checking: Only saves records that have actual changes
  4. Lazy Initialization: Permission records for missing combinations are created on-demand

Common Use Cases

Admin Dashboard

// Create a comprehensive admin interface for managing all permissions
@Component({
  template: `
    <mat-tab-group>
      <mat-tab label="By Entity">
        <mj-entity-permissions-selector-with-grid></mj-entity-permissions-selector-with-grid>
      </mat-tab>
      <mat-tab label="By Role">
        <select [(ngModel)]="selectedRole">
          <option *ngFor="let role of roles" [value]="role.Name">{{role.Name}}</option>
        </select>
        <mj-entity-permissions-grid 
          *ngIf="selectedRole"
          [Mode]="'Role'" 
          [RoleName]="selectedRole">
        </mj-entity-permissions-grid>
      </mat-tab>
    </mat-tab-group>
  `
})
export class PermissionsDashboardComponent { 
  // Implementation
}

Role Creation Wizard

// Include permissions step in role creation
@Component({
  template: `
    <div *ngIf="currentStep === 'permissions'">
      <h3>Configure Permissions for {{newRole.Name}}</h3>
      <mj-entity-permissions-grid
        [Mode]="'Role'"
        [RoleName]="newRole.Name"
        (PermissionChanged)="trackPermissionChanges($event)">
      </mj-entity-permissions-grid>
    </div>
  `
})
export class RoleWizardComponent {
  // Track changes and save with role
}

Troubleshooting

Common Issues

  1. "EntityName is required when Mode is 'Entity'"

    • Ensure you provide the EntityName input when using Entity mode
    • Check that the entity name matches exactly (case-sensitive)
  2. "Entity not found: EntityName"

    • Verify the entity exists in MemberJunction metadata
    • Ensure metadata is properly loaded before component initialization
  3. Permissions not saving

    • Check browser console for transaction errors
    • Verify user has permission to modify EntityPermission records
    • Ensure database constraints are not violated
  4. Grid not displaying

    • Confirm all Kendo UI modules are properly imported
    • Check that EntityPermissionsModule is imported in your module

License

ISC License - see the root MemberJunction repository for details.

2.27.1

8 months ago

2.23.2

8 months ago

2.46.0

5 months ago

2.23.1

8 months ago

2.27.0

8 months ago

2.34.0

6 months ago

2.30.0

7 months ago

2.19.4

9 months ago

2.19.5

9 months ago

2.19.2

9 months ago

2.19.3

9 months ago

2.19.0

9 months ago

2.19.1

9 months ago

2.15.2

9 months ago

2.34.2

6 months ago

2.34.1

6 months ago

2.15.1

9 months ago

2.38.0

5 months ago

2.45.0

5 months ago

2.22.1

9 months ago

2.22.0

9 months ago

2.41.0

5 months ago

2.22.2

9 months ago

2.26.1

8 months ago

2.26.0

8 months ago

2.33.0

6 months ago

2.18.3

9 months ago

2.18.1

9 months ago

2.18.2

9 months ago

2.18.0

9 months ago

2.37.1

5 months ago

2.37.0

6 months ago

2.14.0

10 months ago

2.21.0

9 months ago

2.44.0

5 months ago

2.40.0

5 months ago

2.29.0

8 months ago

2.29.2

8 months ago

2.29.1

8 months ago

2.25.0

8 months ago

2.48.0

4 months ago

2.32.0

7 months ago

2.32.2

7 months ago

2.32.1

7 months ago

2.17.0

9 months ago

2.13.4

10 months ago

2.36.0

6 months ago

2.13.2

11 months ago

2.13.3

10 months ago

2.13.0

11 months ago

2.36.1

6 months ago

2.13.1

11 months ago

2.43.0

5 months ago

2.20.2

9 months ago

2.20.3

9 months ago

2.20.0

9 months ago

2.20.1

9 months ago

2.28.0

8 months ago

2.47.0

4 months ago

2.24.1

8 months ago

2.24.0

8 months ago

2.31.0

7 months ago

2.12.0

12 months ago

2.39.0

5 months ago

2.16.1

9 months ago

2.35.1

6 months ago

2.35.0

6 months ago

2.16.0

9 months ago

2.42.1

5 months ago

2.42.0

5 months ago

2.23.0

8 months ago

2.11.0

12 months ago

2.10.0

12 months ago

2.9.0

12 months ago

2.8.0

1 year ago

2.6.1

1 year ago

2.6.0

1 year ago

2.7.0

1 year ago

2.5.2

1 year ago

2.7.1

1 year ago

1.8.1

1 year ago

1.8.0

1 year ago

1.6.1

1 year ago

1.6.0

1 year ago

1.4.1

1 year ago

1.4.0

1 year ago

2.2.1

1 year ago

2.2.0

1 year ago

2.4.1

1 year ago

2.4.0

1 year ago

2.0.0

1 year ago

1.7.1

1 year ago

1.5.3

1 year ago

1.7.0

1 year ago

1.5.2

1 year ago

1.5.1

1 year ago

1.3.3

1 year ago

1.5.0

1 year ago

1.3.2

1 year ago

1.3.1

1 year ago

1.3.0

1 year ago

2.3.0

1 year ago

2.1.2

1 year ago

2.1.1

1 year ago

2.5.0

1 year ago

2.3.2

1 year ago

2.1.4

1 year ago

2.3.1

1 year ago

2.1.3

1 year ago

2.5.1

1 year ago

2.3.3

1 year ago

2.1.5

1 year ago

1.2.2

1 year ago

1.2.1

1 year ago

1.2.0

1 year ago

1.1.1

1 year ago

1.1.0

1 year ago

1.1.3

1 year ago

1.1.2

1 year ago

1.0.11

1 year ago

1.0.9

2 years ago

1.0.8

2 years ago

1.0.7

2 years ago

1.0.8-next.6

2 years ago

1.0.8-next.5

2 years ago

1.0.8-next.4

2 years ago

1.0.8-next.3

2 years ago

1.0.8-next.2

2 years ago

1.0.8-beta.0

2 years ago

1.0.8-next.1

2 years ago

1.0.8-next.0

2 years ago

1.0.7-next.0

2 years ago

1.0.6

2 years ago

1.0.4

2 years ago

1.0.1

2 years ago

0.9.30

2 years ago

0.9.25

2 years ago

0.9.27

2 years ago

0.9.28

2 years ago

0.9.29

2 years ago

0.9.23

2 years ago

0.9.24

2 years ago

0.9.22

2 years ago

0.9.20

2 years ago

0.9.21

2 years ago

0.9.18

2 years ago

0.9.19

2 years ago

0.9.16

2 years ago

0.9.17

2 years ago

0.9.15

2 years ago

0.9.13

2 years ago

0.9.12

2 years ago

0.9.11

2 years ago

0.9.8

2 years ago

0.9.9

2 years ago

0.9.10

2 years ago

0.9.6

2 years ago

0.9.5

2 years ago

0.9.7

2 years ago

0.9.3

2 years ago

0.9.2

2 years ago