Garet's avatar
Level 3

Multiple actions via a single route/controller

I'm wondering what the best approach is. In my application I have a list of orders. You can view them, create them, edit them, delete them - the usual CRUD.

However, in the index list of orders I have a checkbox next to each one. The idea is that you can tick multiple orders and then perform a bulk action such as "delete selected", "print selected", "mark selected as dispatched", etc.

Since a HTML form element (in this case checkboxes) can only belong to a single form, I figure I need to:

a) Have a single route and controller that deals with all of the different bulk actions. I can determine the intended action by having different submit buttons for each one, albeit they would all submit the same form.

Or

b) Have separate forms, one for each bulk action, each one posting to a separate route/controller, and then I could dynamically append the checkbox values to the appropriate form when it's submitted.

Option A feels incredibly nasty. Option B is perhaps better, but still a bit hacky having to use JavaScript to append the checkbox values to each form as and when they're submitted.

Is there another way that I'm overlooking?

0 likes
9 replies
Snapey's avatar

You cannot have nested forms, so, without javascript;

wrap the whole list in a form element and have checkbox with value of the record ID in the input field.

Have submit buttons with different values, depending on what you want to do with the checked rows.

On each row, you can still have edit button to switch to a view to manage the one record.

What you should not do is have an anchor link for a delete function on each row because anything that changes the server content should always be a POST request. So you need a separate form somewhere on the page that you can trigger when the user presses a delete button on the row. This will need some javascript to trigger submission of the delete form.

gitwithravish's avatar

HTML

<form method="POST" action="url">
	<select name="actionType">
		<option value="delete" selected>Delete</option>
		<option value="print">Print</option>
	</select>
	
	<input type="checkbox" value="1" name="orderIDs[]" checked>
	<input type="checkbox" value="2" name="orderIDs[]">
	<input type="checkbox" value="3" name="orderIDs[]" checked>
</form>

POST DATA

[
	actionType => 'delete'
	orderIDs => [1,3];
]

Garet's avatar
Level 3

Thanks for your replies. I'm familiar with what's been suggested above, it's just a shame there isn't a "better way"

What I would really like is in addition to my OrderController class, to have a BulkOrderController class with some methods such as delete, ship and print.

I'm guessing the only way I can do this is to use JavaScript to append the checkboxes to separate forms, or to make a post request to a "master" method in the BulkOrderController class which then figures out the request and calls one of delete, ship and print accordingly.

Snapey's avatar

I suggested wrapping the whole table in a single form, adding checkboxes for each resource in the table?

Whats wrong with that approach if you send that form to your 'BulkOrderController' ?

How will Javascript make it any better?

Garet's avatar
Level 3

@snapey @ravish The reason for not initially wanting to wrap the whole table in a single form, is because that way I would have to have a single route:

Route::post('/orders/bulk', 'BulkOrderController@doBulkAction')->name('orders.bulk');

The doBulkAction method in the BulkOrderController would then have to figure out what the action is, and call an additional private function within the BulkOrderController to carry out the action. Perhaps this is a reasonable approach?

However from a routing point of view, it's less obvious what's going on, compared with:

Route::post('/orders/bulk/print', 'BulkOrderController@print')->name('orders.bulk.print');

Route::patch('/orders/bulk/dispatch', 'BulkOrderController@dispatch')->name('orders.bulk.dispatch');

Route::delete('/orders/bulk/delete', 'BulkOrderController@delete')->name('orders.bulk.delete');

The latter routing can't be achieved using a single form.

gitwithravish's avatar
Level 16

@garet Set the form attribute action dynamically using javascript

BLADE

<form action="{{  route('orders.bulk.print') }}" method="POST">
	<select name="actionType">
		<option value="{{ route('orders.bulk.print') }}" selected>Print</option>
		<option value="{{ route('orders.bulk.dispatch') }}" >Dispatch</option>
		<option value="{{ route('orders.bulk.delete') }}" >Delete</option>
	</select>
</form>

JAVASCRIPT

$('select').change(function(){
	 $('form').attr('action', $(this).val());
});
Garet's avatar
Level 3

@ravish thanks, I went with your suggestion. In fact, I extended it slightly:

<form action="{{  route('orders.bulk.print') }}" method="POST">
    <select name="actionType">
	    <option value="{{ route('orders.bulk.print') }}" selected data-method="get">Print</option>
	    <option value="{{ route('orders.bulk.dispatch') }}" data-method="patch" >Dispatch</option>
	    <option value="{{ route('orders.bulk.delete') }}" data-method="delete" data-alert="This will delete the selected orders" >Delete</option>
    </select>
</form>

So now as well as dynamically changing the form action, I also change the method to one of get, patch or post accordingly. And for the delete operation I can give the user an on-screen warning that they are about to delete the selected orders.

Please or to participate in this conversation.