Working with ng2-dragula. I'm looking to update the orderNumber for every item in a database using the new dropped order.
dragulaService.dropModel.subscribe((value) => {
var drake = dragulaService.find('bag-orders').drake
var models = drake.models
console.log(models)
})
The new model order that it returns does not reflect the order within the bag.
TL;DR: has anyone implemented reordering within a database onDrop with ng2-dragula?
If you want to be able to drag items (without them disappearing) AND fire the dropModel event:
Put the [dragula] and [dragulaModel] directives in the parent element. (For example, contrary to the current doc where it says to put them in the <li>
, you have to put them in the <ul>
Inject the dragularService, and, in the constructor of your component:
Call the dragulaService.setOptions for the bag (you can pass an empty dictionary). If you don't call it, dropModel
callback is not fired
Subscribe to dropModel
The result:
<!--thecomponent.html-->
<h2>Playlists</h2>
<ul [dragula]='"first-bag"' [dragulaModel]='playlists'>
<li *ngFor="let playlist of playlists">
//Inside the component.ts
playlists: Playlist[];
constructor(private youtubeService: YoutubeService, private dragulaService: DragulaService) {
dragulaService.setOptions('first-bag', {})
dragulaService.dropModel.subscribe((value) => {
this.onDropModel(value);
});
}
private onDropModel(args) {
//Here, this.playlists contains the elements reordered
}
I was using the ng2-dragula and It's quite strange on something that I've noticed. The issue I had was the same. Server call is not updating the data object according to the dragged order.
I've just used if clause inside the ngDoCheck lifecycle hook to solve the issue.
It's sorted in the printed object in the console. After digging deeper a bit could find out in the network that the object that is sent with the update server call was the not updated one.
So, it's because the server call is made before the data object is updated.
All I did was adding a global variable which can keep track of drag has updated the data object.
private dropModelUpdated = false;
ngAfterViewInit() {
// this method only changes a variable to execute a method in ngDoCheck
this.dragulaService.drop.subscribe((value) => {
this.dropModelUpdated = true;
});
}
Then, in the ngDoCheck lifecycle hook,
ngDoCheck() {
if (this.dropModelUpdated) { // this excutes if this.dropModelUpdated is true only
const drake = this.dragulaService.find('any_bag_name').drake;
const models = drake.models;
this.modelContent.groups = models[0];
// ...Here... Make the server or DB call to update the model with the changed/sorted order
this.dropModelUpdated = false; // make sure this is there for the next smooth execution
}
}
I finally found a solution.
I have a horizontal list. The first two elements are the ones a I want to ignore and all have the ignore
CSS class. And all elements have the item
class. So markup:
<ul [dragula]='"foo-list"' class="list">
<li class="list ignore">a</li>
<li class="list ignore">b</li>
<li class="list" *ngFor="let item of getList()" [attr.data-id]="item.id">{{ item.name }}</li>
</ul>
Then the TypeScript:
constructor(private dragulaService: DragulaService) {
let dragIndex: number, dropIndex: number;
dragulaService.setOptions('foo-list', {
moves: (el, source, handle, sibling) => !el.classList.contains('ignore'),
accepts: (el, target, source, sibling) => sibling === null || !sibling.classList.contains('ignore'),
direction: 'horizontal'
});
dragulaService.drag.subscribe((value) => {
// HACK
let id = Number(value[1].dataset.id);
if (!!id) {
dragIndex = _.findIndex(this.getList(), {id: id});
}
});
dragulaService.drop.subscribe((value:any[]) => {
// HACK
let elementNode = value[2].querySelectorAll('.item:not(.ignore)').item(dragIndex);
if (elementNode) {
let id = Number(elementNode.dataset.id);
if (!!id) {
dropIndex = _.findIndex(this.getList(), {id: id});
}
}
if (value[2] === value[3]) { // target === source
if (dragIndex >= 0 && dropIndex >= 0 && dragIndex !== dropIndex) {
let temp: any = this.list[dragIndex];
this.list[dragIndex] = this.list[dropIndex];
this.list[dropIndex] = temp;
}
}
// Custom code here
});
}
getList(): any[] {
return this.list;
}
I found Marçal's answer incredibly helpful, and have even expanded on it a little to post an update to my DB to update the sequence value of each item (contacts within an organization, in my case) in the list:
HTML (container is the contact's organization. a contact, in my case, can't be moved between organizations):
<div class="container" [dragula]="'org-' + org.id" [dragulaModel]="org.contacts">
<nested-contact *ngFor="let contact of org.contacts" [user]="contact" class="contact" [attr.data-id]="contact.id"></nested-contact>
</div>
JS (in my Contact Service, a PUT function to update the stored sequence values associated with each of my contacts, so that their orders persist):
contactsIdPut (id, body) {
let url = '/api/v3/contacts/' + id + '?access_token=' + localStorage.getItem('access_token');
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.put(url, body, options)
.map((response: Response) => {
return response.json();
});
}
JS (in my organization view component, to outline the actions to be taken upon drag and drop, respectively):
export class nestedOrganizationComponent {
orgs = [];
thisOrg: any;
thisContact: any;
constructor (private _contactService: ContactService, private dragulaService: DragulaService) {
this._contactService = _contactService;
let dragIndex: any;
let dropIndex: any;
let elementNode: any;
dragulaService.drag.subscribe((value) => {
let id = Number(value[1].dataset.id);
let orgId: Number = value[0].split('-')[1];
elementNode = value[2].querySelectorAll('.contact:not(.ignore)').item(dragIndex);
if (!!id) {
// this renderedOrgs is just an array to hold the org options that render in this particular view
this._organizationService.renderedOrgs.push(this.org);
this.thisOrg = this._organizationService.renderedOrgs.filter(org => { return org.id == orgId; })[0];
this.thisContact = this.thisOrg.contacts.filter(contact => { return contact.id == id; })[0];
let arr = this.thisOrg.contacts.map(x => { return x.id; });
dragIndex = arr.indexOf(id);
}
});
dragulaService.drop.subscribe((value: any[]) => {
if (elementNode) {
let id = Number(elementNode.dataset.id);
if (!!id) {
let arr = this.thisOrg.contacts.map(x => { return x.id; });
dropIndex = arr.indexOf(id);
}
}
if (value[2] === value[3]) { // target container === source container
if (dragIndex >= 0 && dropIndex >= 0 && dragIndex !== dropIndex) {
this.thisOrg.contacts.forEach((contact, index) => {
contact.sequence = index;
this.updateSequence(contact.id, index);
});
}
}
});
}
updateSequence (id: Number, index: Number) {
let contactBody = {
avatar: {
sequence: index,
}
};
return this._contactService.contactsIdPut(id, contactBody)
.subscribe(
(data: any) => {
// nothing is needed, the same view can apply because the contact has already been moved.
},
(error: any) => {
console.error(error);
}
);
}
}
Hopefully this provides a bit more clarity on the matter to someone else in a spot similar to where I found myself today.