Angular on click event for multiple items - javascript

What I am trying to do:
I am trying to have collapsible accordion style items on a page which will expand and collapse on a click event. They will expand when a certain class is added collapsible-panel--expanded.
How I am trying to achieve it:
On each of the items I have set a click event like so:
<div (click)="toggleClass()" [class.collapsible-panel--expanded]="expanded" class="collapsible-panel" *ngFor="let category of categories">
....
</div>
<div (click)="toggleClass()" [class.collapsible-panel--expanded]="expanded" class="collapsible-panel" *ngFor="let category of categories">
....
</div>
and in the function toggleClass() I have the following:
expanded = false;
toggleClass() {
this.expanded = !this.expanded;
console.log(this.expanded)
}
The issue im facing:
When I have multiple of this on the same page and I click one, they all seem to expand.
I cannot seen to get one to expand.
Edit:
The amount of collapsible links will be dynamic and will change as they are generated and pulled from the database. It could be one link today but 30 tomorrow etc... so having set variable names like expanded 1 or expanded 2 will not be viable
Edit 2:
Ok, so the full code for the click handler is like so:
toggleClass(event) {
event.stopPropagation();
const className = 'collapsible-panel--expanded';
if (event.target.classList.contains(className)) {
event.target.classList.remove(className);
console.log("contains class, remove it")
} else {
event.target.classList.add(className);
console.log("Does not contain class, add it")
}
}
and the code in the HTML is like so:
<div (click)="toggleClass($event)" class="collapsible-panel" *ngFor="let category of categories" >
<h3 class="collapsible-panel__title">{{ category }}</h3>
<ul class="button-list button-list--small collapsible-panel__content">
<div *ngFor="let resource of resources | resInCat : category">
<span class="underline display-block margin-bottom">{{ resource.fields.title }}</span><span class="secondary" *ngIf="resource.fields.description display-block">{{ resource.fields.description }}</span>
</div>
</ul>
</div>

you could apply your class through javascript
<div (click)="handleClick($event)">
some content
</div>
then your handler
handleClick(event) {
const className = 'collapsible-panel--expanded';
if (event.target.classList.contains(className)) {
event.target.classList.remove(className);
} else {
event.target.classList.add(className);
}
}
In plain html and js it could be done like this
function handleClick(event) {
const className = 'collapsible-panel--expanded';
if (event.target.classList.contains(className)) {
event.target.classList.remove(className);
} else {
event.target.classList.add(className);
}
console.log(event.target.classList.value);
}
<div onclick="handleClick(event)">
some content
</div>

Try to pass unique Id. (little modification)Ex: -
in component.ts file:
selectedFeature: any;
categories:any[] = [
{
id: "collapseOne",
heading_id: "headingOne",
},
{
id: "collapseTwo",
heading_id: "headingTwo",
},
{
id: "collapseThree",
heading_id: "headingThree",
}
];
toggleClass(category) {
this.selectedFeature = category;
};
ngOnInit() {
this.selectedFeature = categories[0]
}
in html:-
<div class="collapsible-panel" *ngFor="let category of categories">
<!-- here you can check the condition and use it:-
ex:
<h4 class="heading" [ngClass]="{'active': selectedFeature.id==category.id}" (click)="toggleClass(category)">
<p class="your choice" *ngIf="selectedFeature.id==category.id" innerHtml={{category.heading}}></p>
enter code here
-->
.....
</div>

Try maintaining an array of expanded items.
expanded = []; // take array of boolean
toggleClass(id: number) {
this.expanded[i] = !this.expanded[i];
console.log(this.expanded[i]);
}

Your solution will be the usage of template local variables:
see this: https://stackoverflow.com/a/38582320/3634274

You are using the same property expanded to toggle for all the divs, so when you set to true for one div, it sets it true for all the divs.
Try setting different properties like this:
<div (click)="toggleClass("1")" [class.collapsible-panel--expanded]="expanded1" class="collapsible-panel" *ngFor="let category of categories">
....
</div>
<div (click)="toggleClass("2")" [class.collapsible-panel--expanded]="expanded2" class="collapsible-panel" *ngFor="let category of categories">
....
</div>
TS:
expanded1 = false;
expanded2 = false;
toggleClass(number:any) {
this["expanded" + number] = !this["expanded" + number];
console.log(this["expanded" + number])
}

Related

How to remove the selected data from saved data when we click on button in a selected one

