twg_'s avatar
Level 6

Drag and Drop sorting

I have setup sortable with jQuery UI and I'm able to drag and drop the items in any order I want. What I'm having issues with is saving the order after I move each item.

web.php

Route::prefix('manage')->group(function () {
    Route::prefix('pages')->group(function () {
        Route::post('/reposition', 'RepositionController@post');
    });
});

RepositionController@post

public function submit(Request $request)
{
        dd($request->all());

        if ($request->has('item')) {
            $i = 0;

            foreach ($request->get('item') as $id) {
                print_r($id); die();
                $i++;
                $page = \App\Page::find($id);
                $page->menu_order = $i;
                $page->save();
            }

            return response()->json(array('success' => true));
        } else {
            return response()->json(array('success' => false));
        }
 }

Blade File (index.blade.php)

<div id="pages">
@foreach($pages as $page)
<div class="page" id="{{ $page->id }}">
    <div class="handle"></div>
    <div class="page-details">
        DETAILS HERE
    </div>
</div>
@endforeach
</div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script type="text/javascript">
    $('#pages').sortable({
        'containment': 'parent',
        'revert': true,
        helper: function(e, div) {
            var $originals = div.children();
            var $helper = div.clone();
            $helper.children().each(function(index) {
                $(this).width($originals.eq(index).width());
            });
            return $helper;
        },
        handle: '.handle',
        update: function(event, ui) {
            var url = '{{ route("pages.reposition", ":id") }}';
            url = url.replace(':id', ui.item.attr('id'));
            $.post(
                url,
                $(this).sortable('serialize'),
                function(data) {
                    if (!data.success) {
                        alert('Whoops, something went wrong.');
                    }
                },
                'json'
            );
        }
    });
</script>
0 likes
10 replies
twg_'s avatar
Level 6

I forgot to add when I look at the dd in the networking tab and response. I get an array of array:1 [ 2 => null ]

Snapey's avatar

You should have a list of page IDs and send the complete list each time dragging stops or the user presses save

In the controller, iterate through all the passed IDs giving them each the next number in sequence

mstrauss's avatar

If you console.log $(this).sortable('serialize') what is the output?

twg_'s avatar
Level 6

@mstrauss The output is blank. It doesn't return NULL or EMPTY or anything.

mstrauss's avatar

So maybe this:

$( "#pages" ).sortable( "serialize" );

For the data passed to the server instead of:

$(this).sortable('serialize')

Does that console log anything different?

twg_'s avatar
Level 6

That still doesn't output anything to the console.

mstrauss's avatar
mstrauss
Best Answer
Level 14

Hmmm.... Maybe this tip from the JQueryUI site on the serialize method could be it.

Note: If serialize returns an empty string, make sure the id attributes include an underscore. They must be in the form: "set_number" For example, a 3 element list with id attributes "foo_1", "foo_5", "foo_2" will serialize to "foo[]=1&foo[]=5&foo[]=2". You can use an underscore, equal sign or hyphen to separate the set and number. For example "foo=1", "foo-1", and "foo_1" all serialize to "foo[]=1".

So you may have to update your blade like this:

<div class="page" id="page_{{ $page->id }}">
hollyit's avatar

@mstrauss is correct. You need to give each item an id to use serialize (or toArray), or use some other attribute and set the attribute option to the name of that attribute.

I also suggest not sending an update to the server on each update in sortable. That can become a UX nightmare. Either put a submit button in, or use a debounce function.

twg_'s avatar
Level 6

Hi @hollyit @mstrauss ,

I implemented the page_ in the id and the console.log is working now. This will be pretty small reordering of pages so I don't think I will run into a UX nightmare. I've never used a debounce function. Can you share any more on that?

hollyit's avatar

Debounce limits actions to only happen once every X milliseconds. If you do an update, then you set a debounce to say 250ms (1/4 second). If no other updates happen in that time, it sends the ajax call. If another one does happen, then the timer gets reset and it waits another 250ms. There's debounce libraries for JQuery.

Another problem with your implementation as it stands is that you're assuming that calls will be processed on the server in the order they were sent by the browser. There's never a guarantee of that using stateless communications like HTTP/Ajax. Packets can get bounced, a process handling the first request could be a bit slower than the one handling the second, etc. If you keep it as is, then at least put some kind of UI blocker in there to prevent another call from being made too quickly (or before the previous one is complete).

Please or to participate in this conversation.