sortablejs changes order on second dragStart

Viewed 201

So I've added sortablejs to my blazor application, and it works - for the first drag n drop.

First, here's what's happening:

enter image description here

Blazor component code:

<ul class="ulManufacturers">
    @foreach (var m in manufacturers)
    {
        var mGrayedOut = m.Visible ? "" : "grayout";
        var toggleButtonImg = showManufacturerBikes[m] ? "up" : "down";
        var toggleButtonTitle = showManufacturerBikes[m] ? $"Sakrij {m.Nickname} motore" : $"Prikaži {m.Nickname} motore";
        <li data-id="@m.Id">
            <div class="divManufacturerHeader">
                <div class="buttons">
                    <img class="zoom-on-hover cursor-grab fit-image grayout drag-handle"
                         src="/lib/bootstrap-icons-1.5.0/list.svg"
                         data-toggle="tooltip" data-placement="top"
                         title="Uhvati i premesti"/>

                    <img class="zoom-on-hover cursor-pointer fit-image grayout"
                         src="/lib/bootstrap-icons-1.5.0/plus-circle.svg"
                         data-toggle="tooltip" data-placement="top"
                         title="Dodaj novi @m.Nickname motor"
                         @onclick="()=>AddBike(m)" />

                    <img class="zoom-on-hover cursor-pointer fit-image grayout"
                         src="/lib/bootstrap-icons-1.5.0/arrow-@(toggleButtonImg)-circle.svg"
                         data-toggle="tooltip" data-placement="top"
                         title="@toggleButtonTitle"
                         @onclick="()=>ToggleBikes(m)" />
                </div>
                <div class="divLogoOrName">
                    @if (string.IsNullOrEmpty(m.LogoUrl))
                    {
                        <h1 class="zoom-on-hover cursor-pointer @mGrayedOut"
                            @onclick="()=>SelectManufacturer(m)">
                            @m.Nickname
                        </h1>
                    }
                    else
                    {
                        <img class="zoom-on-hover cursor-pointer fit-image imgManufacturerLogo @mGrayedOut"
                             src="@m.LogoUrl"
                             @onclick="()=>SelectManufacturer(m)" />
                    }
                </div>
            </div>
            <div class="divBikeThumbnailsContainer sortable-list @(showManufacturerBikes[m] ? "" : "hidden")">
                @foreach (var b in m.Bikes)
                {
                    var bGrayedOut = b.Visible ? string.Empty : "grayout";
                    <div class="sortable-list-grid-item drag-handle @bGrayedOut" @onclick="() => SelectBike(b)">
                        <img class="zoom-on-hover cursor-grab fit-image grayout"
                             src="/lib/bootstrap-icons-1.5.0/list.svg"
                             data-toggle="tooltip" data-placement="top"
                             title="Uhvati i premesti" />
                        <BikeThumbnailPage Bike="b" />
                    </div>
                }
            </div>
        </li>
    }

    @if (showButtonAddManufacturer)
    {
        <li>
            <div class="divManufacturerHeader">
                <img class="zoom-on-hover cursor-pointer fit-image grayout"
                     src="/lib/bootstrap-icons-1.5.0/plus-circle.svg"
                     data-toggle="tooltip" data-placement="top"
                     title="Dodaj novog proizvodjača"
                     @onclick="AddManufacturer" />
            </div>
        </li>
    }
</ul>

@code {

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var objRef = DotNetObjectReference.Create(this);
            await jsRuntime.InvokeVoidAsync("EnableManufacturerSorting", objRef);
        }

        await base.OnAfterRenderAsync(firstRender);
    }


    [JSInvokable]
    public void OnManufacturerDragStart()
    {
        // hide all manufacturer bikes
        foreach (var m in manufacturers)
            showManufacturerBikes[m] = false;

        showButtonAddManufacturer = false;

        StateHasChanged();
    }

    [JSInvokable]
    public async void OnManufacturerDragEnd(int[] ids)
    {
        showButtonAddManufacturer = true;

        // set order numbers for manufacturers
        for (int orderNo = 0; orderNo < ids.Length; orderNo++)
            manufacturers
                .First(m => m.Id == ids[orderNo])
                .OrderNumber = orderNo;

        _manufacturerService.UpdateAll(manufacturers);
        //ReloadManufacturers();
        //StateHasChanged();
        //var objRef = DotNetObjectReference.Create(this);
        //await jsRuntime.InvokeVoidAsync("EnableManufacturerSorting", objRef);
    }
}

