Skip to content

Conversation

@xingarr
Copy link

@xingarr xingarr commented Nov 5, 2025

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?

  1. 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
  2. Implemented before($destination) method: This method moves the current widget to appear immediately before the specified destination widget. It follows the same pattern as after() but inserts the widget before the destination instead.

  3. Added comprehensive unit tests: Created WidgetTest.php with 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 $this if 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 $this for 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:

  1. Create a test controller or use an existing CRUD controller
  2. 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');
  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 $this if destination doesn't exist)
  • Maintains fluent interface pattern for method chaining
  • Consistent with existing makeFirst() and makeLast() 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)

…nja_Work/Git_work/CRUD/src/app/Library/Widget.php:108:4-141:5) and [before()](cci:1://file:///d:/Ninja_Work/Git_work/CRUD/src/app/Library/Widget.php:143:4-176:5)
@welcome
Copy link

welcome bot commented Nov 5, 2025

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant