Trouble with Angular Drag and Drop - html

I'm trying to make it so a User has a page where they view their company cards. I want to be able to create containers or just some sorting objects that they can use to organize them.
I created an ng-expansion panel that I want it to be based on. However, I'm having trouble making a proper Drag and Drop to work between them.
I've been using the cdkDragDrop tool and I only managed to make the company cards themselves be drag and dropped on screen but not on the container. I can't seem to be able to make the proper connection, the Angular website cdk page only seems to want to drag and drop sentences, I can't find a way to rework it to drag and drop my cards instead.
My expansion-panel is written like this:
<mat-expansion-panel cdkDropList (opened)="panelOpenState = true" (closed)="panelOpenState = false" (cdkDropListDropped)="drop($event)">
<mat-expansion-panel-header>
<mat-panel-title>
Group Name
</mat-panel-title>
<mat-panel-description>
Currently I am {{panelOpenState ? 'open' : 'closed'}}
</mat-panel-description>
</mat-expansion-panel-header>
</mat-expansion-panel>
and my cards are formatted to where I called the drag and drop here:
<div cdkDropList class="d-flex align-content-start flex-wrap mt-4" (cdkDropListDropped)="drop($event)" *ngIf="!isLoading">
<ng-container *ngFor="let perspective of watchlist">
<div class="watchlist-card" cdkDrag>
<div class="watchlist-card-content">
<div class="d-flex flex-column watchlist-card-body">
<!-- CARD HEADER AND REMOVE ICON -->
<div class="d-flex flex-row justify-content-between">
<p class="card-title">{{ perspective.company.title }}</p>
<button class="button-icon" (click)="removeCompanyFromWatchlist(perspective)">
<mat-icon class="btn-close">close</mat-icon>
</button>
</div>
<!-- 3P-SCORE -->
<ng-container *ngIf="scoreExists(perspective.company.scores) === true">
<div class="d-flex flex-column">
<!-- 3P SCORE HEADER -->
<div class="d-flex flex-row justify-content-between">
<p class="text-header">3P Score</p>
<!-- TOTAL 3P SCORE -->
<p class="text-header"> {{ calculateTotal3PScore(perspective.company.scores)}}/100</p>
</div>
<!-- SCORE BREAKDOWN: People, Product & Performance -->
<ng-container *ngFor="let criteria of scoreCriteria">
<div class="d-flex flex-column">
<!-- SCORE CRITERIA & SCORE OUT OF 100 -->
<div class="d-flex flex-row justify-content-between">
<div class="d-flex flex-row jutify-content-start align-self-center">
<img src={{criteria.icon}} class="score-icon">
<p class="score-label align-self-center">{{criteria.label | titlecase}}</p>
</div>
<p class="score-label">{{ perspective.company.scores[0][criteria.label] }}/100
</p>
</div>
<!-- PROGRESS BAR FOR CRITERIA -->
<div class="d-flex flex-row jutify-content-start slider-bottom">
<div class="slider-top"
[ngStyle]="{width: perspective.company.scores[0][criteria.label]+ '%'}">
</div>
</div>
</div>
</ng-container>
</div>
</ng-container>
<!-- NO 3P SCORE EXISTS -->
<ng-container *ngIf="scoreExists(perspective.company.scores) === false">
<div class="d-flex flex-column">
<p class="text-header">3P Score</p>
<p class="text-body">We don't have the 3P score of this input as of now. You can request the
score and we will get back to you if it’s a
company.</p>
<button class="btn port-btn small-label" *ngIf="!perspective.company.score_requested"
(click)="request3PScore(perspective)">Request 3P Score</button>
<p class="text-body port-orange" *ngIf="perspective.company.score_requested"><b>Your request
has been registered. We'll get back you to shortly.</b></p>
</div>
</ng-container>
<!-- FOOTER: KEWORDS AND ADD BUTTON -->
<div class="d-flex flex-column mt-auto">
<!-- KEYWORD CHIPS -->
<div class="d-flex flex-nowrap watchlist-keywords mt-2 mb-1">
<mat-chip *ngFor="let keyword of perspective.keywords" class="keyword-chip mr-2" [removable]="removable"
(removed)="removeKeywordFromWatchlist(perspective.keywords, keyword, perspective.id)">
{{ keyword }}
<mat-icon matChipRemove *ngIf="removable">close</mat-icon>
</mat-chip>
</div>
<!-- ADD KEYWORD BUTTON -->
<div class="d-flex flex-row flex-fill mr-auto mt-auto">
<!-- INPUT FIELD FOR ADDING KEYWORDS -->
<div class="d-flex flex-row flex-fill justify-content-between align-items-center" *ngIf="perspective.activeAddKeyword">
<div class="d-flex flex-row justify-content-between align-items-center search-bar p-3">
<input [(ngModel)]="keywordInput" type="text" placeholder="Add keyword here"
(keyup.enter)="addKeywordToWatchlist(perspective.keywords, perspective.id)">
<mat-icon matSuffix type="submit" (click)="addKeywordToWatchlist(perspective.keywords, perspective.id)">add</mat-icon>
</div>
<button class="button-curved ml-2 p-1" *ngIf="perspective.activeAddKeyword"
(click)="closeAddKeywordInput(perspective)">
Cancel
</button>
</div>
<button class="button-icon button-raised mt-auto" *ngIf="!perspective.activeAddKeyword" (click)="showAddKeywordInput(perspective)">
<mat-icon>add</mat-icon>
</button>
</div>
<div class="card-handle" cdkDragHandle>
<svg width="24px" fill="currentColor" viewBox="0 0 24 24">
<path
d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z">
</path>
<path d="M0 0h24v24H0z" fill="none"></path>
</svg>
</div>
</div>
</div>
</div>
</div>
</ng-container>
</div>
The rest of the card setups follows, this is just where I just the cdkDrag
*Edit, fixed up the cdkDropList as suggested by one of the answers.
*Edit2, Showed the whole card code.