JavaScript function that's fired on

function EnableManufacturerSorting(dotNetHelper) {

    $('.ulManufacturers').each(function() {
        new Sortable(this, {
            group: 'manufacturers',
            handle: '.drag-handle',
            direction: 'vertical',
            animation: 150,
            onStart: function () {
                console.log("onStart");
                dotNetHelper.invokeMethodAsync('OnManufacturerDragStart');
            },
            onEnd: function () {
                console.log("onEnd");
                var orderList = [];
                $(this.el).children().each(function (index, element) {
                    orderList.push($(element).data('id'));
                });
                var params = JSON.stringify({ 'orderList': orderList });
                dotNetHelper.invokeMethodAsync('OnManufacturerDragEnd', orderList);
                //dotNetHelper.dispose();
                //this.destroy();
            },
        });
    });
}

As you can see from comments in JS function and OnManufacturerDragEnd blazor function, I've tried all combinations with and without commented code, but the result is the same


UPDATE 1

I've found out that if I remove

// set order numbers for manufacturers
for (int orderNo = 0; orderNo < ids.Length; orderNo++)
    manufacturers
        .First(m => m.Id == ids[orderNo])
        .OrderNumber = orderNo;
    _manufacturerService.UpdateAll(manufacturers);

from public async void OnManufacturerDragEnd(int[] ids) it works fine -> but I end up not saving the new order of items.

Same if I leave that in but remove

showButtonAddManufacturer = true;
StateHasChanged();

That means will either not save the new order or that I can't hide the button 'add new' (the plus circle at the bottom of the gif, at the bininging), and bring it back.

I suspect there's asynchronicity between sortablejs and blazors rendering, I just can't quite figure it out yet. The 'add new' button might have something to do with it


UPDATE 2

Check my answer, I've found a workaround. The question is now -> what is exactly happening on the second drag? I'll accept your answer if you can just explain to me what is happening.


UPDATE 3

I think that this is the step in the right direction. Is is possilbe to make jQuery and Blazor work in harmony? jQuery not working properly with my Blazor app

1 Answers

Now, I don't know exactly the reason behind why this does what it does, but a workaround I've found, to both save the new order AND hide/show the add-new-button, is to just hide/show the add-new-button via jQuery instead of Blazor, like this:

function EnableManufacturerSorting(dotNetHelper) {
    console.log('enabled');
    $('.ulManufacturers').each(function () {
        new Sortable(this, {
            group: 'manufacturers',
            handle: '.drag-handle',
            direction: 'vertical',
            animation: 150,
            onStart: function () {
                dotNetHelper.invokeMethodAsync('OnManufacturerDragStart');
                $('.ulManufacturers .hide-while-dragging').hide(); // this line
            },
            onEnd: function () {
                var orderList = [];
                $(this.el).children().each(function (index, element) {
                    var id = $(element).data('id');
                    if (id != null)
                        orderList.push(id);
                });
                dotNetHelper.invokeMethodAsync('OnManufacturerDragEnd', orderList);
                $('.ulManufacturers .hide-while-dragging').show();  // this line
            },
        });
    });
}

Of course, I've also added the hide-while-dragging class to the add-new-button <li> in html.

Now, for more complex situations, I'd like to know what exactly is happening so I won't mark this as an answer. I see someone upvoted my question so that means I'm probably not the only one trying this. Hope someone finds this useful.

Related