Group same strings in array and count them - javascript

This is an Angular project and I have data that comes from the server. I am creating three lists. I have three types of sentiment and each sentiment has a description
for (const sentiment of this.dataSource.allFavourabilities) {
if (sentiment.sentiment === 'Positive') {
this.positiveList.push(sentiment);
}
if (sentiment.sentiment === 'Negative') {
this.negativeList.push(sentiment);
}
if (sentiment.sentiment === 'Neutral') {
this.neutralList.push(sentiment);
}
}
This is how it looks like when I console.log that.
coverageId:137
description:"Positive"
id:119
sentiment:"Positive"
In HTML I am listing this in three divs and the only constant is sentiment which is displayed as an icon, the description can be different. But client mainly types positive, negative or neutral so I get an ugly list with same data. I want to filter that so it is a matching string in the description and I have positive in the description like 50 times, in my HTML it should say
Preferred outcome:
positive(50)
negative(2)
neutral
and not
positive
positive
positive
positive etc.
negative
negative
neutral
This is my HTML:
<div class="form-item favourability-wrapper flex">
<div class="column" *ngIf="positiveList">
<div class="sentiment-label">Positive</div>
<ng-container *ngFor="let a of positiveList">
<div class="sentiment sentiment-positive ">
<i class="fas fa-smile"></i>
<div class="description">{{ a.description }}</div>
</div>
</ng-container>
</div>
<div class="column" *ngIf="negativeList">
<div class="sentiment-label">Negative</div>
<ng-container *ngFor="let b of negativeList">
<div class="sentiment sentiment-neutral">
<i class="fas fa-meh"></i>
<div class="description">{{ b.description }}</div>
</div>
</ng-container>
</div>
<div class="column" *ngIf="neutralList">
<div class="sentiment-label">Neutral</div>
<ng-container *ngFor="let c of neutralList">
<div class="sentiment sentiment-negative">
<i class="fas fa-frown"></i>
<div class="description">{{ c.description }}</div>
</div>
</ng-container>
</div>
</div>

Change this to a html element that shows the length
<ng-container *ngFor="let a of positiveList">
<div class="sentiment sentiment-positive ">
<i class="fas fa-smile"></i>
<div class="description">{{ a.description }}</div>
</div>
</ng-container>
to
<div *ngIf="positiveList">
{{ '(' + (positiveList?.length || '0')+')' }}
</div>

You can try this:
<div class="form-item favourability-wrapper flex">
<div class="column" *ngIf="positiveList">
<div class="sentiment-label">Positive</div>
<div class="sentiment sentiment-positive ">
<i class="fas fa-smile"></i>
<div class="description">({{ positiveList.length > 0 ? positiveList.length : '0' }})</div>
</div>
</div>
<div class="column" *ngIf="neutralList">
<div class="sentiment-label">Neutral</div>
<div class="sentiment sentiment-neutral">
<i class="fas fa-meh"></i>
<div class="description">({{ neutralList.length > 0 ? neutralList.length : '0' }})</div>
</div>
</div>
<div class="column" *ngIf="negativeList">
<div class="sentiment-label">Negative</div>
<div class="sentiment sentiment-negative">
<i class="fas fa-frown"></i>
<div class="description">({{ negativeList.length > 0 ? negativeList.length : '0' }})</div>
</div>
</div>
</div>

<div class="form-item favourability-wrapper flex">
<div class="column" *ngIf="positiveList">
<div class="sentiment-label">Positive{{ positiveList.length ? '(' + positiveList.length + ')' : '' }}</div>
</div>
<div class="column" *ngIf="negativeList">
<div class="sentiment-label">Negative{{ negativeList.length ? '(' + negativeList.length + ')' : '' }}</div>
</div>
<div class="column" *ngIf="neutralList">
<div class="sentiment-label">Neutral{{ neutralList.length ? '(' + neutralList.length + ')' : '' }}</div>
</div>
</div>
maybe this works for you, you can use Array.length to get the count.

