kolydart / laravel
A collection of laravel helper classes
Requires
- php: >=7.4
Requires (Dev)
- illuminate/console: ^7.0|^8.0|^9.0|^10.0
- illuminate/database: ^7.0|^8.0|^9.0|^10.0
- illuminate/events: ^7.0|^8.0|^9.0|^10.0
- illuminate/support: ^7.0|^8.0|^9.0|^10.0
- illuminate/translation: ^7.0|^8.0|^9.0|^10.0
- laravel/framework: ^7.0|^8.0|^9.0|^10.0
- mockery/mockery: ^1.6
- phpunit/phpunit: ^9.0
README
A collection of Laravel helper classes including ordered pivot relationships functionality.
Table of Contents
Installation
composer require kolydart/laravel
The service provider will be automatically registered via Laravel's package auto-discovery.
Ordered Pivot Relationships
This package provides functionality to maintain order in many-to-many (pivot) relationships. This abstraction allows you to preserve the selection order of related models, which is particularly useful for forms where the order of selection matters.
Quick Start
1. Create migration for order column:
php artisan make:ordered-pivot-migration paper_user --order-column=order --after=user_id
This creates a migration that adds an order
column to the paper_user
pivot table.
2. Update your model:
<?php namespace App; use Illuminate\Database\Eloquent\Model; use Kolydart\Laravel\App\Traits\HasOrderedPivot; class Paper extends Model { use HasOrderedPivot; public function users() { return $this->orderedBelongsToMany(User::class) ->withPivot('order') ->orderBy('paper_user.order'); } }
3. Update your controller:
<?php namespace App\Http\Controllers; use Kolydart\Laravel\App\Traits\HandlesOrderedPivot; class PaperController extends Controller { use HandlesOrderedPivot; public function store(Request $request) { $paper = Paper::create($request->validated()); $this->syncWithOrder($paper, 'users', $request->input('users', [])); return redirect()->route('papers.index'); } public function edit(Paper $paper) { $users = User::pluck('name', 'id'); $selectedUsers = $this->getOrderedIds($paper, 'users'); return view('papers.edit', compact('paper', 'users', 'selectedUsers')); } }
4. Add assets to your build process:
For Vite (Laravel 9+):
Add to your vite.config.js
:
import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; export default defineConfig({ plugins: [ laravel({ input: [ 'resources/css/app.css', 'resources/js/app.js', 'public/vendor/kolydart/js/ordered-select.js' // Add this line ], refresh: true, }), ], });
Then in your Blade layout:
@vite(['resources/css/app.css', 'resources/js/app.js', 'public/vendor/kolydart/js/ordered-select.js'])
For Laravel Mix:
Add to your webpack.mix.js
:
mix.js('resources/js/app.js', 'public/js') .postCss('resources/css/app.css', 'public/css') .copy('public/vendor/kolydart/js/ordered-select.js', 'public/js/ordered-select.js');
Then in your Blade layout:
<script src="{{ asset('js/ordered-select.js') }}"></script>
Manual inclusion (current setup):
<script src="{{ asset('vendor/kolydart/js/ordered-select.js') }}"></script>
5. Use in your Blade templates:
{{-- Using the Blade component --}} <x-kolydart::ordered-select name="users" :options="$users" :selected="$selectedUsers ?? []" multiple class="form-control select2" /> {{-- Or manually with the ordered-select class --}} <select name="users[]" class="form-control select2 ordered-select" multiple> @foreach($users as $id => $name) <option value="{{ $id }}">{{ $name }}</option> @endforeach </select> {{-- Using Livewire component for dynamic options --}} @livewire('kolydart-ordered-select', [ 'name' => 'users', 'options' => $users, 'selected' => $selectedUsers ?? [], 'multiple' => true, 'allowAdd' => true, 'modelClass' => 'App\\User', 'displayField' => 'name', 'valueField' => 'id' ])
Components
1. Model Trait: HasOrderedPivot
Provides methods for models that need ordered relationships.
Methods:
orderedBelongsToMany()
- Define an ordered many-to-many relationshipsyncWithOrder()
- Sync related models with order preservationgetOrderedIds()
- Get ordered IDs from a relationship
2. Controller Trait: HandlesOrderedPivot
Provides controller methods for handling ordered pivot relationships.
Methods:
syncWithOrder()
- Sync a relationship with order preservationgetOrderedIds()
- Get ordered IDs for form displayprepareOrderedRelationshipForEdit()
- Prepare data for edit forms
3. Artisan Command: make:ordered-pivot-migration
Generates migrations for adding order columns to pivot tables.
Usage:
php artisan make:ordered-pivot-migration {table} [--order-column=order] [--after=column]
4. JavaScript Component: OrderedSelect
Preserves selection order in Select2 dropdowns and provides dynamic option management.
Methods:
OrderedSelect.init()
- Auto-initialize all elements with 'ordered-select' classOrderedSelect.getOrderedValues($select)
- Get selected values in orderOrderedSelect.setOrderedValues($select, values)
- Set values in specific orderOrderedSelect.addOption($select, value, text, selected, preserveOrder)
- Add new optionOrderedSelect.createAddForm($select, config)
- Create modal for adding options
5. Blade Component: <x-kolydart::ordered-select>
Reusable component for ordered select fields.
6. Livewire Component: @livewire('kolydart-ordered-select')
Advanced component with dynamic option addition and real-time updates.
Usage Examples
Example 1: Basic Paper-User Relationship
// Model class Paper extends Model { use HasOrderedPivot; public function users() { return $this->orderedBelongsToMany(User::class); } } // Controller class PaperController extends Controller { use HandlesOrderedPivot; public function store(StorePaperRequest $request) { $paper = Paper::create($request->all()); $this->syncWithOrder($paper, 'users', $request->input('users', [])); return redirect()->route('papers.index'); } public function edit(Paper $paper) { $users = User::pluck('name', 'id'); $selectedUsers = $this->getOrderedIds($paper, 'users'); return view('papers.edit', compact('paper', 'users', 'selectedUsers')); } }
Example 2: Custom Order Column
// Migration php artisan make:ordered-pivot-migration project_task --order-column=priority --after=task_id // Model class Project extends Model { use HasOrderedPivot; public function tasks() { return $this->orderedBelongsToMany(Task::class, null, null, null, null, null, 'priority'); } } // Controller $this->syncWithOrder($project, 'tasks', $taskIds, 'priority');
Example 3: Dynamic Option Addition
// Add "Add New User" functionality function addNewUser() { const $select = $('#users'); OrderedSelect.createAddForm($select, { title: 'Add New User', label: 'User Name', onSubmit: function(name, callback) { // Make AJAX call to create user $.post('/api/users', {name: name}, function(response) { callback(response.id, response.name); }); } }); }
Example 4: Using JavaScript Directly
// Initialize ordered select with custom options OrderedSelect.init(); // Get current order const orderedValues = OrderedSelect.getOrderedValues($('#my-select')); // Set specific order OrderedSelect.setOrderedValues($('#my-select'), [3, 1, 4, 2]); // Add new option dynamically OrderedSelect.addOption($('#my-select'), 'new-id', 'New Option', true, true);
API Reference
HasOrderedPivot Trait
orderedBelongsToMany()
public function orderedBelongsToMany( string $related, string $table = null, string $foreignPivotKey = null, string $relatedPivotKey = null, string $parentKey = null, string $relatedKey = null, string $orderColumn = 'order' ): BelongsToMany
syncWithOrder()
public function syncWithOrder( BelongsToMany $relationship, array $ids, string $orderColumn = 'order' ): void
getOrderedIds()
public function getOrderedIds( BelongsToMany $relationship, string $orderColumn = 'order' ): array
HandlesOrderedPivot Trait
syncWithOrder()
protected function syncWithOrder( Model $model, string $relationshipName, array $ids, string $orderColumn = 'order' ): void
getOrderedIds()
protected function getOrderedIds( Model $model, string $relationshipName, string $orderColumn = 'order' ): array
prepareOrderedRelationshipForEdit()
protected function prepareOrderedRelationshipForEdit( Model $model, string $relationshipName, string $orderColumn = 'order' ): array
OrderedSelect JavaScript
init()
OrderedSelect.init() // Auto-initializes all .ordered-select elements
getOrderedValues()
OrderedSelect.getOrderedValues($select) // Returns: Array
setOrderedValues()
OrderedSelect.setOrderedValues($select, values) // Returns: void
addOption()
OrderedSelect.addOption($select, value, text, selected = false, preserveOrder = true)
createAddForm()
OrderedSelect.createAddForm($select, config = { title: 'Add New Option', label: 'Name', onSubmit: function(text, callback) { /* custom logic */ } })
Blade Component
<x-kolydart::ordered-select name="field_name" :options="$options" :selected="$selected" :multiple="true" placeholder="Select options..." :required="false" class="additional-classes" :attributes="['data-custom' => 'value']" />
Livewire Component
@livewire('kolydart-ordered-select', [ 'name' => 'field_name', 'options' => $options, 'selected' => $selected, 'multiple' => true, 'allowAdd' => true, 'modelClass' => 'App\\Model', 'displayField' => 'name', 'valueField' => 'id' ])
Migration from Manual Implementation
If you have an existing manual implementation, here's how to migrate:
1. Replace Manual Traits
Before:
// Custom syncUsersWithOrder method in controller private function syncUsersWithOrder(Paper $paper, array $userIds) { $paper->users()->detach(); foreach ($userIds as $order => $userId) { $paper->users()->attach($userId, ['order' => $order + 1]); } }
After:
use HandlesOrderedPivot; // Use the trait method $this->syncWithOrder($paper, 'users', $userIds);
2. Update Model Relationships
Before:
public function users() { return $this->belongsToMany(User::class)->withPivot('order')->orderBy('paper_user.order'); }
After:
use HasOrderedPivot; public function users() { return $this->orderedBelongsToMany(User::class); }
3. Simplify JavaScript
Before:
$('#users').on('select2:select', function (e) { var element = e.params.data.element; var $element = $(element); $element.detach(); $(this).append($element); $(this).trigger('change'); });
After:
// Just add 'ordered-select' class for auto-initialization // Or call OrderedSelect.init() manually
4. Use Blade Component
Before:
<select name="users[]" id="users" class="form-control select2" multiple> {{-- Complex logic for ordering options --}} @if(isset($selectedUsers)) @foreach($selectedUsers as $userId) @if(isset($users[$userId])) <option value="{{ $userId }}" selected>{{ $users[$userId] }}</option> @endif @endforeach @endif @foreach($users as $id => $user) @if(!in_array($id, $selectedUsers ?? [])) <option value="{{ $id }}">{{ $user }}</option> @endif @endforeach </select>
After:
<x-kolydart::ordered-select name="users" :options="$users" :selected="$selectedUsers ?? []" />
Testing
The package includes comprehensive tests. To run them:
cd vendor/kolydart/laravel composer test
License
GPL-3.0-or-later