Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Record Nav Laravel Package

nben/filament-record-nav

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require nben/filament-record-nav
    

    Publish config (optional):

    php artisan vendor:publish --tag=filament-record-nav-config
    
  2. First Use Case: Add actions to getHeaderActions() in your ViewRecord or EditRecord page:

    use Nben\FilamentRecordNav\Actions\{NextRecordAction, PreviousRecordAction};
    
    protected function getHeaderActions(): array {
        return [
            PreviousRecordAction::make(),
            NextRecordAction::make(),
        ];
    }
    
  3. Where to Look First:

    • config/filament-record-nav.php for global ordering settings
    • Actions/NextRecordAction.php and Actions/PreviousRecordAction.php for API reference
    • Your resource's getPages() method for custom route names

Implementation Patterns

Core Workflow

  1. Default Integration:

    // ViewRecord.php
    protected function getHeaderActions(): array {
        return [
            PreviousRecordAction::make()
                ->label('Previous')
                ->icon('heroicon-o-chevron-left'),
            NextRecordAction::make()
                ->label('Next')
                ->icon('heroicon-o-chevron-right'),
        ];
    }
    
  2. Page-Specific Navigation:

    // EditRecord.php
    protected function getHeaderActions(): array {
        return [
            PreviousRecordAction::make()
                ->navigateTo(NavigationPage::View),
            NextRecordAction::make()
                ->navigateTo(NavigationPage::View),
        ];
    }
    
  3. Custom Route Integration:

    // Resource.php
    public static function getPages(): array {
        return [
            'index' => ListPosts::route('/'),
            'view' => ViewPost::route('/{record}'),
            'edit' => EditPost::route('/{record}/edit'),
            'publish' => PublishPost::route('/{record}/publish'),
        ];
    }
    
    // ViewRecord.php
    protected function getHeaderActions(): array {
        return [
            NextRecordAction::make()
                ->navigateTo(NavigationPage::custom('publish')),
        ];
    }
    

Integration Tips

  • Action Styling: Use Filament's action modifiers:

    PreviousRecordAction::make()
        ->color('gray')
        ->size(Size::Small)
        ->tooltip('Previous record')
    
  • Conditional Visibility:

    PreviousRecordAction::make()
        ->visible(fn ($livewire) => $livewire->record && $livewire->record->is_archived)
    
  • Key Bindings:

    NextRecordAction::make()
        ->keyBindings(['mod+right'])
    
  • Resource-Wide Setup: Create a base page class:

    // BaseRecordPage.php
    abstract class BaseRecordPage extends ViewRecord {
        protected function getHeaderActions(): array {
            return [
                PreviousRecordAction::make(),
                NextRecordAction::make(),
            ];
        }
    }
    

Gotchas and Tips

Pitfalls

  1. Disabled Buttons:

    • Cause: Records with identical order_column values (e.g., created_at with same timestamp)
    • Fix: Add unique index or use id column:
      'order_column' => 'id',
      
  2. Custom Route Mismatch:

    • Error: InvalidArgumentException
    • Fix: Verify route name matches getPages() exactly:
      // Correct:
      NavigationPage::custom('verified-view')
      
      // Resource must define:
      'verified-view' => VerifiedViewPage::route('/{record}/verified')
      
  3. Trait Not Working:

    • Symptom: Custom query methods ignored
    • Check:
      • Trait is properly used
      • Method signatures match exactly:
        public function getPreviousRecord(): ?Model
        
  4. Performance on Large Tables:

    • Solution: Add database index to order_column
    • Optimization: Cache results in getPreviousRecord()/getNextRecord():
      protected ?Model $cachedPreviousRecord;
      
      public function getPreviousRecord(): ?Model {
          if ($this->cachedPreviousRecord) return $this->cachedPreviousRecord;
      
          $this->cachedPreviousRecord = $this->getRecord()
              ->newQuery()
              ->where('status', 'published')
              ->orderBy('published_at')
              ->first();
      
          return $this->cachedPreviousRecord;
      }
      