Since you're asking for help, let me help you improve your code :
this.feelings = [
{ name: 'Positive', sentiments : [] },
{ name: 'Negative', sentiments : [] },
{ name: 'Neutral', sentiments : [] },
];
for (const sentiment of this.dataSource.allFavourabilities) {
if (sentiment.sentiment === 'Positive') {
this.feelings[0].sentiments.push(sentiment);
}
if (sentiment.sentiment === 'Negative') {
this.feelings[1].sentiments.push(sentiment);
}
if (sentiment.sentiment === 'Neutral') {
this.feelings[2].sentiments.push(sentiment);
}
}
<!-- I'll let you append the classes to your convenience -->
<div *ngFor="let feeling of feelings">
{{ feeling.name }} : {{ feeling.sentiments.length }}
</div>
See how the HTML got reduced ? We can even reduce the typescript further with this
this.feelings: { name: string, sentiments : any[] }[] = this.dataSource.allFavourabilities
.reduce((p, n) => {
if (!p.find(feeling => feeling.name === n.sentiment)) {
p.push({ name: n.sentiment, sentiments : []});
}
p.find(feeling => feeling.name === n.sentiment).sentiments.push(n);
return p;
}, []);

Related

Filtering popup menu

In general, I need to make a pop-up menu with a choice of a category, everything works for me, but there is one point that needs to be improved.
When the category has already been selected, it is necessary that in the blog-filter class, All should be changed to the selected category, I tried many options, but all the non-working ones turned out to be.
html
<div class="blog-filter">
<div class="blog-filter_item active js-filter-blog-list" data-filter="all">All</div>
</div>
<div class="blog-filter-container">
<div class="container">
<h1 class="blog-filter-title">Choose Category</h1>
<div class="item-wrapper">
<div class="blog-filter_item active" data-filter="all">All</div>
#foreach($categories as $category)
<div class="blog-filter_item" data-filter=".category_{{$category->id}}">{{ $category->title }} ({{ $category->articles_count }})</div>
#endforeach
</div>
</div>
</div>
#foreach ($articles as $article)
<div class="blog-list category_{{ $article->blog_category_id }}">
<div class="article article--left" >
<h2 class="article_title">{{ $article->title }}</h2>
</div>
<div class="article article--right">
<h2 class="article_title">{{ $article->title }}</h2>
</div>
</div>
#endforeach
js
$('.js-filter-blog-list').on('click', event => {
const modalFilter = document.querySelector('.blog-filter-container');
$(modalFilter).toggleClass('open');
});
document.querySelectorAll('.blog-filter_item').forEach(el => {
el.addEventListener('click', () => {
document
.querySelector('.blog-filter_item.active')
.classList.remove('active');
el.classList.add('active');
var dataFilter = $(el).attr('data-filter');
if (dataFilter == 'all') {
$('.blog-list').show();
}
else {
$('.blog-list').hide();
$(dataFilter).show();
}
});
});
Solution:
#foreach($categories as $category)
<div class="blog-filter_item" data-filter=".category_{{$category->id}}" value="{{ $category->title }} ({{ $category->articles_count }})>{{ $category->title }} ({{ $category->articles_count }})</div>
#endforeach
var dataFilter = $(el).attr('data-filter');
var value = $(el).attr('value');
if (dataFilter == 'all') {
$('.blog-list').show();
$('.js-filter-blog-list').text('All');
}
else {
$('.blog-list').hide();
$(dataFilter).show();
$('.js-filter-blog-list').text(value);
}

How to create new bootstrap row per two items on Angular?

I'd like to add row on each 2 elements of ngFor loop.But I couldnt handle it.
I have studentNames array like below
studentNames=[
{
name:"Jonas",
age:22,
number:"1234"
},
{
name:"Mathilda",
age:25,
number:"5432"
},
{
name:"Jacob",
age:20,
number:"4321"
},
{
name:"Ivan",
age:23,
number:"6984"
},
{
name:"Kate",
age:21,
number:"3432"
},
{
name:"James",
age:20,
number:"4312"
},
{
name:"Sean",
age:23,
number:"4321"
},
{
name:"Julia",
age:23,
number:"4321"
},
]
Here what I tried
<ng-container *ngFor="let item of studentNames;let i=index">
<div class="row" *ngIf="i%2==0">
<div class="col md-12">
{{item.name}}
</div>
</div>
</ng-container>
This only skipped when index is not even.
Here how I want to see them below(order doesnt matter only should be 2 by 2 per row).
Stackblitz example: https://stackblitz.com/edit/bootstrap-wpfukz?file=app%2Fapp.component.html
This is a bit more "hacky" approach but it's HTML-only:
<ng-container *ngFor="let item of studentNames;let i=index">
<div *ngIf="i%2==0">
<div class="row">
<div class="col md-12">
{{studentNames[i].name}}
</div>
<div class="col md-12">
{{studentNames[i + 1].name}}
</div>
</div>
</div>
</ng-container>
You can try like below. Use *ngFor exported value index and use half length of the array to continue the loop
<ng-container *ngFor="let item of studentNames; let j = index;">
<ng-container *ngIf="j < (studentNames.length / 2)">
<div class="row">
<div class="col md-6">
{{item.name}}
</div>
<div class="col md-6">
{{ studentNames[ j + (studentNames.length / 2) ].name }}
</div>
</div>
</ng-container>
</ng-container>
Working stackblitz
Note : Array length should be an even number ;)