The angular material Drag&Drop works with the concept of lists.
What you can do is connect lists (Drag and drop, or within the same list, or to another list).
So, let's assume, that the expansion panel is a list (It is a list because if you can drag things there, for Angular Material Drag & Drop it is a list)
To connect your expansion panel with cards that are not in there. You are going to have and keep two arrays. The first contain all your cards, and the second contain the cards that are inside the expansion panel
In your expansion panel:
<mat-expansion-panel
cdkDropList
#expansionPanelList="cdkDropList"
[cdkDropListData]="arrayCardsInsideExpansion"
[cdkDropListConnectedTo]="[listOfAllCards]"
(cdkDropListDropped)="drop($event)"
(opened)="panelOpenState = true"
(closed)="panelOpenState = false">
<mat-expansion-panel-header>
<mat-panel-title>
Group Name
</mat-panel-title>
<mat-panel-description>
Currently I am {{panelOpenState ? 'open' : 'closed'}}
</mat-panel-description>
</mat-expansion-panel-header>
<ng-container *ngFor="let perspective of arrayCardsInsideExpansion">
<div class="watchlist-card" cdkDrag>
.
.
.
</div>
</ng-container>
</mat-expansion-panel>
And your cards:
<div cdkDropList
#listOfAllCards="cdkDropList"
[cdkDropListData]="watchlist"
[cdkDropListConnectedTo]="[expansionPanelList]"
class="d-flex align-content-start flex-wrap mt-4"
(cdkDropListDropped)="drop($event)">
<ng-container *ngFor="let perspective of watchlist">
<div class="watchlist-card" cdkDrag>
.
.
.
Finally in your .ts Code, your drop Function:
import {CdkDragDrop, moveItemInArray, transferArrayItem} from '#angular/cdk/drag-drop';
.
.
.
.
drop(event: CdkDragDrop<string[]>) {
if (event.previousContainer === event.container) {
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
} else {
transferArrayItem(event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex);
}
}
}
If you have a lot of expansion panel, check CdkDropListGroup

Related

Can anyone explain how to list the objects vertically instead of horizontal?

*Here is the code. With this code listing is done vertically.Here is the current output I want to make this vertically.
*This is the current code I written.
{% extends 'index.html' %}
{%block body_block%}
{%for p in carslist%}
<section class="py-5">
<div class="container px-4 px-lg-5 mt-2">
<div class="row gx-4 gx-lg-5 row-cols-2 row-cols-md-3 row-cols-xl-4 justify-content-center">
<div class="col mb-5">
<div class="card h-100">
<!-- Product image-->
<img class="card-img-top" src="{{p.carpic.url}}" alt="..." />
<!-- Product details-->
<div class="card-body p-4">
<div class="text-center">
<!-- Product name-->
<h5 class="fw-bolder">{{p.carcompany}}</h5>
<p>{{p.carmodel}}</p>
<!-- Product price-->
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">Price: {{p.carprice}}</li>
<li class="list-group-item">Kms: {{p.carkms}}</li>
</ul>
</div>
<!-- Product actions-->
<div class="card-footer p-4 pt-0 border-top-0 bg-transparent">
<div class="text-center"><a class="btn btn-outline-dark mt-auto" href="#">View Details</a></div>
</div>
</div>
</div>
</div>
</div>
</section>
{%endfor%}
For the parent element of each of your products, you need to write the properties: display: flex;
flex direction: row;
flex-wrap: wrap;
In tailwind - flex flex-row flex-wrap
simply remove all the row-cols classes and things should stack up vertically by default.
Classes to remove: row-cols-2 row-cols-md-3 row-cols-xl-4.
When you have a lot of cluttered html like that and you can't make sense of it anymore I find that the best way to go about it is to reduce the structure to the very minimum, and build from there.
Comment your code and rebuild it little step by little step. Then you can see the impact of each individual element or class that you add.

