Dragula and Angular 5 - update object location value by dropping - javascript

I have a bag with several divs where each div has a list of items that all have the same value for location, so the "Misc" div has items that all have "location: misc", the "Armor" div has items that all have "location: armor", etc.
I can sort the items into their respective divs but I want to be able to drag an item to another div and then change the item's location value accordingly, but I have no idea how to go about this.
I tried this solution but I must not be understanding it correctly.
Code Snippet - this just consoles 'undefined'
HTML:
<div
[dragula]='"bag-equipment"'
[dragulaModel]="equipmentBagOfHolding"
[attr.data-id]="bag-equipment"
>
<mat-card
*ngFor="let item of equipmentBagOfHolding"
>
{{ item.name }}
</mat-card>
</div>
<div
[dragula]='"bag-equipment"'
[dragulaModel]="equipmentArmor"
[attr.data-id]="bag-equipment"
>
<mat-card
*ngFor="let item of equipmentArmor"
>
{{ item.name }}
</mat-card>
</div>
TS:
dragulaService.drop.subscribe(value => {
const [bagName, e, el] = value;
console.log('id is:', e.dataset.id);
});

I realized that I can get the origin and destination with:
this.dragula.drop.subscribe(value => {
//Bag
console.log(value[0]);
// What is moved
console.log(value[1]);
// Destination
console.log(value[2]);
// Origin
console.log(value[3]);
});
And find the id, of the destination for example, with:
console.log(value[2]['id']);

Related

How do I pass a value from an HTML tag to Vue's data object on each iteration of a for loop