ERROR Error: Cannot find control with name: '[object Object]

I'm working with Reactive Forms and I'm trying to pass my form down to child components, but I'm running into the error above. Initially at the top level of the form I was using a FormArray to hold my form and that was working fine before I tried passing it down to the child components. Thanks to this post I now know that the top level of a form should be a FormGroup and the FormArray should be a child of the FormGroup.
So now I am nesting my FormArray inside of a FormGroup and I'm getting the error above. I'm not sure what I'm doing wrong? Below in the relevant code.
// Parent component.ts
ngOnInit() {
if (!!this.rows) {
this.tableForm = new FormArray([]);
this.rows.forEach((row) => {
this.rowGroup = new FormGroup({})
this.columns.forEach(column => {
this.rowGroup.addControl(column.key, new FormControl(row[column.key]));
});
this.tableForm.push(this.rowGroup);
})
this.tableGroup = new FormGroup({
rows: new FormControl(this.tableForm)
})
}
}
// Parent HTML
<section
*ngIf="!!modal"
class="modal__mask">
<section
class="modal__container"
#carousel
[ngStyle]="{'left': start + 'px'}"
(window:resize)="onResize($event)"
[formGroup]="tableGroup">
<div
*ngFor='let row of selectedRows; let i = index'
class="modal modal__large"
[formArrayName]="rows">
<div
[formGroupName]="i"
[ngClass]="{'opacity': modalPage !== i}">
<div class="modal__header modal__header--large">
<h6>Edit Employee Details</h6>
</div>
<div class="u-flex u-wrap">
<div
class="u-flex modal__body"
style="width: 50%"
*ngFor="let column of columns">
<div
*ngIf="column.label"
class="input__wrapper"
[ngClass]="{'input__wrapper--inline': layout === 'horizontal'}">
<z-input
*ngIf="column.label"
class="u-maxX"
[group]="tableGroup"
[config]="column">
</z-input>
<!-- <div>
<label
class="input__label">
<p class="t-bold t-data">{{column.label}}</p>
</label>
<div class="z-input__default">
<input
class="input u-maxX"
[formControlName]="column.key"
[value]="row[column.key]">
</div>
</div> -->
</div>
</div>
<section class="modal__footer u-fillRemaining">
<div class="u-flex">
<button
class="button button--medium"
(click)="nextSelectedRow()">
<div class="button__content">
<i
class="icon icon--medium"
*ngIf="!!icon">
{{icon}}
</i>
<span>Skip</span>
</div>
</button>
</div>
<div class="u-flex">
<button
class="button button--low"
(click)="reset(row, i)">
<div class="button__content">
<i
class="icon icon--medium"
*ngIf="!!icon">
{{icon}}
</i>
<span>Reset</span>
</div>
</button>
<button
class="button button--low"
(click)="saveChanges(row, i)">
<div class="button__content">
<i
class="icon icon--medium"
*ngIf="!!icon">
{{icon}}
</i>
<span>Save Changes</span>
</div>
</button>
</div>
</section>
</div>
</div>
</div>
// Child component.ts
#Input() config;
#Input() group: FormGroup;
#Input() view: string;
#Input() layout: string;
// Child HTML
<div
class="input__wrapper"
[ngClass]="{'input__wrapper--inline': layout === 'horizontal'}"
[formGroup]="group"
[ngSwitch]="config.type">
<label
class="input__label"
*ngIf="!!config.label">
<p class="t-bold t-data">{{config.label}}</p>
</label>
<z-select
*ngSwitchCase="'select'"
[config]="config"
[group]="group"
[view]="view"
gridColumn="1 / 5">
</z-select>
<div class="z-input__default">
<input
*ngSwitchDefault
class="input u-maxX"
[formControlName]="config.key"
[attr.type]="config.type"
[attr.placeholder]="config.placeholder">
</div>

How to read data from a v-for (vue.js)?