The data is not rendering in the mat-menu

I been trying to create a navbar with a submenu. The nav bar data is rendered successfully but the submenu is not working, I am very new to this. Please let me know where is the problem.
<mat-nav-list class="navigation relative">
<div *ngFor="let link of navLinks; let i=index" style="padding-bottom: 14px;">
<div class="card">
<mat-list-item appAccordionlinks routerLinkActive="active-link" (click)="onClick(link.name)">
<a mat-button [matMenuTriggerFor]="submenu" *ngIf="link?.submenu?.length > 0" appAccordiontoggle
class="relative main-active">
<mat-icon *ngIf="link.icon">{{link.icon}}</mat-icon>
<img class="menu-img" *ngIf="link.img" [src]="link.img" alt="">
<app-menu-icon *ngIf="link.img" [name]="'employee'"></app-menu-icon>
<span>{{link.name}}</span><span fxFlex></span>
</a>
<a *ngIf="!(link?.submenu?.length > 0)" [routerLink]="link.path" appAccordiontoggle
class="relative main-active" mat-ripple fxLayout="row">
<mat-icon *ngIf="link.icon">{{link.icon}}</mat-icon>
<app-menu-icon *ngIf="link.img" [name]="link.name" [color]="link.color"></app-menu-icon>
<!-- <img class="menu-img" *ngIf="link.img" [src]="link.img" alt=""> this is need to mark as comment -->
<span>{{link.name}}</span>
</a>
</mat-list-item>
</div>
</div>
</mat-nav-list>
<mat-menu #submenu>
<ng-container *ngFor="let sublink of link?.submenu">
<ng-container *ngIf="link?.submenu?.length > 0">
<button mat-menu-item>
<img class="menu-img" *ngIf="sublink.img" [src]="sublink.img">
<span>{{sublink.name}}</span>
</button>
</ng-container>
</ng-container>
</mat-menu>
The mat-menu element is out of the scope of link variable. You need to include it (mat-menu) in the div you are dynamically rendering the links, i.e. within link's scope:
<div *ngFor="let link of navLinks; let i = index"...>
<--! card html -->
<mat-menu #submenu>
<--! submenu child elements... -->
</mat-menu>
</div>

Html : view an id outside its parent id

I have this collapse in my web app :
<div id="accordionExample" >
#foreach ($mysection as $section)
<?php $sec_name='home.sec'.$section->section_name ;
$collaps_count++;
$nf = new NumberFormatter("en", NumberFormatter::SPELLOUT);
$numbers_name=$nf->format($collaps_count);
$numbers_name=ucfirst($numbers_name);
$collapse_id='collapse'.$numbers_name;
$heading='heading'.$numbers_name;?>
<div class="card">
<div class="ard-header py-0 px-0" id="$heading" style="border: none">
<button class=" btn w-100 text-right bg-light " type="button" data-toggle="collapse" data-target="#{{$collapse_id}}" aria-expanded="true" aria-controls="{{$collapse_id}}" >
<h5 class="">{{__($sec_name)}}</div></h5>
</button>
</div>
<div style="height:5px"></div>
<div id="{{$collapse_id}}" class="collapse <?php if($collaps_count == 1) echo 'show'?>" aria-labelledby="{{$heading}}" data-parent="#accordionExample">
#foreach ($course->Lession as $lession)
#if ($section->section_name == $lession->section_name)
<div class=" card-body flex-row d-flex justify-content-between">
<div>- {{$lession->$less_lang}}</div>
#if ($lession->id==$order->lession_id)
<div class="text-left ml-2"><i class="material-icons" style="color:green">visibility</i></div>
#else
<div class="text-left ml-2"><i class="material-icons" style="color:grey">visibility</i></div>
#endif
</div>
#endif
#endforeach
</div>
</div>
#endforeach;
</div>
<!-- End of Lessionss Card -->
</div>
The problem is while looping the collapse :
first row is start inside (accordionExample) id correctly.
second row start outside (accordionExample) id.
this row start with the parent of (accordionExample) id as show in image :
You have 2 errors in your markup:
Closing div inside button tag:
<h5 class="">{{__($sec_name)}}</div></h5>
Replace by
<h5 class="">{{__($sec_name)}}</h5> OR <h5 class=""><div>{{__($sec_name)}}</div></h5>
Extra div at the end.
your foreach loop is starting after #accordionExample but at the end there is 2 closing tag of div.
#endforeach;
</div>
<!-- End of Lessionss Card -->
</div>
this should be change to
</div>
#endforeach;
<!-- End of Lessionss Card -->
</div>
``

