Reputation: 24462
I have a list of items in a loop. the array has maximum of 6 items.
<div *ngFor="let item of items; let i=index">
<item-detail [item]="item" [dragula]='"first-bag"' [dragulaModel]='myItems' id="{{item.id}}" position={{i}}></item-detail>
</div>
My desired output is that when the user will drag and drop an item to another location, An API Call will be executed and the new order will be saved in my database.
In order to do this, I need the item ID, and the new location. the location must be a number from 1 to 6 (based on where the user dragged the item..)
What I've done so far is the following method:
private onDrop(args) {
let [e, el] = args;
console.log(el.id)
console.log(el.position)
}
but the ID and position are not working correctly.. I'm sure there is an easier,simplier, and correct way to do this.
Any ideas?
Upvotes: 3
Views: 5904
Reputation: 71
I know that this was a while back...But I struggled so hard with something pretty much exactly like this, hopefully others will benefit from what I figured out:
my html:
<tbody [dragula]='"bag-one"' [dragulaModel]="currentWorkingData" #bag1>
<tr *ngFor="let faq of currentWorkingData; let i = index;" class="faq" [attr.data-id]="faq.id" [attr.data-index]="i" [attr.data-title]="faq.title" [attr.data-description]="faq.description">
<td>
<span>{{ faq.title }}</span>
</td>
<td>
<button (click)="removeFaq(faq)" class="btn btn-xs btn-danger">Remove</button>
<br />
<button (click)="editFaq(faq)" class="btn btn-xs btn-info">Edit</button>
</td>
</tr>
</tbody>
And in my component (typescript) I have the following:
export class CategoriesComponent {
categoryList: any = [];
url: string = '';
updatedCategory: any = [];
constructor(private apiService: ApiService, private dragulaService: DragulaService) {
let currentCategory = this.categoryList;
this.url = apiService.urls.categories;
apiService.get(apiService.urls.categories).subscribe(
data => this.loadCategories(data),
err => this.loadCategories('err'));
dragulaService.setOptions('bag-one', {
revertOnSpill: true
});
dragulaService.drag.subscribe((value: any) => {
let currentCategory = this.categoryList; //onchage event ---> pushing data through
});
dragulaService.drop.subscribe((value: any[]) => { //runs when item being dragged is dropped into new location
let currentCategory = this.categoryList; // --> pushing the data through
const [bagName, e, el] = value;
this.onDrop(value.slice(1)); // --> passing to onDrop
});
}
private onDrop(args: any) {
let [el, target, source] = args;
const rowData = Array.from(target.children);
this.updatedCategory = rowData.map((row: any, index: number) => {
return {
id: row.dataset.id,
name: row.dataset.name,
sideBar: row.dataset.sidebar,
index
}
});
return new Promise((resolve: any, reject: any) => {
this.handleSaveRequest();
});
}
loadCategories(res:any) {
if(res === 'err'){
swal('Ooops!', 'Something went wrong, prease try again.', 'error');
} else {
console.log(res); //returns the current (correct) array
for (let i = 0; i < res.categories.length; i++) {
this.categoryList.push({
id: res.categories[i].id,
value: res.categories[i].name,
sideBar: res.categories[i].sideBar,
index: res.categories[i].index
});
}
}
}
The first time you run this through, you'll have to manually loop an index number into it so that it has an initial value (or set it when saving to the database).
And then, when you drag and drop something ^^^ the ondrop method will run a handleSave method also in the same component (typescript)... For me, I looped through the current values on the page. I think this is really the best way as you are pushing several things through at one time (though, I'm no javascript expert):
handleSaveRequest(): Promise < any > {
const listCatArrange = this.updatedCategory;
const { name, sideBar, id, index } = this.categoryList;
let side_bar = sideBar;
const bodyCL = { name, side_bar, index };
return new Promise((resolve: any, reject: any) => {
let i = 0;
let processRequest = () => {
if(i < listCatArrange.length){
let bodyList = {
name: listCatArrange[i].name,
sideBar: listCatArrange[i].sideBar,
index: listCatArrange[i].index
};
let url = this.apiService.urls.categories;
let curId = listCatArrange[i].id;
this.apiService.patch(url + `/${curId}`, bodyList).subscribe(
data => processRequest(),
err => resolve('err'),
);
i++;
processRequest();
} else{
resolve(true);
}
};
processRequest();
});
}
I hope this helps someone out there. It took me a long long time to figure this out with a friend. There is definitely not a lot out there for dragula documentation on how to do something like this.
Upvotes: 1
Reputation: 795
You need to move your dragula directive in the parent container of your items like this:
<div class="container" [dragula]='"bag-one"' [dragulaModel]='items'>
<div [attr.id]="item.id" [attr.title]="i" class="card-item" *ngFor="let item of items; let i=index">
<item-detail></item-detail>
</div>
</div>
In yourComponent.ts
let [el, target, source] = args;
console.log(el.id);
console.log(el.title);
You could also make use of @Input in your Item-Detail component to enter needed id and position.
<item-detail [iteminfo]="item"></item-detail>
In your Component.ts
Import {Component, Input} from '@angular/core';
@Component({....});
@Input() iteminfo: Item;
Here's what i did in my proj. For my parent component:
import { Component, Input } from '@angular/core';
import { Card } from '../model/card';
import { Item } from '../model/item';
import { dragula, DragulaService } from 'ng2-dragula/ng2-dragula';
@Component({
selector: 'card', //parent component for item component
template: `
<div class="items"[dragula]='"bag-one"' [dragulaModel]='card.items'>
<div class="card-item" *ngFor="let item of card.items; let i = index; trackBy item?.item_Id">
<item [item]="item" [index]="i"></item>
</div>
</div>
})
export class CardComponent {
constructor(private dragulaService: DragulaService)
{
dragulaService.setOptions('bag-one', {
revertOnSpill: true
});
dragulaService.drop.subscribe((value) => {
this.onDrop(value.slice(1));
});
}
}
For the Item Component:
import { Component, Input } from '@angular/core';
import { Item } from '../model/item';
@Component({
selector: 'item',
template: `
{{item.title}} {{index}}
`
})
export class ItemComponent implements OnInit {
constructor(private itemSvc:ItemService) {}
private _index: number;
@Input()item: Item;
@Input()
set index(i: number) {
this._index = i;
// Do something here like save to database.
console.log('item index changed: ', this.item.title + i);
}
// Getter for the index Input property
get index(): number{
return this._index;
}
}
Final Note: See "Intercept input property changes with a setter" at angular.io site under CookBook -> Components Interaction https://angular.io/docs/ts/latest/cookbook/
Upvotes: 5