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!
Related
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()"
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])
}
Here's a simplified version of my code :
<template>
/* ----------------------------------------------------------
* Displays a list of templates, #click, select the template
/* ----------------------------------------------------------
<ul>
<li
v-for="form in forms.forms"
#click="selectTemplate(form)"
:key="form.id"
:class="{selected: templateSelected == form}">
<h4>{{ form.name }}</h4>
<p>{{ form.description }}</p>
</li>
</ul>
/* --------------------------------------------------------
* Displays the "Editable fields" of the selected template
/* --------------------------------------------------------
<div class="form-group" v-for="(editableField, index) in editableFields" :key="editableField.id">
<input
type="text"
class="appfield appfield-block data-to-document"
:id="'item_'+index"
:name="editableField.tag"
v-model="editableField.value">
</div>
</template>
<script>
export default {
data: function () {
return {
editableFields: [],
}
},
methods: {
selectTemplate: function (form) {
/* ------------------
* My problem is here
*/ ------------------
for (let i = 0; i < form.editable_fields.length; i++) {
this.editableFields.push(form.editable_fields[i]);
}
}
}
}
</script>
Basically I want to update the array EditableFields each time the user clicks on a template. My problem is that Vuejs does not update the display because the detection is not triggered. I've read the documentation here which advise to either $set the array or use Array instance methods only such as splice and push.
The code above (with push) works but the array is never emptied and therefore, "editable fields" keep pilling up, which is not a behavior I desire.
In order to empty the array before filling it again with fresh data, I tried several things with no luck :
this.editableFields.splice(0, this.editableFields.length);
for (let i = 0; i < form.editable_fields.length; i++) {
this.editableFields.push(form.editable_fields[i]);
}
==> Does not update the display
for (let i = 0; i < form.editable_fields.length; i++) {
this.$set(this.editableFields, i, form.editable_fields[i]);
}
==> Does not update the display
this.editableFields = form.editable_fields;
==> Does not update the display
Something I haven't tried yet is setting a whole new array with the fresh data but I can't understand how I can put that in place since I want the user to be able to click (and change the template selection) more than once.
I banged my head on that problem for a few hours now, I'd appreciate any help.
Thank you in advance :) !
I've got no problem using splice + push. The reactivity should be triggered normally as described in the link you provided.
See my code sample:
new Vue({
el: '#app',
data: function() {
return {
forms: {
forms: [{
id: 'form1',
editable_fields: [{
id: 'form1_field1',
value: 'form1_field1_value'
},
{
id: 'form1_field2',
value: 'form1_field2_value'
}
]
},
{
id: 'form2',
editable_fields: [{
id: 'form2_field1',
value: 'form2_field1_value'
},
{
id: 'form2_field2',
value: 'form2_field2_value'
}
]
}
]
},
editableFields: []
}
},
methods: {
selectTemplate(form) {
this.editableFields.splice(0, this.editableFields.length);
for (let i = 0; i < form.editable_fields.length; i++) {
this.editableFields.push(form.editable_fields[i]);
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<ul>
<li v-for="form in forms.forms"
#click="selectTemplate(form)"
:key="form.id">
<h4>{{ form.id }}</h4>
</li>
</ul>
<div class="form-group"
v-for="(editableField, index) in editableFields"
:key="editableField.id">
{{ editableField.id }}:
<input type="text" v-model="editableField.value">
</div>
</div>
Problem solved... Another remote part of the code was in fact, causing the problem.
For future reference, this solution is the correct one :
this.editableFields.splice(0, this.editableFields.length);
for (let i = 0; i < form.editable_fields.length; i++) {
this.editableFields.push(form.editable_fields[i]);
}
Using only Array instance methods is the way to go with Vuejs.
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>
So what's the problem. I want to exclude item from WinJS List View with specific parameter (ID - passed from JSON). How to do that?
Things i've tried:
a) Before pushing data to someView.itemDataSource process it with this function (It work's, but looks dirty).
fldView.itemDataSource = this._processItemData(Data.items.dataSource);
....
_processItemData: function (data) {
for (var i = data.list.length; i >= 1; i--) {
if (data.list._groupedItems[i]) {
if (data.list._groupedItems[i].groupKey == 'Folders')
continue;
else {
if (data.list._groupedItems[i].data.folderID) {
data.list.splice(i - 1, 1);
}
}
}
}
return data;
}
b) The traditional way with two conditional templates (Doesn't work):
fldView.itemTemplate = this.getItemTemplate;
....
getItemTemplate: function(promise){
return promise.then(function(item){
var
itemTemplate = null,
parent = document.createElement("div");
if(item.data.folderID){
itemTemplate = document.querySelector('.hideItemTemplate')
}else{
itemTemplate = document.querySelector('.itemTemplate')
}
//console.log(item.data.folderID);
itemTemplate.winControl.render(item.data, parent);
return parent;
})
}
2 HTML templates
<div class="itemTemplate" data-win-control="WinJS.Binding.Template">
<div class="item">
<img class="item-image" src="#" data-win-bind="src: backgroundImage; alt: title" />
<div class="item-overlay">
<h4 class="item-title" data-win-bind="textContent: title" style="margin-left: 0px;"></h4>
<h6 class="item-subtitle win-type-ellipsis" data-win-bind="textContent: subtitle" style="margin-left: 0px; margin-right: 4.67px;"></h6>
</div>
</div>
</div>
<div class="hideItemTemplate" data-win-control="WinJS.Binding.Template">
<div class="display-none"></div>
</div>
and CSS display: none
.hideItemTemplate, .display-none{
display:none;
}
Thank's in advance!
Suggest to filter the item either before building the WinJS.Binding.List using array.filter or do a filter projection on the list after it is built. if grouping is required, grouping can be done on the filtered list.
var list; // assuming this is all data items
var filteredList = list.createFiltered(function filter(item)
{
if (item.FolderID)
return false;
else
return true;
});
var groups = filteredList.createGrouped(...);