Implement Widget Positioning Methods
Implement Widget Positioning Methods: after() and before()
WHY
BEFORE - What was wrong? What was happening before this PR?
The Widget class had two empty placeholder methods (after() and before()) marked with TODO comments, indicating that the functionality to position widgets relative to other widgets was planned but not yet implemented. This limited developers' ability to precisely control widget ordering in their admin panels.
Developers could only use makeFirst() and makeLast() methods to position widgets at the beginning or end of a section, but had no way to insert a widget immediately before or after a specific widget in the middle of the collection.
AFTER - What is happening after this PR?
Developers can now use the after($destination) and before($destination) methods to position widgets relative to other widgets by name. This provides fine-grained control over widget ordering, allowing for more flexible and intuitive widget management.
Example usage:
// Move a widget to appear right after another widget
Widget::add('my_widget')->after('existing_widget');
// Move a widget to appear right before another widget
Widget::add('another_widget')->before('existing_widget');
HOW
How did you achieve that, in technical terms?
-
Implemented
after($destination)method: This method moves the current widget to appear immediately after the specified destination widget in the collection. It:- Validates that the destination widget exists
- Removes the current widget from the collection
- Finds the position of the destination widget
- Reconstructs the collection with the current widget inserted after the destination
- Updates the global widget collection
-
Implemented
before($destination)method: This method moves the current widget to appear immediately before the specified destination widget. It follows the same pattern asafter()but inserts the widget before the destination instead. -
Added comprehensive unit tests: Created
WidgetTest.phpwith 10 test cases covering:- Basic functionality of both methods
- Handling of non-existent destination widgets (graceful failure)
- Complex scenarios with multiple widgets
- Method chaining capabilities
- Return type verification
Technical Implementation Details
Both methods use the following approach:
- Access the widget collection from the service container
- Validate the destination widget exists (returns
$thisif not, allowing for graceful failure) - Use
array_slice()to split the collection at the appropriate position - Reconstruct the collection with the current widget in the new position
- Use Laravel Collection's
replace()method to update the global collection - Return
$thisfor method chaining
Is it a breaking change?
No, this is not a breaking change. The methods were previously empty placeholders that did nothing, so any existing code calling these methods will continue to work (though it will now actually perform the positioning operation). This is a purely additive feature that enhances existing functionality.
How can we test the before & after?
Manual Testing:
- Create a test controller or use an existing CRUD controller
- Add multiple widgets in the
setup()method:
Widget::add(['name' => 'widget_1', 'type' => 'card', 'content' => ['body' => 'Widget 1']]);
Widget::add(['name' => 'widget_2', 'type' => 'card', 'content' => ['body' => 'Widget 2']]);
Widget::add(['name' => 'widget_3', 'type' => 'card', 'content' => ['body' => 'Widget 3']]);
// Move widget_3 to appear after widget_1
Widget::add('widget_3')->after('widget_1');
- Verify the widgets appear in the order: widget_1, widget_3, widget_2
Automated Testing:
Run the new unit tests:
vendor/bin/phpunit tests/Unit/CrudPanel/WidgetTest.php --testdox
Expected output should show all 10 tests passing:
- ✓ After method moves widget after destination
- ✓ Before method moves widget before destination
- ✓ After method with non existent destination does nothing
- ✓ Before method with non existent destination does nothing
- ✓ After method works with multiple widgets
- ✓ Before method works with multiple widgets
- ✓ After method returns widget instance
- ✓ Before method returns widget instance
- ✓ After method can be chained
- ✓ Before method can be chained
Additional Notes
Code Quality
- Follows PSR-2 coding standards
- Includes comprehensive PHPDoc comments
- Implements graceful error handling (returns
$thisif destination doesn't exist) - Maintains fluent interface pattern for method chaining
- Consistent with existing
makeFirst()andmakeLast()implementations
Future Enhancements
This implementation resolves the TODO items in the Widget class. Similar positioning logic could potentially be applied to other areas of the codebase where ordering is important (e.g., the moveColumn() method in ColumnsProtectedMethods.php has a similar TODO about refactoring).
Related Issues
This PR addresses the TODO comments found at:
- Line 109 in
src/app/Library/Widget.php(after method) - Line 114 in
src/app/Library/Widget.php(before method)
BOOM! Your first PR with us, thank you so much! Someone will take a look at it shortly.
Please keep in mind that:
- if this constitutes a breaking change, it might take quite a while for this to get merged; we try to emulate the Laravel release cycle as much as possible, so developers can upgrade both software once; this means a new big release every ~6 months;
- even if it's a non-breaking change, it might take a few days/weeks for the PR to get merged; unless it's a no-brainer, we like to have some community feedback on new features, before we merge them; this leads to higher-quality code, in the end; we learnt this the hard way :-)
- not all PRs get merged; sometimes we just have to hold out new features, to keep the packages lean; sometimes we don't include features that only apply to niche use cases;
- we're not perfect; if you think we're wrong, call us out on it; but in a kind way :-) we all make mistakes, best we learn from them and build better software together;
Thank you!
-- Justin Case The Backpack Robot