In ng2, I want to select multiple items when i click on the item, as of now, it selecting one item where clicked, but, i want to retain already selected items as well when i click on the current selection.
html
<span class="tag" *ngFor="let selectedTagItem of tagsAvailable;let i = index" [ngClass]="{'activeTag': selectedIdx == i}" (click)="selectItem(i)">{{selectedTagItem}}</span>
ts
export class listComponent implements OnInit {
public tagsAvailable:string[] = ['Alabama', 'Alaska', 'Arizona', 'Arkansas',
'California', 'Colorado']
selectedIdx = 0;
selectItem(index):void {
this.selectedIdx = index;
}
}
plnker: http://plnkr.co/edit/7b3VUnGERBspSU1JKEXi?p=preview
this code, currently selecting only one item that i clicked, but, what i am expecting is i want to retain the last selection, previous so on.
Any help
You can also do like this.
selectItem(index,event):void {
console.log(index);
event.target.attributes.class.nodeValue="tag activeTag"
this.itemsSelected.push(this.tagsAvailable[index]);
this.itemsSelectedIndex.push(index)
}
DEMO
You should be using as below
<p><span class="tag"
*ngFor="let selectedTagItem of tagsAvailable;let i = index"
(click)="selectItem(i)">{{selectedTagItem}}</span></p>
{{itemsSelected |json}}
selectItem(index):void {
this.itemsSelected.push(index);
console.log(this.itemsSelected);
}
LIVE DEMO
Related
I want to add the class name 'active' onClick to 'li a' & also remove any 'active' class present on the 'li a'. The current code is working properly if I click sequence from top elements, but when I click elements from bottom to top, it's not working.
component.html
<div class="container text-center">
<ul id="myList" class="pt-5">
<li class="p-3">
List 1
</li>
<li class="p-3">
List 2
</li>
</ul>
</div>
component.ts
linkActive(event) {
const activeClass = event.srcElement.classList.contains('active');
const classFound = document.querySelector('li a');
const hpn = classFound.classList.contains('active');
if (activeClass == true) {
if (hpn == true) {
classFound.classList.remove('active');
}
alert('true');
event.srcElement.classList.remove('active');
} else {
if (hpn == true) {
classFound.classList.remove('active');
}
alert('false');
event.srcElement.classList.add('active');
}
}
Please find the sample code : https://stackblitz.com/edit/angular-ivy-jf9xvp
Do not manipulate the DOM directly like this,
You can use ngClass to achieve the desired result:
template:
List1
.ts
public activeList: number;
...
public linkActive(listNumber: number) {
this.activeList = listNumber;
}
In general, as recommended in comments, do the heroes tutorial and try to understand how to use typescript.
So, I am creating a quiz application where I have a scrollbar for questions and in that scrollbar I have buttons depending on the length of a question set. So I've created those buttons using *ngFor directive. Now what I want to do is whenever a user selects any option (mcq), the question buttons in the scrollbar should get highlighted in following way:
If user selects any option, then change the question button color to Green
If user skips the question, then change the question button color to Yellow
HTML Code for Question Scrollbar:
<div id="answer-buttons" class="ques-grid ">
<button #navBarButton *ngFor="let item of items">{{item}}</button>
</div>
I'm have tried doing it by first accessing the buttons using ViewChild in ts file and then apply some logic, but it's not working, it is only changing the color of first button
#ViewChild('navBarButton',{ static: false }) navBarButton:ElementRef
//and in some function I've tried this logic
if(this.attemptedQuestionCount[this.currentQuestionIndex]==1){
this.navBarButton.nativeElement.style.backgroundColor = "#228B22"
}
else{
this.navBarButton.nativeElement.style.backgroundColor = "#FCF55F"
}
How can I achieve my objective?
You can check for attemptedQuestionCount and change background color like this
<div id="answer-buttons" class="ques-grid ">
<button *ngFor="let question of questions; let i=index"
[style.background-color]="attemptedQuestionCount[i] === 1 ? '#228B22' : '#FCF55F'">{{question}}</button>
</div>
Add button tag as follows:
<button *ngFor="let question of questions; let i=index"
[style.background-color]="attemptedQuestionCount[i] === 1 ? '#228B22' : '#FCF55F'">{{question}}</button>
You can add the click handler directly to the button using
<button *ngFor="let item of items; let indexOfelement=index"
(click)="heyYouClicked(indexOfelement)">{{item}}</button>
And then in the component you place the handler
export class AppComponent {
items = ["hello", "world"]
heyYouClicked(index){
console.log("you clicked " + index)
}
}
You can try ngClass for simplicity.
<button #navBarButton *ngFor="let item of items" class="defualt_state" [ngClass]="{'new_state': (condition_here)}">{{item}}</button>
And in the stylesheet you can have the above class configured
.new_state { background-color: #228B22 !important }
And set the default color of the button this way
.default_state { background-color : #FCF55F}
So when the condition matches it will take the color specified in the new_state class or else will take the default color from default_state class.
I'm working on a project where I need to implement some sort of drop zone where you can drag element from a list, then drop them in a zone where they can be dragged freely. I also would like to use a cdkDropList for the zone, because it provides all the tools for connecting lists.
I used this example as a reference for my implementation, but I am not able to make it work right.
When I drop an item in the zone, it does not drop where the cursor was, it just goes to the top left of my zone, like it was in a list.
When I drag an item in the zone, it either drags correctly to where I want it to be dropped, gets dropped near the drop point, or just goes back to the top left.
Here is my cdkDrag element, it differs from the example linked above because I absolutely need it to be in it's own component (I would like to apply some logic to it in the future), but it is essentially the same concept (cdkDrag div in cdkDropList div). I managed to route all the needed events to the parent element (the zone) using Outputs.
<div class="element-box"
cdkDrag
cdkDragBoundary={{boundary_name}}
(cdkDragDropped)="dragDroppedEventToParent($event)"
(cdkDragStarted)="dragStartedEventToParent($event)"
(cdkDragMoved)="dragMovedEventToParent($event)">
{{object_name}}
<div *cdkDragPlaceholder class="field-placeholder"></div>
</div>
Here is the logic for the drag element:
export class ElementBoxComponent implements OnInit {
#Input () object_name: string;
#Input () boundary_name: string;
#Input () itemSelf: any;
#Output () dragMovedEvent = new EventEmitter<CdkDragMove>();
#Output () dragStartedEvent = new EventEmitter<CdkDragStart>();
#Output () dragDroppedEvent = new EventEmitter<any>();
constructor() {}
ngOnInit(): void {
}
dragMovedEventToParent(event: CdkDragMove) {
this.dragMovedEvent.emit(event);
}
dragStartedEventToParent(event: CdkDragStart){
this.dragStartedEvent.emit(event);
}
dragDroppedEventToParent(event: CdkDragEnd){
this.dragDroppedEvent.emit({event, "self": this.itemSelf});
}
}
Here is my drop zone element, where I render the drag elements (you can see that I routed the Outputs to methods in my logic for the zone):
<div class="drop-container"
#cdkBoard
cdkDropList
[id]="'cdkBoard'"
[cdkDropListData]="itemsInBoard"
[cdkDropListConnectedTo]="connectedTo"
cdkDropListSortingDisabled="true"
(cdkDropListDropped)="itemDropped($event)">
<app-element-box *ngFor="let item of itemsInBoard; let i=index"
object_name="{{item.name}}"
boundary_name=".drop-container"
itemSelf="{{item}}"
style="position:absolute; z-index:i"
[style.top]="item.top"
[style.left]="item.left"
(dragMovedEvent)="elementIsMoving($event)"
(dragStartedEvent)="startedDragging($event)"
(dragDroppedEvent)="stoppedDragging($event)"></app-element-box>
<svg-container class="bin-container" containerId="bin-image-container" *ngIf="_binVisible" height=40>
<svg-image class="bin-icon" [imageUrl]="BIN_ICON_URL" height=40 width=40></svg-image>
</svg-container>
</div>
And here are the relevant methods in the TS file for my drop zone:
itemDropped(event: CdkDragDrop<any[]>) {
if (event.previousContainer === event.container) {
moveItemInArray(this.itemsInBoard, event.previousIndex, event.currentIndex);
} else {
copyArrayItem(
event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex
)
}
}
changePosition(event: CdkDragDrop<any>, field) {
const rectZone = this.dropZone.nativeElement.getBoundingClientRect();
const rectElement = event.item.element.nativeElement.getBoundingClientRect();
const top = rectElement.top + event.distance.y - rectZone.top;
const left = rectElement.left + event.distance.x - rectZone.left;
const out = (top < 0) || (left < 0) || (top > (rectZone.height - rectElement.height)) || (left > (rectZone.width - rectElement.width));
if (!out) {
event.item.element.nativeElement.style.top = top + 'px';
event.item.element.nativeElement.style.left = left + 'px';
} else {
this.itemsInBoard = this.itemsInBoard.filter((x) => x != event.item);
}
}
Again, the only differences are that my elements are encapsulated in their own components, and that the way I access the top and left style elements of the components are different (the code in the example did not work).
I know that the problem is the way I calculate the top and left variable, but I've been stuck on this for a week and cannot seem to find out what's wrong with it.
Here is a short demonstrative video if you want to better visualize what I am talking about.
Does anyone know what could be wrong with this ? I am open to any suggestions, thank you :)
It's difficut without a stackblitz know what is wrong
In this stackblitz I made two ckd-list
One cdkDropList (todoList) it's a typical "list", the other one is a dropZone (doneList). My elements are in the way
{label:string,x:number,y:number,'z-index':number}
The cdkDrag in the dropZone is in the way
<div cdkDrag class="item-box"
[style.top.px]="item.y"
[style.left.px]="item.x"
[style.z-index]="item['z-index']"
>
I choose that the todoList is connected to the dropZone, but the dropZone is not connected to anything. When I mover the elements of the dropZone if it's move away this one, simply add to the list
We need get the doneList as ElementRef
#ViewChild('doneList',{read:ElementRef,static:true}) dropZone:ElementRef;
And the neccesary functions
drop(event: CdkDragDrop<any[]>) {
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,
);
event.item.data.y=(this._pointerPosition.y-this.dropZone.nativeElement.getBoundingClientRect().top)
event.item.data.x=(this._pointerPosition.x-this.dropZone.nativeElement.getBoundingClientRect().left)
this.changeZIndex(event.item.data)
}
this.posInside={source:null,x:0,y:0}
}
moved(event: CdkDragMove) {
this._pointerPosition=event.pointerPosition;
}
changeZIndex(item:any)
{
this.done.forEach(x=>x['z-index']=(x==item?1:0))
}
changePosition(event:CdkDragDrop<any>,field)
{
const rectZone=this.dropZone.nativeElement.getBoundingClientRect()
const rectElement=event.item.element.nativeElement.getBoundingClientRect()
let y=+field.y+event.distance.y
let x=+field.x+event.distance.x
const out=y<0 || x<0 || (y>(rectZone.height-rectElement.height)) || (x>(rectZone.width-rectElement.width))
if (!out)
{
field.y=y
field.x=x
}
else{
this.todo.push(field)
this.done=this.done.filter(x=>x!=field)
}
}
The .html like
<div class="wrapper">
<div
cdkDropList
#todoList="cdkDropList"
[cdkDropListData]="todo"
[cdkDropListConnectedTo]="[doneList]"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
<div
class="example-box"
*ngFor="let item of todo"
cdkDrag
[cdkDragData]="item"
(cdkDragMoved)="moved($event)"
>
{{ item.label }}
<div *cdkDragPlaceholder class="field-placeholder"></div>
</div>
</div>
<div
cdkDropList
#doneList="cdkDropList"
[cdkDropListData]="done"
class="drag-zone"
cdkDropListSortingDisabled="true"
(cdkDropListDropped)="drop($event)"
>
<ng-container *ngFor="let item of done">
<div
cdkDrag
class="item-box"
[style.top.px]="item.y"
[style.left.px]="item.x"
[style.z-index]="item['z-index']"
(cdkDragStarted)="changeZIndex(item)"
(cdkDragDropped)="changePosition($event, item)"
>
{{ item.label }}
<div *cdkDragPlaceholder class="field-placeholder"></div>
</div>
</ng-container>
</div>
</div>
I have a lot of buttons in my *ngFor, and I want that when someone click's on a button - it becomes active(it gets active class).
What I've done :
HTML :
<button
[ngClass]="{'activeBtn': buttActive }"
(click)="addDistrict(item);changeActive(i)"
*ngFor="let item of items; let i = index"
ion-button
#disable>
{{item.name}}
</button>
TS : (changing all buttons class to active (i want to change only that one i clicked)
buttActive = false;
changeActive(i) {
console.log(i);
this.buttActive = !this.buttActive;
}
have a buttActive property in the object and change it
button [ngClass]="{'activeBtn': item.buttActive }" (click)="addDistrict(item);changeActive(item,i)"
*ngFor="let item of items; let i = index" ion-button #disable>{{item.name}}</button>
changeActive(item, i){
console.log(i);
item.buttActive = !item.buttActive;
}
If you don't want to create a property on each item, then create a lastClickedIndex property in your Component class and set it with the index of the button that was clicked:
lastClickedIndex;
changeActive(i) {
this.lastClickedIndex = i;
}
And in your template, check for the lastClickedIndex button based on index to apply the activeBtn class.
<button
*ngFor="let item of items; let i = index"
[ngClass]="(lastClickedIndex == i) ? 'activeBtn': ''"
(click)="addDistrict(item);changeActive(i)"
ion-button
#disable>
{{item.name}}
</button>
That way you won't have to create a property on each item object. This will also take care of removing the class from the previously selected button when some other button is clicked.
Here's a StackBlitz for your ref.
I have six square views in my project, I have to options to click on them:
click on one and and it get selected with some css fade color on top of it to show its selected. this already works.
select couple of them and having the same css fade color to show the squares are selected. this works to some degree but messed with something else.
so to recognize which mode im at I have radio buttom that call multipleSelectionMode.
so this is the code:
this is my square component html file.
<div *ngIf="oneChoosed" layout="row" layout-align="center center" class="one-chose">
</div>
<div *ngIf="multipleChoosed" layout="row" layout-align="center center" class="omult-chose">
</div>
in this component class I have 2 inputs I get from the main screen component:
#Input() oneChoosed: boolean;
#Input() multipleChoosed: boolean;
this is have I update this inputs from the main screen component html file:
[oneChoosed]="isOneChoosed(squares)"
[multipleChoosed]="isMultipleChoosed(squares)"
and in the main screen component class I get the click events and the id's of the squares, it looks like this:
export class mainScreenComp implements OnInit, OnDestroy {
multipleSelectionMode: Observable<boolean>;
oneChoosenIds: string[];
multipleChosenIds: string[];
_oneChoseSquareIdsSubscription: Subscription;
_multipleChoseSquaresIdsSubscription: Subscription;
ngOnInit() {
this.multipleSelectionMode = this.brainComp.curSelectionTyp.map(selecType => selecType === SelectionType.multiple);
this._oneChoseSquareIdsSubscription = this.brainComp.choosingOption.subscribe(
(chos: ChoosingOption) => {
switch (chos.option) {
case ChoosingOption.oneChose:
this.oneChoosenIds = chos.chosedIds.squares;
break;
case ChoosingOption.finishedOneSel:
this.oneChoosenIds = undefined;
break;
}
}
);
this._multipleChoseSquaresIdsSubscription = this.brainComp.choosingOption.subscribe(
(chos: Chosed) => {
this.multipleChosenIds = chos.squares;
}
);
}
isOneChoosed(square: SquaresIQ): boolean {
return (this.oneChoosenIds ? this.oneChoosenIds.includes(square.squareId) : false);
}
isMultipleChoosed(square: SquaresIQ): boolean {
return (this.multipleChosenIds ? this.multipleChosenIds.includes(square.squareId) : false);
}
so basically I add squares id's to the arrays oneChoosenIds and multipleChosenIds and check if they empty or not to decide what to return,
this works, BUT, when im in oneChoosing state where I can
only choose one, when I click on it I see both css square covers that shows that the square is selected...cause they have different design I can notice it.
so it means this *ngIf="oneChoosed" and this *ngIf="multipleChoosed" both returned true...so I thought to put extra condition with my observer multipleSelectionMode in the _multipleChoseSquaresIdsSubscription subscription, but this is async cause its an observer and the other actions are sync so its not working properly. do you have a suggestion for me to solve it?