Debugging Tips

  1. Log Queries:

    public function getPreviousRecord(): ?Model {
        \Log::debug('Finding previous record for ID: ' . $this->getRecord()->id);
        return $this->getRecord()
            ->newQuery()
            ->where('created_at', '<', $this->getRecord()->created_at)
            ->first();
    }
    
  2. Verify Cache: Add debug output to actions:

    PreviousRecordAction::make()
        ->url(fn ($livewire) => {
            $record = $livewire->resolveRecordNavigation('previous');
            \Log::debug('Resolved previous record:', ['id' => $record?->id]);
            return $record ? static::getResource()::getUrl('view', ['record' => $record]) : null;
        })
    
  3. Check Livewire Context:

    PreviousRecordAction::make()
        ->url(fn ($livewire) => {
            \Log::debug('Livewire context:', [
                'record_id' => $livewire->record?->id,
                'page' => get_class($livewire),
            ]);
            // ...
        })
    

Extension Points

  1. Dynamic Order Column:

    // In your page class
    public function getOrderColumn(): string {
        return $this->getRecord()->is_draft ? 'draft_order' : 'published_at';
    }
    
    // Override the action's resolve method
    public function getPreviousRecord(): ?Model {
        $column = $this->getOrderColumn();
        return $this->getRecord()
            ->newQuery()
            ->where($column, '<', $this->getRecord()->{$column})
            ->orderBy($column, 'desc')
            ->first();
    }
    
  2. Multi-Table Navigation:

    public function getNextRecord(): ?Model {
        return Post::whereHas('author', fn ($q) => $q->where('id', $this->getRecord()->author_id))
            ->where('published_at', '>', $this->getRecord()->published_at)
            ->orderBy('published_at')
            ->first();
    }
    
  3. Soft Deletes Handling:

    public function getPreviousRecord(): ?Model {
        return $this->getRecord()
            ->newQuery()
            ->withTrashed()
            ->where('deleted_at', '<', $this->getRecord()->deleted_at ?? now())
            ->orderBy('deleted_at', 'desc')
            ->first();
    }
    
  4. Custom URL Generation:

    public function getRecordNavigationUrl(Model $record, $page): string {
        return route('admin.posts.show', [
            'post' => $record->slug,
            'tab' => $page->value === 'edit' ? 'content' : 'metadata'
        ]);
    }
    

Configuration Quirks

  1. Timestamp Columns:

    • Use created_at/updated_at carefully - ensure distinct values:
      'order_column' => 'created_at',
      'previous_direction' => 'asc', // Reverse for timestamps
      'next_direction' => 'desc',
      
  2. Case Sensitivity:

    • Route names in NavigationPage::custom() are case-sensitive:
      // Correct:
      NavigationPage::custom('verified-view')
      
      // Incorrect (throws exception):
      NavigationPage::custom('VerifiedView')
      
  3. Default Values:

    • The package uses id as fallback, but ensure your model's primary key is properly defined:
      class Post extends Model {
          public $primaryKey = 'post_id';
      }
      

Performance Optimization

  1. Query Caching: Leverage Laravel's query caching for large datasets:

    public function getPreviousRecord(): ?Model {
        return Cache::remember(
            "filament_nav_prev_{$this->getRecord()->id}",
            now()->addMinutes(5),
            fn() => $this->getRecord()
                ->newQuery()
                ->where('status', 'published')
                ->orderBy('published_at')
                ->first()
        );
    }
    
  2. Eager Loading:

    public function getPreviousRecord(): ?Model {
        return $this->getRecord()
            ->newQuery()
            ->with(['author', 'category'])
            ->where('published_at', '<', $this->getRecord()->published_at)
            ->orderBy('published_at', 'desc')
            ->first();
    }
    
  3. Cursor-Based Pagination: For

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope