Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dragula Figures out sort order - how to disable

I am using Dragula for Drag and Drop functionality. It works until I refresh my list from the server side:

this.columnList = newValue;

It seams that Dragula wants to preserve order in the list like it was before so it mess up server side sort order. I don't need this functionality. I have read documentation, tutorials, examples but cannot find how to disable auto-sorting in Dragula.

like image 613
Radenko Zec Avatar asked Apr 24 '17 16:04

Radenko Zec


1 Answers

I'm quite confident there isn't auto sorting, at least by default. Minimal self contained examples are our friend. Well regardless the web could use more examples, and while difficult to prove a negative I will show that in the default case there is no auto sorting, and attempt to guess at where the issue lies.

Sorry only after did I realized that you are using pure JS and probably using AngularJS rather than Angular2. Regardless the following should still be somewhat useful.

First we need a simple code base, lets use Angular-CLI to create the base (https://www.npmjs.com/package/angular-cli): then follow How to set up angular-quickstart with ng2-dragula and we will have the exact same starting base.

Now replace the contents of app.component.html with:

<div><button (click)="inOrderGreekAlphabet()">In Order Greek Alphabet</button></div>
<div><button (click)="reversedGreekAlphabet()">Reversed Greek Alphabet</button></div>
<div><button (click)="displyStateOfItems()">Display state of Items</button></div>
<div>
    <div class='wrapper'>
        <div class='container' [dragula]='"first-bag"'  [dragulaModel]="items">
            <div *ngFor="let item of items">{{item}}</div>
        </div>
    </div>
</div>

Replace the contents of app.component.ts with:

import {Component} from '@angular/core';
import {DragulaService} from 'ng2-dragula/ng2-dragula';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css'],
    viewProviders: [DragulaService]
})
export class AppComponent {
    public items: string[];
    private GREEK_ALPHABET:string[] = ["alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "pi", "rho", "sigma", "tau", "upsilon", "phi", "chi", "psi", "omega"];

    constructor() {
        this.inOrderGreekAlphabet();
    }

    public inOrderGreekAlphabet(){
        this.items = this.GREEK_ALPHABET.slice();
    }

    public reversedGreekAlphabet() {
        this.items = this.GREEK_ALPHABET.reverse();
    }

    public displyStateOfItems(){
        alert(JSON.stringify(this.items));
    }    
}

If you run the above you'll find that the model is in sync with the list, as plainly shown with the Display state of Items button. However if we remove [dragulaModel]="items" from app.component.html we will find that the model is not synchronized. It isn't that dragula is doing something extra, it is actually doing something less.


Now to fix the issue...

When synchronizing local state we can attempt this a few ways:

First note that while adding [dragulaModel]="items" does synchronize the list with the array but we still need a hook for our custom server synchronization code, more on that later.

If we ignore the dragula framework and perhaps try to create a getter setter for items. However if we rename all instances of items to _items and then add:

//This is to demonstrate an issue, this is NOT A Solution!
get items():string[]{
    console.log("call from getter: could do server sync here?");
    return this._items;
}

set items(i:string[]){
    console.log("call from setter: or perhaps here?");
    this._items = i;
}

However the above WILL NOT WORK, or rather it could work but looking at the console logs shows that the setter is called once and then all subsequent access is done with the getter, and that the getter is called many times during a drag, if the user hovers and moves about possibly hundreds of calls could be generated in a matter of seconds. Further it might be nice to let the user know if the page needs to be saved or not, doing an array comparison on a potentially unbounded list, an indeterminate number of times just doesn't sound like a good idea, and while there may be pure Java/TypeScript solutions that could solve this dragula provides a way to do the check just once when the item is dropped.

Add the following code, replacing the constructor, and delete that setter/getter because it was a silly idea (we are really just interested in the drop part but completeness never hurts):

constructor(private dragulaService: DragulaService) {
    dragulaService.drag.subscribe((value) => {
        console.log(`drag: ${value[0]}`);
        this.onDrag(value.slice(1));
    });
    dragulaService.drop.subscribe((value) => {
        console.log(`drop: ${value[0]}`);
        this.onDrop(value.slice(1));
    });
    dragulaService.over.subscribe((value) => {
        console.log(`over: ${value[0]}`);
        this.onOver(value.slice(1));
    });
    dragulaService.out.subscribe((value) => {
        console.log(`out: ${value[0]}`);
        this.onOut(value.slice(1));
    });
    this.inOrderGreekAlphabet();
}

private onDrag(args) {
    let [e, el] = args;
    //do something
}

private onDrop(args) {
    let [e, el] = args;
    // do something
}

private onOver(args) {
    let [e, el, container] = args;
    // do something
}

private onOut(args) {
    let [e, el, container] = args;
    // do something
}

The above was taken from https://github.com/valor-software/ng2-dragula#events and provides the ability to manually implement the synchronization without the use of the dragulaModel directive but we can also build on that directive as the next section of the documentation shows (https://github.com/valor-software/ng2-dragula#special-events-for-ng2-dragula), so instead of doing the above, minimally we would just need (and we must use the dragulaModel directive in this case):

constructor(private dragulaService: DragulaService) {
    dragulaService.dropModel.subscribe((value) => {
        console.log(`dropModel: ${value[0]}`);
        this.onDropModel(value);
    });
    this.inOrderGreekAlphabet();
}

private onDropModel(args){
    let [bagName, el, target, source] = args;
    //do something, such as sync items with the server.
    //or setting a flag to indicate if items is different from when it was last saved
}

And you should have a nice working solution.

like image 139
Quaternion Avatar answered Oct 21 '22 22:10

Quaternion