Given the next v-for:
<div class="container-fluid" id="networdapp" style="display:none;">
<div class="row" >
<div v-for="result in results" class="col-sm-6" >
<div class="card m-3 h-240 bg-light" >
<div class="card-header text-center" > {{ result.title }} </div>
<div class="card-body" style="height:200px" >
<p class="card-text" v-html="result.prevDesc"></p>
</div>
<div class="card-footer bg-transparent border-info">
<a href="/details" class="btn btn-info" #click="getData(result)" >Details</a>
</div>
</div>
</div>
</div>
</div>
And the next Vue.js script:
<script type="text/javascript">
const vm = new Vue({
el: '#networdapp',
data: {
results:[]
},
methods: {
getData: function(result){
window.alert($(this).parents("#networdapp").find(".card-header.text-center").outerHTML);
window.alert(document.getElementsByClassName("card-header").outerHTML);
window.alert(result.outerHTML);
}
},
mounted() {
axios.get('/getJson')
.then(response => {
this.results = response.data;
})
.catch( e => {
console.log(e);
});
}
});
</script>
I want to get data from a specific iteration,let's say if I click the "Details" button of the 3rd div from the v-for I want to get the {{result.title }} data from the 3rd for.Is it possible?I've been reading the Vue.js documentation but I didn't find anything about reading the data from DOM.If it is not possible,than how can I do that without Vue.js?Is there any other option?
The main goal is to get this data and to put it into a js object passing it to another webpage.
you have to pass index key and use is to get from results's position.
change the for loop div into
<div v-for="(result,i) in results" :key="i" class="col-sm-6" >
also chnange the methods parameter
<a href="/details" class="btn btn-info" #click="getData(i)" >Details</a>
and the method will get the index key and here i have used console to see the result.title that you have wanted. you can use it any how you want.
getData: function(key){
console.log(this.results[key].title)
}
so
Given the next v-for:
<div class="container-fluid" id="networdapp" style="display:none;">
<div class="row" >
<div v-for="(result,i) in results" :key="i" class="col-sm-6" >
<div class="card m-3 h-240 bg-light" >
<div class="card-header text-center" > {{ result.title }} </div>
<div class="card-body" style="height:200px" >
<p class="card-text" v-html="result.prevDesc"></p>
</div>
<div class="card-footer bg-transparent border-info">
<a href="/details" class="btn btn-info" #click="getData(i)" >Details</a>
</div>
</div>
</div>
</div>
And the next Vue.js script:
<script type="text/javascript">
const vm = new Vue({
el: '#networdapp',
data: {
results:[]
},
methods: {
getData: function(key){
console.log(this.results[key].title)
}
},
mounted() {
axios.get('/getJson')
.then(response => {
this.results = response.data;
})
.catch( e => {
console.log(e);
});
}
});
To get the data you want to access in the results array, you can use an index in your v-for loop
v-for="(result, index) in results"
you can check the docs here https://v2.vuejs.org/v2/guide/list.html
I also strongly recommend you to add a key attribute after the v-for to help vue.js
keep track of each result, see https://v2.vuejs.org/v2/guide/list.html#key

jQuery function in an AngularJS way

I'm moving some code from jQuery to AngularJS, but I am still having trouble understanding its logic, so switching to AngularJS caused confusion. I have the following example which I did in AngularJS:
<div class="main" ng-repeat="data in mainData">
<div class="row forecast_daily">
<div class="col-md-4 col-md-offset-2">
{{ data.date.weekday }}
{{ data.date.day }}
{{ data.edit }}
</div>
<div class="col-md-3 range-index">
<div class="range">
<span class="min">{{ data.low }}</span>
<span class="max">{{ data.high }}</span>
</div>
</div>
</div>
</div>
But I have this jQuery function which I'm having trouble doing in an AngularJS way.
$('.range-index').each(function(){
var min = parseInt($(this).find('.min').html()),
max = parseInt($(this).find('.max').html());
var l = max - (max-min);
var w = (max-min)*20;
$(this).find('.range').css({left:l+"%",width: w});
});
It looks like ng-style can work well here:
<div class="col-md-3 range-index">
<div class="range"
ng-style="{left: data.low + '%', width: (data.high - data.low) * 20 + 'px'}">
<span class="min">{{ data.low }}</span>
<span class="max">{{ data.high }}</span>
</div>
</div>
Or better. You can create a helper function in the controller:
$scope.getStyle = function(data) {
return {
left: data.low + '%',
width: (data.high - data.low) * 20 + 'px'
};
};
And use it like this:
<div class="col-md-3 range-index">
<div class="range" ng-style="getStyle(data)">
<span class="min">{{ data.low }}</span>
<span class="max">{{ data.high }}</span>
</div>
</div>

Categories

Resources