bootstrap4 accordion not working inside ngFor by passing index

i am using bootstrap 4 accordion inside ngFor , I am passing index to the data-target element and to the id , the ids are coming different but accordion is not working, since it is not working with dynamic for loop, below is the code , can any one please help me on this. Thanks in advance
<div class="accordion" id="accordionExample">
<div class="card mb-2" *ngFor="let list of workOrderList; let i=index">
<div class="card-header bg-white" id="heading0">
<h6 class="mb-0">
<a type="button" class="" data-toggle="collapse" data-target="#collapse0">
<i class="fa fa-plus"></i> {{list?.awoNumber}}</a>
</h6>
</div>
<div id="collapse0" class="collapse" aria-labelledby="heading0" aria-expanded="true" data-parent="#accordionExample">
<div class="card-body">
<p>HTML stands for HyperText Markup Language. HTML is the standard markup language for describing the
structure of web pages.
Learn more.
</p>
</div>
</div>
</div>

How to separately collapse dynamic accordions inside a vue js v-for?

I need to separately collapse accordions created inside a vue js v-for. I know that something like Id which can be used to separately identify the accordion will solve this. But don't know where to give this dynamic id?
Here is my HTML
<div role="tablist">
<div v-for="item in productFormData" v-bind:key="item.id">
<b-card no-body class="mb-1">
<b-card-header header-tag="header" class="p-1" role="tab">
<b-button block v-b-toggle.accordion-productdetails variant="info">Product Details</b-button>
</b-card-header>
<b-collapse
id="accordion-productdetails"
visible
accordion="productdetails-accordion"
role="tabpanel"
>
<div class="container-fluid">
<div class="row">
<div class="col-sm-4">
<span style="font-size:13px;font-weight:bold;">Name</span>
<div>
<span id="spnCustomerName">{{item.name}}</span>
</div>
</div>
</div>
</b-collapse>
</b-card>
</div>
To add a dynamic id to it you need to add index to the for loop so every time you use the index to identify the specific accordion like this, or you can use your item.id but i don't know the content of it:
CODEPEN :
https://codepen.io/emkeythekeyem/pen/WNbqeyM?editors=1010
<div role="tablist">
<div v-for="(item,index) in productFormData" v-bind:key="item.id">
<b-card no-body class="mb-1">
<b-card-header header-tag="header" class="p-1" role="tab">
<b-button block v-b-toggle="'accordion-productdetails'+index" variant="info">Product Details</b-button>
</b-card-header>
<b-collapse
:id="'accordion-productdetails'+index"
visible
:accordion="'productdetails-accordion'+index"
role="tabpanel"
>
<div class="container-fluid">
<div class="row">
<div class="col-sm-4">
<span style="font-size:13px;font-weight:bold;">Name</span>
<div>
<span id="spnCustomerName">{{item.name}}</span>
</div>
</div>
</div>
</b-collapse>
</b-card>
</div>
see https://bootstrap-vue.js.org/docs/components/collapse/#usage:
<!-- Using value -->
<b-button v-b-toggle="'collapse-2'" class="m-1">Toggle Collapse</b-button>
EDIT :
Reviewing my code i noticed that the accordion param in <b-collapse needs to change also, just edit it to :
:accordion="'productdetails-accordion'+index"
I also edited the code of my first answer
Using Bootstrap 5 with CDN, first import the <link> and JS bundle in the public/index.html.
The code is from the Bootstrap 5 accordion. Notice that the flush-headingOne has been changed to 'flush-heading'+index.
Notice the accordionCollection could be imported from the js file with data(), or passed down from the parent props. The property title, description could be different defined in the array data.
HTML (template)
<div class="accordion accordion-flush" id="accordionFlushExample">
<div class="accoridon-item" v-for="(accordionItem,index) in accordionCollection" :key="index">
<h2 class="accordion-header" :id="'flush-heading'+index">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
:data-bs-target="'#flush-collapse'+index"
aria-expanded="false"
:aria-controls="'flush-collapse-'+index">
{{ accordionItem.title }}
</button>
</h2>
<div
:id="'flush-collapse'+index"
class="accordion-collapse collapse"
:aria-labelledby="'flush-heading'+index"
data-bs-parent="#accordionFlushExample">
<div class="accordion-body"> {{ accordionItem.description }} </div>
</div>
</div>
</div>