In my application I have saved the data when we click on it(we can add the multiple data by entering some data and save the multiple data by clicking the save button).
.component.html
<ng-container *ngFor="let categoryDetail of selectedCategoryDetails">
<div class="__header">
<div>
<b>{{ categoryDetail.category }}</b>
</div>
</div>
<div
class="clinical-note__category__details"
*ngIf="categoryDetail.showDetails">
<ul>
<li class="habit-list"
*ngFor="let habits of categoryDetail.habitDetails" >
<div class="target-details">
<b>{{ clinicalNoteLabels.target }}: </b
><span class="habit-list__value">{{ habits.target }}</span>
</div>
</li>
</ul>
<div class="habit-footer">
<span class="m-l-10"
[popoverOnHover]="false"
type="button"
[popover]="customHabitPopovers"><i class="fa fa-trash-o" ></i> Delete</span>
</div>
<div class="clinical-note__popoverdelete">
<popover-content #customHabitPopovers [closeOnClickOutside]="true">
<h5>Do you want to delete this habit?</h5>
<button
class="btn-primary clinical-note__save" (click)="deletedata(habits);customHabitPopovers.hide()">yes </button>
</popover-content></div>
</div>
</ng-container>
In the above code when we click on delete button it will show some popup having buttons yes(implemented in above code) and now so my requirement is when we clcik on yes button in from the popover it has to delete the particular one.
.component.ts
public saveHealthyHabits() {
let isCategoryExist = false;
let categoryDetails = {
category: this.clinicalNoteForm.controls.category.value,
habitDetails: this.healthyHabits.value,
showDetails: true,
};
if (this.customHabitList.length) {
categoryDetails.habitDetails = categoryDetails.habitDetails.concat(
this.customHabitList
);
this.customHabitList = [];
}
if (this.selectedCategoryDetails) {
this.selectedCategoryDetails.forEach((selectedCategory) => {
if (selectedCategory.category === categoryDetails.category) {
isCategoryExist = true;
selectedCategory.habitDetails = selectedCategory.habitDetails.concat(
categoryDetails.habitDetails
);
}
});
}
if (!this.selectedCategoryDetails || !isCategoryExist) {
this.selectedCategoryDetails.push(categoryDetails);
}
this.clinicalNoteForm.patchValue({
category: null,
});
this.healthyHabits.clear();
public deletedata(habits){
if (this.selectedCategoryDetails) {
this.selectedCategoryDetails.forEach((selectedCategory) => {
if (selectedCategory.category ==categoryDetails.category) {
isCategoryExist = true;
this.selectedCategoryDetails.splice(habits, 1);
}
});
}
}
The above code I have written is for saving the data(we can enter multiple data and save multiple )
Like the above I have to delete the particular one when we click on yes button from the popover.
Can anyone help me on the same
If you're iterating in your html like:
<... *ngFor="let categoryDetails of selectedCategoryDetails" ...>
and your button with deletedata() is in the scope of ngFor, you can:
Change your iteration to include index of an item and trackBy function for updating the array in view:
<... *ngFor="let categoryDetails of selectedCategoryDetails; let i = index; trackBy: trackByFn" ...>
On the button click pass the index to deletedata() method like:
deletedata(index)
Create your deletedata method like:
deletedata(index:number){
this.selectedCategoryDetails.splice(index, 1);
// other code here, like calling api
// to update the selectedCategoryDetails source
// etc.
}
Create trackByFn method like:
trackByFn(index,item){
return index;
}
EDIT: Without index
If you want to iterate over selectedCategoryDetails in the ts file, without using ngFor with index in your html, you can have your deletedata like this:
deletedata(categoryDetails:any){
for (let i = this.selectedCategoryDetails.length - 1; i >= 0; i--) {
if (this.selectedCategoryDetails[i] === categoryDetails.category) {
this.selectedCategoryDetails.splice(i, 1);
}
}
}
It will iterate over selectedCategoryDetails backwards and remove the categoryDetails if it finds it in the array of objects.
Now, you only need to pass the categoryDetails to deletedata in your html:
(click)="deletedata(categoryDetails);customHabitPopovers.hide()"

Implement two conditions in the presentation of data

I created an image gallery. This gallery may or may not have several types `(A, B ...).
To present the types I am missing the following array: Cats = ["A", "B"].
The images only appear when this array is filled, that is, the size is different from 0 or undefined.
How can I display images, usually, when the size of the Cat array is 0 or undefined?
When the cat array has these conditions, the images appear normally, when the array is different from undefined or greater than 0, it presents the images separated by types as shown in the figure.
Is there a way to implement this without creating two "html"? Can someone help me?
<div style="margin-left: 16px; margin-right: 16px;" class="first" *ngIf="Cats != undefined">
<div *ngFor="let cat of Cats">
<div *ngIf="counts[cat]">
<div class="row">
<span class="nomeCategoria">{{cat}}</span>
</div>
<ul class="mdc-image-list my-image-list">
<ng-container *ngFor="let it of items">
<li class="mdc-image-list__item" *ngIf="it.Cat == cat">
<div class="mdc-image-list__image-aspect-container">
<ng-container *ngIf="it.image == null; else itImage">
<img src="./assets/image-not-found.svg" class="mdc-image-list__image imagenotfound">
</ng-container>
<ng-template #itImage>
<img [src]="it.image" class="mdc-image-list__image">
</ng-template>
</div>
</li>
</ng-container>
</ul>
</div>
</div>
</div>
I'll check if Cats array is empty and if it's then I'll populate an array with all items Cat. Something like this:
export class AppComponent {
ngOnInit(){
this.checkCatsArray()
}
Cats=[]
items=[
{
ID:1,
Cat:"A",
image:"https://material-components-web.appspot.com/images/photos/2x3/3.jpg",
},
{
ID:2,
Cat:"B",
image:"https://material-components-web.appspot.com/images/photos/3x2/10.jpg",
},
{
ID:3,
Cat:"M",
image:"https://material-components-web.appspot.com/images/photos/2x3/6.jpg",
},
]
get counts() {
return this.items.reduce((obj, value) => {
if (value.Cat in obj) {
obj[value.Cat]++;
} else {
obj[value.Cat] = 1;
}
return obj;
}, {});
}
checkCatsArray() {
if (this.Cats.length == 0) {
for (let cat of this.items) {
this.Cats.push(cat.Cat)
}
}
}
}
In this scenario, I didn't touch HTML and I got the desired result - show every Cat if Cats array is empty. Maybe you need to configure checkCatsArray() at your own but I believe that this is something that you are looking for.
Hope that will help!

Filter data in ng-repeat to display only the clicked item with Angularjs

Please help a little bit.
I have a list of 7 events displayed already with Angularjs. I'd like when I click on the <h2> (the event name) of some event, to open an ovelay that displays the same data from the database but only for this event which is clicked.
I'm sure that 'filter' will do the work but it seems I'm doing something wrong.
Here is my code. The ng-app and ng-controller are in the <main> tag.
Angularjs version: 1.7.9
My Html:
<main ng-app="eventsApp" ng-controller="eventsCtrl">
<!-- Overlay that holds and displays a single event -->
<div>
<div ng-repeat="x in singlePageEvent | filter:hasName(x.eventName)">
<div>
<img ng-src="{{x.eventImgSrc}}" alt="{{x.eventImgName}}"/>
<h2 class="event-name">{{x.eventName}}</h2>
<p>{{x.eventTime}}</p>
<p>{{x.eventPlace}}</p>
</div>
</div>
</div>
<!-- A list with all the events -->
<div ng-repeat="x in events">
<div>
<img ng-src="{{x.eventImgSrc}}" alt="{{x.eventImgName}}"/>
<h2 ng-click="singleEventOpen(x)" class="event-name">{{x.eventName}}</h2>
<p>{{x.eventTime}}</p>
<p>{{x.eventPlace}}</p>
</div>
</div>
</main>
My script:
let eventsApp = angular.module('eventsApp', []);
this filter below is not working at all. It continues to show all the events.
eventsApp.filter('hasName', function() {
return function(events, evName) {
var filtered = [];
angular.forEach(events, function(ev) {
if (ev.eventName && ev.eventName.indexOf(evName) >-1) {
filtered.push(ev);
}
});
return filtered;
}
});
eventsApp.controller('eventsCtrl', function($scope, $http) {
let x = window.matchMedia("(max-width: 450px)");
let singleEventOverlay = angular.element(document.querySelector('div.single-event.overlay'));
let singleEvent = singleEventOverlay;
function responsiveEventImages(x) { //this displays the list with events
if (x.matches) {
$http.get('./includes/events_res.inc.php').then(function(response) {
$scope.events = response.data.events_data;
});
} else {
$http.get('./includes/events.inc.php').then(function(response) {
$scope.events = response.data.events_data;
});
}
}
...and then by invoking singleEventOpen() the overlay appears, but it displays all the data, not just the clicked event
$scope.singleEventOpen = function(singleEvent) {
let clickedEvent = singleEvent.eventName; //I got the value of each h2 click thanx to #georgeawg but now what?
console.log("Fetching info for ", singleEvent.eventName);
$http.get('./includes/single_event.inc.php').then(function(response) {
$scope.singlePageEvent = response.data.events_data;
});
singleEventOverlay.removeClass('single-event-close').addClass('single-event-open');
}
});
The php file with the database extraction is working fine so I won't display it here.
What should I do to make the overlay display only the event which <h2> is clicked?
Here is a pic of the list with events
Here is a pic of the overlay
Thanx in advance.
EDITED
I got the value of each h2 click thanx to #georgeawg but now what?
UPDATE
Hey, thanx a lot #georgeawg . After many attempts I finally did this:
$scope.singleEventOpen = function(singleEvent) {
$http.get('./includes/single_event.inc.php').then(function(response) {
let allEvents = response.data.events_data;
for (var i = 0; i < allEvents.length; i++) {
singleEvent = allEvents[i];
}
});
console.log('Fetching data for', singleEvent);
$scope.ex = singleEvent;
});
And it works well.
Change the ng-click to pass an argument to the singleEventOpen function:
<div ng-repeat="x in events">
<div>
<img ng-src="{{x.eventImgSrc}}" alt="{{x.eventImgName}}"/>
<h2 ng-click="singleEventOpen(x)" class="event-name">{{x.eventName}}</h2>
<p>{{x.eventTime}}</p>
<p>{{x.eventPlace}}</p>
</div>
</div>
Then use that argument:
$scope.singleEventOpen = function(singleEvent) {
console.log("Fetching info for ", singleEvent.eventName);
//...
//Fetch and filter the data
$scope.ex = "single item data";
}
Adding an argument is the key to knowing which <h2> element was clicked.
Update
Don't use ng-repeat in the overlay, just display the single item:
<!-- Overlay that holds and displays a single event -->
̶<̶d̶i̶v̶ ̶n̶g̶-̶r̶e̶p̶e̶a̶t̶=̶"̶x̶ ̶i̶n̶ ̶s̶i̶n̶g̶l̶e̶P̶a̶g̶e̶E̶v̶e̶n̶t̶ ̶|̶ ̶f̶i̶l̶t̶e̶r̶:̶h̶a̶s̶N̶a̶m̶e̶(̶x̶.̶e̶v̶e̶n̶t̶N̶a̶m̶e̶)̶"̶>̶
<div ng-if="ex"">
<div>
<img ng-src="{{ex.eventImgSrc}}" alt="{{ex.eventImgName}}"/>
<h2 class="event-name">{{ex.eventName}}</h2>
<p>{{ex.eventTime}}</p>
<p>{{ex.eventPlace}}</p>
</div>
</div>

How can I change the default ordered and unordered list providing various options in Jodit Editor?

I am being able to provide option's in the menu bar but when I click on any of the option it's working absolutely fine,but if I tried to select twice it is changing at all places.
For Ex.
I want to make a ordered list such as:
1.Some Text here
a.option 1 b.option 2
c.option 3 d.option 4.
When I am trying to select some text its also converting 1 to A.
Please help.Thanks in advance
document.execCommand('insertUnorderedList',true,null);
let selectedStyle = event.target.parentNode.classList.value;
$('li').attr('id', function(i) {
return 'unorder'+(counter+1);
});
$('ol > li').css({'list-style-type':selectedStyle});
You can ignore the counter part its not required.I was trying to put id at every element and use jquery to update only that part.
let counter = 0;
document.querySelectorAll (".unorder-list-select .unorder-list-main .unorder-container ul").forEach((elem)=>{
elem.addEventListener("click",(event)=>{
counter++;
$('li').attr('id', function(i) {
return 'unorder'+(counter+1);
});
$('ul > li').css({'list-style-type':selectedStyle});
}
HTML
<div class="order-list-select" id="orderList" draggable="true">
<div>
<span class="jodit_popup_triangle"></span>
<div class="math-toolbar-top" id="orderlistHeader"></div>
<div class="order-list-main">
<div class="order-list-container"></div>
<div class="order-container">
<ol class="upper-roman">
<li><hr></li>
</ol>
<ol class="decimal">
<li><hr></li>
</ol>
</div>
</div>
</div>
</div>
Actual
1.text
2.text
Some text here
1.Txt
2.Txt
Expected
1.text
2.text
Some text here
a.Txt
b.Txt
I did it by Jquery don't know if its right approach but it has made me achieve my task.
Below is the code for my this.
document.querySelectorAll(".unorder-list-select .unorder-list-main .unorder-container ul").forEach((elem)=>{
elem.addEventListener("click",(event)=>{
counter++;
document.execCommand('insertUnorderedList',true,null);
let selectedStyle = event.target.parentNode.classList.value;
let select = window.getSelection().focusNode.parentElement.parentElement;
let ul_id = ""
$(select).attr('id', function(i) {
ul_id = "unorder"+(counter);
return ul_id;
});
if(selectedStyle == "right-arrow"){
$('#'+ul_id).css({'list-style-image':`url('icons/list-right-editor.svg')`});
}else if(selectedStyle == "open-square"){
$('#'+ul_id).css({'list-style-image':`url('icons/square-open-editor.svg')`});
}else{
$('ul').attr('type',function(){
return selectedStyle;
})
$('#'+ul_id).css({'list-style-type':selectedStyle});
}
document.querySelector(".unorder-list-select").remove();
})
});
Please change the classess accordingly to get the tag and click.

Adjust ng-repeat to match object structure

I have an object which looks like this:
$scope.hobbies = {
list: [
{
"PersonId": 23,
"PersonName": "John Smith",
"Hobbies": [
{
"HobbyTitle": "Paragliding",
"HobbyId": 23
},
{
"HobbyTitle": "Sharking",
"HobbyId": 99
}
]
}
]
};
I'm trying to develop a view which allows users to make a selection of each person's hobby.
I have a plunker here
My problem is that all selected hobbies are displayed under every person. This is because I'm just pushing all selected hobbies to a selectedHobbies Array.
$scope.addHobbyItem = function (item) {
var index = $scope.selectedHobbies.list.indexOf(item);
if (index === -1) {
$scope.selectedHobbies.list.push(item);
}
};
This of course doesn't work, as once a hobby is selected, it is shown under every person. How could I adjust the code to work with the way I'm ng-repeating over the selectedHobbies?
The HTML is below. I'm also using a directive to listen to click on the hobby container and trigger addHobbyItem()
<div data-ng-repeat="personHobby in hobbies.list">
<div>
<div style="border: 1px solid black; margin: 10px 0 10px">
<strong>{{ personHobby.PersonName }}</strong>
</div>
</div>
<div class="">
<div class="row">
<div class="col-xs-6">
<div data-ng-repeat="hobby in personHobby.Hobbies" data-ng-if="!hobby.selected">
<div data-hobby-item="" data-selected-list="false" data-ng-class="{ selected : hobby.selected }"></div>
</div>
</div>
<div class="col-xs-6">
<div data-ng-repeat="hobby in selectedHobbies.list">
<div data-hobby-item="" data-selected-list="true"></div>
</div>
</div>
</div>
</div>
</div>
your selectedHobbies should be a map in which the key is the person id and the value is a list of his selected hobbies. checkout this plunker
$scope.selectedHobbies = {
map: {}
};
// Add a hobby to our selected items
$scope.addHobbyItem = function(pid, item) {
if(!$scope.selectedHobbies.map[pid]) {
$scope.selectedHobbies.map[pid] = [];
}
var index = $scope.selectedHobbies.map[pid].indexOf(item);
if (index === -1) {
$scope.selectedHobbies.map[pid].push(item);
}
};
in the directive call addHobbyItem with the person id
scope.addHobbyItem(scope.personHobby.PersonId, scope.hobby);
and lastly in you html iterate on each person's selected hobbies
<div data-ng-repeat="hobby in selectedHobbies.map[personHobby.PersonId]">
<div data-hobby-item="" class="add-remove-container--offence" data-selected-list="true"></div>
</div>
something like this:
http://plnkr.co/edit/7BtzfCQNTCb9yYkv1uPN?p=preview
I used the $parent.$index to create an array of arrays containing hobbies for each person.
$scope.addHobbyItem = function (item, index) {
var ine = $scope.selectedHobbies[index].indexOf(item);
if (ine === -1) {
$scope.selectedHobbies[index].push(item);
}
};
function hobbyClickEvent() {
if (!$(element).hasClass('selected')) {
scope.addHobbyItem(scope.hobby, scope.$parent.$index);
} else {
scope.removeHobbyItem(scope.hobby);
}
}
and in the HTML:
<div data-ng-repeat="hobby in selectedHobbies[$index]">
<div data-hobby-item="" class="add-remove-container--offence" data-selected-list="true"></div>
</div>

Categories