I have a list of users and I have ordered them by title. When the list if created I want to check if the previous user has a different title than the current user and if so do some logic.
<div>
<div v-for="user in the_category`.`users" :key="user.id">
<h1 v-bind:title="user.title" >{{ user.title }}</h1>
// If the title is not the same as the previous user.title I would like the following <h3> below to show
// However if the title is the same as the previous user.title I do not want it to show.
// How do I pass the value/text of the <h1> to my title data variable
// and compare the value with the last value on each loop?
<h3 v-show="user.title != title">This is a new title</h3>
</div>
</div>
</template>
<script>
export default {
props: [
"the_category",
"title"
],
data: function() {
return {
category: this.the_category,
title: "",
};
},
}
</script>
```
You can get the index of your loop in your v-for and then use it to check the full array.
First, replace this:
v-for="user in the_category`.`users"
With this:
v-for="(user, index) in the_category`.`users"
That will get both the current element (user) and its index in the array (index).
Then, replace this:
<h3 v-show="user.title != title">This is a new title</h3>
With this:
<h3 v-show="index === 0 || user.title != the_category`.`users[index - 1].title">This is a new title</h3>
Your <h3> will then be visible if your element is the first (index === 0) or if the current title differs from the previous.

Angular cdkDropList drag element restriction

I am currently using Angular 2 and a drag and drop module from https://material.angular.io/cdk/drag-drop/overview. I have made the drag and drop features work. I have two different types of class objects that i desire to be limited to their own types of drag and drop lists.
This could very likely be solved with grouping the lists but since I am using recursion other issues came up...
Currently I am having every lists inside the same group, meaning that anything can be dragged and dropped in every list (cdkDropListGroup, is positioned in a component before the recursion part is performed).
I am trying to make the lists restricted to only accept either Element or Attribute (but not both), but I have no idea of how to do this...
I have the following:
Classes:
export class Attribute {
name: string;
type: string;
}
export class Element {
id: number;
name: string;
elements: Element[]
attributes: Attribute[];
}
HTML:
<div >
Elements
<div
cdkDropList
[cdkDropListData]="elements"
class="example-list"
(cdkDropListDropped)="drop($event)"
[cdkDropListEnterPredicate]="isElement">
<div type="button" text-align="right" class="btn btnNotInline" (click)="addNewElement()">
<img src="assets/img/IconPlus.png" class="elementListIcon"></div>
<div *ngFor="let element of elements" class="example-box" cdkDrag>
<mat-list>
<mat-list-item>
<mat-form-field appearance="standard dense" class="example-container">
<input matInput placeholder="{{element.name}}">
</mat-form-field>
</mat-list-item>
<mat-list-item>
<div
cdkDropList
[cdkDropListData]="attributes"
class="cdk-drag-list-attributes"
(cdkDropListDropped)="drop($event)"
[cdkDropListEnterPredicate]="isAttribute">
<div type="button" text-align="right" class="btn btnNotInline" (click)="addNewAttribute()">
<img src="assets/img/IconPlusPurple.png" class="elementListIcon"></div>
<div *ngFor="let attribute of attributes" class="example-container" cdkDrag>
<p class="mat-input-element-attribute">
<input matInput placeholder="{{attribute.name}}">
<input matInput placeholder="{{attribute.type}}">
</p>
</div>
</div>
</mat-list-item>
<mat-list-item>
<app-listboardelement [attributes]="element.attributes" [elements]="element.elements"></app-listboardelement>
</mat-list-item>
</mat-list>
</div>
The ts. method being called (the attribute looks alike)
isElement(drag : CdkDrag){
console.log("check " + (drag instanceof Element) + typeof drag + " , "+ typeof drag.data + ", "+ drag.data + " , " +(drag.data instanceof Element));
return (drag.data instanceof Element);
}
from the output I simply gets: "check false object , undefined, undefined , false"
From this I have tried to compare the dragged object with a class.. but I didn't have any luck.
Is there any way I can limit dragged object to certain lists dynamically? I know about [cdkDropListConnectedTo] but this gave me issues with the occuring recursion and the bindings. Any guidance would be appreciated
EDIT:
Added image for presentation of how it is displayed - but does not work properly;
You can always check the drag-n-drop 'origin to destination' containers and take action accordingly, something like :
drop(event: CdkDragDrop<string[]>) {
// same container (just reorder items)
if (event.previousContainer === event.container) {
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
} else {
// from first list to second list
if (event.previousContainer.id === 'cdk-drop-list-0' && event.container.id === 'cdk-drop-list-1') {
// do something
}
// from second list to first list
if (event.previousContainer.id === 'cdk-drop-list-1' && event.container.id === 'cdk-drop-list-0') {
// do something
}
}
}
Hope this helps!
The ts. method being called (the attribute looks alike)
isElement(drag : CdkDrag){
console.log("check " + (drag instanceof Element) + typeof drag + " , "+ typeof drag.data + ", "+ drag.data + " , " +(drag.data instanceof Element));
return (drag.data instanceof Element);
}
drag.data does not work, because you didn't assign any data to your cdkDrag via [cdkDragData]
<div *ngFor="let element of elements" class="example-box" cdkDrag [cdkDragData]="element">
...
</div>
To your question... You can either create two sets of cdkDropList arrays for elements and attributes and connect the list groups with the [cdkDropListConnectedTo] binding or connect all lists together in one array and allow the drop with the [cdkDropListEnterPredicate]="isElement" you already mentioned.
To solve the problem regarding recursion, you need to do some additional steps and check if the current drop container is the right one.
I've a detailed description in my question regarding nesting.
Angular Nested Drag and Drop / CDK Material cdkDropListGroup cdkDropList nested

Remove the selected option from select box

I am making angular application with angular form.
Here i have given a form with input fields first name and last name which will always showing..
After that i am having children which will be displayed upon clicking the add button and the children will get removed on click remove button.
As of now everything works fine.
Here i am making patching of data to the inputs on click option from select box.. The neccessary inputs gets patched..
HTML:
<div>
<form (ngSubmit)="onSubmit()" [formGroup]="form">
<div *ngFor="let question of questions" class="form-row">
<ng-container *ngIf="question.children">
<div [formArrayName]="question.key">
<div *ngFor="let item of form.get(question.key).controls; let i=index" [formGroupName]="i">
<div *ngFor="let item of question.children">
<app-question [question]="item" [form]="form.get(question.key).at(i)"></app-question>
</div>
</div>
<select multiple (change)="changeEvent($event)">
<option *ngFor="let opt of persons" [value]="opt.key">{{opt.value}}</option>
</select>
</div>
</ng-container>
<ng-container *ngIf="!question.children">
<app-question [question]="question" [form]="form"></app-question>
</ng-container>
</div>
<div class="form-row">
<!-- <button type="submit" [disabled]="!form.valid">Save</button> -->
</div>
</form> <br>
<!-- Need to have add and remove button.. <br><br> -->
<button (click)="addControls('myArray')"> Add </button>
<button (click)="removeControls('myArray')"> Remove </button><br/><br/>
<pre>
{{form?.value|json}}
</pre>
</div>
TS:
changeEvent(e) {
if (e.target.value == 1) {
let personOneChild = [
{ property_name : "Property one" },
{ property_name : "Property two" },
]
for (let i = 0; i < personOneChild.length; i++) {
this.addControls('myArray')
}
this.form.patchValue({
'myArray': personOneChild
});
}
if (e.target.value == 2) {
let personTwoChild = [
{ property_name : "Property three" },
{ property_name : "Property four" },
{ property_name : "Property five" },
]
for (let i = 0; i < personTwoChild.length; i++) {
this.addControls('myArray')
}
this.form.patchValue({
'myArray': personTwoChild
});
}
}
addControls(control: string) {
let question: any = this.questions.find(q => q.key == control);
let children = question ? question.children : null;
if (children)
(this.form.get(control) as FormArray).push(this.qcs.toFormGroup(children))
}
removeControls(control: string) {
let array = this.form.get(control) as FormArray;
array.removeAt(array.length - 1);
}
Clear working stackblitz: https://stackblitz.com/edit/angular-x4a5b6-fnclvf
You can work around in the above link that if you select the person one option then the value named property one and property two gets binded to the inputs and in select box the property one is highlighted as selected..
The thing i am in need is actually from here,
I am having a remove button, you can see in demo.. If i click the remove button, one at last will be got removed and again click the last gets removed..
Here i am having two property one and two, if i remove both the inputs with remove button, the the highlighted value person one in select box needs to get not highlighted.
This is actually my requirement.. If i remove either one property then it should be still in highlighted state.. Whereas completely removing the both properties it should not be highlighted..
Hope you got my point of explanation.. If any needed i am ready to provide.
Note: I use ng-select for it as i am unable implement that library, i am making it with html 5 select box.. In ng-select library it will be like adding and removing the option.. Any solution with ng-select library also appreciable..
Kindly help me to achieve the result please..
Real time i am having in application like this:
Selected three templates and each has one property with one,two,three respectively:
If choose a dropdown then the property values for the respective will get added as children.
Here you can see i have deleted the property name three for which the parent is template three and the template three still shows in select box even though i removed its children
Firstly, get a reference to the select, like so:
HTML:
<select multiple (change)="changeEvent($event)" #mySelect>
<option *ngFor="let opt of persons" [value]="opt.key">{{opt.value}}</option>
</select>
TS:
import { ViewChild } from '#angular/core';
// ...
#ViewChild('mySelect') select;
Then, in your remove function, check if all elements have been removed, and if they have, set the value of the select to null
if (array.length === 0) {
this.select.nativeElement.value = null;
}
Here is a fork of the StackBlitz

Angular/Javascript - Hide button with id onclick

I have multiple buttons on one page, "Add to cart" buttons where each button has a unique id attribute.
I want to hide a particular button when the user clicks on it.
The issue:
What's happening currently is that when a user clicks on a button 1 it hides, then clicks on button 2 it hides but on the same time it shows button 1
The expected behavior:
When the user clicks on button 1 it should hide and keep hiding even after clicking on button 2
P.S. the information of the buttons (products) gets added to an array.
Current code:
Html:
<div *ngFor="let product of products; let i = index">
<div *ngIf="hideButton != i" [attr.id]="i" class="addButton" (click)="addToCart(product, i)">ADD</div>
</div>
JS
addToCart(itemDetails, index) {
this.hideButton = index;
}
You need an array of hidden buttons and you need to add the index to that array:
JS:
// at the top
hiddenButtons = [];
addToCart(itemDetails, index) {
this.hiddenButtons.push(index);
}
HTML:
<div *ngFor="let product of products; let i = index">
<div *ngIf="hiddenButton.indexOf(i) === -1" [attr.id]="i" class="addButton" (click)="addToCart(product, i)">ADD</div>
</div>
If you have a cart to which products are being added, you can look in the cart to check whether the product already exists in it, and use that to decide whether to display the ADD button.
If your product objects can have more properties to them, you can do away with indexes completely.
HTML
<div *ngFor="let product of products">
<div *ngIf="productInCart(product)" [attr.id]="product.id" class="addButton" (click)="addToCart(product)">ADD</div>
</div>
JS
productInCart(product) {
return this.products.findIndex(p => p.id==product.id)!=-1;
}
addToCart(product) {
this.products.push(product);
}
<div *ngFor="let product of products; let i = index">
<div *ngIf="!product.isHidden" [attr.id]="i" class="addButton" (click)="addToCart(product, i)">ADD</div>
</div>
In component
addToCart(itemDetails, index) {
itemDetails.isHidden = true;
this.products[index] = itemDetails;
}
Logic behind this is to create a new property in product when it clicked for add to cart. Initially there will be no property with name isHidden. SO, it will return undefined and undefined will treat as false.
I would suggest the following:
<div *ngFor="let product of products; let i = index">
<div *ngIf="!isInCart(product)" [attr.id]="i" class="addButton" (click)="addToCart(product, i)">ADD</div>
</div>
private hiddenProducts = new Set<FooProduct>();
products: FooProduct[] = [];
loadProducts(){
this.products = // some value
hiddenProducts = new Set<FooProduct>();
}
isInCart(product: FooProduct): boolean {
return this.hiddenProducts.has(product);
}
addToCart(product: FooProduct, index: number){
// optional: check if the element is already added?
this.hiddenProducts.add(product);
// rest of your addToCart logic
}
Why using a set instead of a simple array?
Performance: access time is constant.
Why not use the index as identifier?
Weak against list mutations (filter, reorder, etc)

Angular 4 *ngFor, ngx-pipes get name of top-level key

I'm using the ngx-pipes in my ionic 3 app because I am pulling an object of objects from firebase.
I retrieve my objects and turn it into a variable that I can use on the page:
getEvents() {
this.firebaseDatabase.getEvents.subscribe(data => {
console.log("Events ", data);
this.events = data;
}, error => {
console.log("Events ", error);
});
}
I log the data and it comes back like this:
eventIDHashed_1:
endDateTime:"tomorrow"
location:"somewhere"
roles:
GyyapYhHQDOriruHLvGPKaTOiRp2:"admin"
startDateTime:"today"
title:"Arisss & Nathan"
type:"wedding"
eventIDHashed_2:
endDateTime:"tomorrow"
location:"somewhere"
roles:
GyyapYhHQDOriruHLvGPKaTOiRp2:"admin"
startDateTime:"today"
title:"Jack & Marlana"
type:"wedding"
The way I'm displaying it on the page is a temporary solution, but everything is working fine except the first line. I need to get the event name (the key) where it says eventIDHashed_1 and eventIDHashed_2
<div class="event-container">
<div *ngFor="let event of events | values; let i = index">
<span>{{events[i]}}</span>
<span>{{event.title}}</span>
<span>{{event.location}}</span>
<span>{{event.type}}</span>
<span *ngFor="let role of event.roles | pairs">
<span>{{role[0]}}, {{role[1]}}</span>
</span>
<span>{{event.startDateTime}}</span>
<span>{{event.endDateTime}}</span>
</div>
</div>
I have everything working and am able to retrieve all information, except the actual event ID <span>{{events[i] | keys}}</span> This gives me a list of all of the keys inside of it (endDateTime, location, title, startDateTime...), but I need to get the id of the event eventIDHashed_1 and eventIDHashed_2
After playing with ngx-pipes, I found that I could use the pairs operator fairly simply. I've changed the template from the above to this:
<div *ngFor="let event of events | pairs">
<span>{{event[0]}}</span>
<span>{{event[1].title}}</span>
<span>{{event[1].location}}</span>
<span>{{event[1].type}}</span>
<span *ngFor="let role of event[1].roles | pairs">
<span>{{role[0]}}, {{role[1]}}</span>
</span>
<span>{{event[1].startDateTime}}</span>
<span>{{event[1].endDateTime}}</span>
</div>
ngx-pairs gives me the key (position [0]) and the value (position [1]) of the event

Categories

Resources