Angular 1.6 - ng-repeat orderby and groupby items - javascript

So what I want to achieve is:
loop through array of messages (ng-repeat)
sort messages by date
then group current message with previous one, if author is matching (store in the same div element)
create new div if current messages's author is not matching previous one
continue loop
I'm stuck here:
<div class="message" ng-repeat="msg in chatDetails.msgList">
<p>{{ msg.text }}</p>
</div>
I need to keep exact order - simply, if previous element doesn't match with current - new box should be created.
Is that even possible in angular? If so, could you show me how, please?
Thank you!
edit
Heres sample result of chatDetails:
{
msgList: [
{ author: 0, text: 'hi', date: 1493050181799 },
{ author: 1, text: 'hola!', date: 1493050181801 },
{ author: 1, text: 'wilkomen', date: 1493050181802 },
{ author: 0, text: 'czesc', date: 1493050181803 }
{ author: 0, text: 'ciao', date: 1493050181804 }
{ author: 1, text: 'bonjour', date: 1493050181805 }
]
}
Somehow desired result:
<div class="message-list">
<div class="message-group" data-author="1">
<div class="message">
<p>hola</p>
</div>
<div class="message">
<p>ciao</p>
</div>
</div>
<div class="message-group" data-author="0">
<div class="message">
<p>hola</p>
</div>
</div>
<div class="message-group" data-author="1">
<div class="message">
<p>hola</p>
</div>
<div class="message">
<p>hola</p>
</div>
</div>
</div>

I believe your problem can be solved with "orderBy".
https://docs.angularjs.org/api/ng/filter/orderBy
It would look something like:
<div class="message" ng-repeat="msg in chatDetails.msgList | orderBy: 'date'">
<p>{{ msg.text }}</p>
</div>
If your msg objects have some date or index value, you can sort by that number.

Related

Vue.js: Textarea inside v-for loop is not working

v-model value inside v-for loop is not unique.
This is my template:
<template>
<div
id="FAQ"
v-for="(question, index) in questions.slice().reverse()"
:key="index"
class="m-2 col-7"
>
<div v-if="getloggedUser.role == 'tutor'">
<div id="divQuestion">
<p class="m-5">{{ question.text }}</p>
</div>
<div id="divAnswer" class="mt-2">
<p class="m-2">{{ question.answer }}</p>
</div>
</div>
<div v-else>
<div id="divQuestion">
<p class="m-5">{{ question.text }}</p>
</div>
<div id="divAnswer" class="mt-2">
<p class="m-2">{{ question.answer }}</p>
</div>
<div v-if="question.answer == null" id="divPsycAnswer">
<textarea cols="60" rows="3" style="border-radius:12px" v-model="answer.text"></textarea>
<button id="btnSend" #click="publishAnswer(question.id)">Responder</button>
</div>
</div>
</div>
</template>
This is the script (data):
data() {
return {
// arrayFAQ: [],
form: {
text: ""
},
answer: {
text: ''
},
questions: [],
message: "",
loading: false,
id: null
}
},
This way, everytime I write in one textarea, I write for all the others. I have tried adding [index] in the v-model but there's this error: "TypeError: Cannot use 'in' operator to search for '0' in "
Try this as your v-model
<textarea v-model="answers[`text${index}`]"></textarea>
The answer object could be an empty object.
It will give you text0, text1, text2, as answer's properties.
You can add anything instead of index, like your question's id for easy access and knowing which text is from which question.

How do I loop over a slice of an array using ngFor?

I am creating a FAQ page with accordion buttons, with groups of buttons under sub-headers. I designed it using an ngFor statement in the faq.html file.
<h1>Frequently Asked Questions</h1>
<div *ngFor="let item of data; let i = index;">
<button class="accordion" (click)="toggleAccordion($event, i)"> {{item.parentName}} </button>
<div class="panel" *ngFor="let child of item.childProperties" hide="!item.isActive">
<p> {{child.propertyName}} </p>
</div>
Here is a snippet of the faq.ts file.
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-faq',
templateUrl: './faq.html',
styleUrls: ['./faq.scss']
})
export class FaqComponent implements OnInit {
data: any = [
{
parentName: 'Question 1A',
childProperties: [{ propertyName: 'Answer 1A' }],
},
{
parentName: 'Question 2A',
childProperties: [{ propertyName: 'Answer 2A' }],
},
{
parentName: 'Question 3A',
childProperties: [{ propertyName: 'Answer 3A' }],
},
{
parentName: 'Question 1B',
childProperties: [{ propertyName: 'Answer 1B' }],
},
{
parentName: 'Question 2B',
childProperties: [{ propertyName: 'Answer 2B' }],
},
];
}
I want to put sub-headers over Section A (Questions 1A, 2A, and 3A), and Section B (Questions 1B and 2B). I think I can use slice in the ngFor statement in faq.html, but the code won't compile.
I tried this slice pipe:
<div *ngFor="let item of data | slice:0:2; let i = index;">
What should I change to get it to compile and break up the FAQ sections? Is the slice pipe the way to go?
Slicing Your Data
The problem here is that the slice pipe returns your data as type unknown. There are a couple of ways around this:
$any
<p *ngFor="let item of data | slice:2:4">
{{ $any(item).parentName }}
</p>
Bracket notation
<p *ngFor="let item of data | slice:2:4">
{{ item['parentName'] }}
</p>
A function
slicedData(data : any[]) : any[] {
return data.slice(2,4)
}
<p *ngFor="let item of slicedData(data)">
{{ item['parentName'] }}
</p>
You might want to properly type your data though, instead of using any. It is called Typescript for a reason after all.
Here are some examples in a Stackblitz.
I had to change the html to access the properties in different way and it got compiled:
<div *ngFor="let item of data; let i = index;">
<button class="accordion" (click)="toggleAccordion($event, i)"> {{item['parentName']}} </button>
<div class="panel" *ngFor="let child of item['childProperties'] | slice:0:2; let i = index;" hide="!item.isActive">
<p> {{child['propertyName']}} </p>
</div>
You can just add one *ngIf and check if i < 3:
<h1>Frequently Asked Questions</h1>
<div *ngFor="let item of data; let i = index;">
<ng-container *ngIf="i < 3">
<button class="accordion" (click)="toggleAccordion($event, i)"> {{item.parentName}} </button>
<div class="panel" *ngFor="let child of item.childProperties" hide="!item.isActive">
<p> {{child.propertyName}} </p>
</ng-container>
</div>
Thank you for your help, everyone. I changed faq.html to:
<h1>Frequently Asked Questions</h1>
<h2>General</h2>
<div *ngFor="let item of data; let i = index;">
<ng-container *ngIf="i < 3">
<button class="accordion" (click)="toggleAccordion($event, i)"> {{item.parentName}} </button>
<div class="panel" hide="!item.isActive">
<p> {{item.childName}} </p>
</div>
and it worked.

How to loop trough an array of images within an array in vue js

I would like go display the images inside an array named "image" within another array named product.
So basically if a product contain an array of 3 images i would like to display 3 images ,etc...
here's my code
<template>
<div class="details">
<div class="container">
<div class="row">
<div class="col-md-12" v-for="(product,index) in products" :key="index">
<div v-if="proId == product.productId">
<h1>{{product.productTitle}}</h1>
<h2>{{product.productId}}</h2>
<img :src="product.image[0]" class="img-fluid">
</div>
</div>
<div class="col-md-12" v-for="(product,index) in products" :key="index">
<div v-if="proId == product.productId">
<img :src="product.image[1]" class="img-fluid">
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "details",
data() {
return {
proId: this.$route.params.Pid,
title: "details",
products: [
{
productTitle: "ABCN",
image: [
require("../assets/images/autoportrait.jpg"),
require("../assets/images/bagel.jpg")
],
productId: 1
},
{
productTitle: "KARMA",
image: [require("../assets/images/bagel.jpg")],
productId: 2
},
{
productTitle: "Tino",
image: [require("../assets/images/bagel2.jpg")],
productId: 3
},
{
productTitle: "EFG",
image: [require("../assets/images/bagel3.jpg")],
productId: 4
}
]
};
}
};
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
I im able to display information within the first array example the product title, the product id, but the only way i found to display more images from the second array is to duplicate my code in the vue template and change the value of the index "product.image[0]", "product.image[1]".
There must be a better way to do this...
Thank a lot for the help
You can iterate over product images using v-for directive, just like you iterate over products:
<div class="col-md-12" v-for="(product, index) in products" :key="index">
<div v-if="proId == product.productId">
<h1>{{product.productTitle}}</h1>
<h2>{{product.productId}}</h2>
<div v-for="(image, imageIndex) in product.image">
<img :src="image" class="img-fluid" :key="imageIndex" />
</div>
</div>
</div>

how to filter post by title using Vue js 2.x directives instead of vue js 1.x

I am learning to use vue js with wprest api by following watch-learn tutorials on the same topic. the problem is that the vue js version used in the tutorial seems to be v 1.x and i started using vue js 2.x. I was able to figure out the initial stages on how to display all the post using vue js 2.x.. I have an input field using which I search for a specific title it should filter and show the post. The issue is unable to workout exactly how it needs to be computed using vuejs 2.x.. I have included a codepen link containing the json data as well as my working code.
the following is the input field to be used to filter the posts by title
<div class="row">
<h4>Filter by name:</h4>
<input type="text" name="" v-model="nameFilter">
</div>
<div class="row">
<div class="col-md-4" v-for="post in posts">
<div class="card post">
<img class="card-img-top" v-bind:src="post.fi_300x180" >
<div class="card-body">
<h2 class="card-text">{{ post.title.rendered }}</h2>
<small class="tags" v-for="category in post.cats">{{ category.name }}</small>
</div>
</div> <!-- .post -->
</div> <!-- .col-md-4 -->
</div> <!-- .row -->
https://codepen.io/dhivyasolomon/pen/LdZKJY
I would appreciate any help in figuring out the next step. thanks.
You don't need directives, achieve this using the power of Computed properties
So you will have to itarate over the computed property which filter the posts by the input value and return a new array of posts.
Little example:
new Vue({
el: '#example',
computed: {
filteredPosts () {
return this.posts.filter(p => p.title.toLowerCase().includes(this.filterText.toLowerCase()))
}
},
data () {
return {
filterText: '',
posts: [
{
title: 'My first post title',
body: 'foo'
},
{
title: 'Another post title',
body: 'foo'
},
{
title: 'This will work fine',
body: 'foo'
},
{
title: 'Omg it\'s working!',
body: 'foo'
}
]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.15/vue.js"></script>
<div id="example">
<input type="text" v-model="filterText" />
<ul>
<li v-for="post in filteredPosts">{{ post.title }}</li>
</ul>
</div>

Angular ng-click function populates table?

So I have these boxes that show minimal info for an array created using an ng-repeat. There could be 1 and 10 items returned with objects and their properties These boxes are clickable and when clicked, should populate the table. Problem is, I keep getting a reference error and for the likes of me, cannot see why. The function is defined and should call and fill the table based on the Id of the box to fill the table with more info. The array object is called "swipe".
My html for the the boxes:
<div class="swipeBoxes" ng-repeat="swipe in swipes">
<a href="" ng-click="editSwipe(swipe.id)" >
<div class="swipeBox col-md-12">
<div class="col-md-12 claimObject">
<span class="claimLine" style="width: 3px; position: absolute; top: 5px; left: 5px;">{{ $index + 1 }}.</span>
<span class="claimedLeft">swipe date:</span>
<span class="claimedRight">{{ swipe.date | date: 'MM/dd/yyyy'}}</span>
</div>
<div class="col-md-12 claimObject">
<span class="claimedLeft">provider:</span
><span class="claimedRight">{{ swipe.merchant }}</span>
</div>
<div class="col-md-12 claimObject">
<span class="claimedLeft">amount:</span>
<span class="claimedRight">{{ swipe.amount | currency }}</span>
</div>
</div>
</a>
</div>
My target table that should be filled with data once one of the objects are selected from above:
<div class="swipeDetails col-md-12">
<div class="swipeFlexHead col-md-12">
<p>Swipe Details</p>
</div>
<div class="swipeFlexHead2 col-md-12">
<p>{{ swipe.merchant }}</p>
</div>
<div class="col-md-12">
<div class="swipeFlexElement col-md-4">
<p>Swipe Date</p>
<p>{{ swipe.date | date: 'MM/dd/yyyy' }}</p>
</div>
<div class="swipeFlexElement col-md-4">
<p>Status</p>
<p>{{ swipe.status }}</p>
</div>
<div class="swipeFlexElement col-md-4">
<p>Card Used</p>
<p>{{ swipe.cardHolder }} {{ swipe.cardUsed }}</p>
</div>
</div>
<div class="col-md-12">
<div class="swipeFlexElement col-md-4">
<p>Swipe Amount</p>
<p>{{ swipe.amount | currency }}</p>
</div>
<div class="swipeFlexElement col-md-4">
<p>Amount Requiring Documentation</p>
<p>{{ swipe.reqAmount | currency }}</p>
</div>
<div class="swipeFlexElement col-md-4">
<p>Documentation Due Date</p>
<p>{{ swipe.dueDate | date: 'MM/dd/yyyy' }}</p>
</div>
</div>
</div>
And finally, my controller for this that is pulling data and has the 'editSwipe' function:
app.controller('swipeController', ['$scope', 'swipesService', '$location', 'Upload', '$timeout', '$filter', 'utilsService',
function ($scope, swipesService, $location, Upload, $timeout, $filter, utilsService) {
$scope.swipes = [];
$scope.swipeFormSubmitted = false;
swipesService.getSwipes().then(function (results) {
$scope.swipes = results.data;
});
$scope.swipe = {
id: '',
descr: '',
merchant: '',
date: '',
amount: '',
reqAmount: '',
status: '',
cardUsed: '',
cardHolder: '',
dueDate: ''
}
$scope.editSwipe = function(id) {
$scope.swipeInfo = angular.copy(swipe);
};
}]);
In your editSwipe function you are not doing anything with id. With angular.copy(swipe) you are using swipe which is not defined. I guess you want to copy the swipe you've clicked, then you need to do
ng-click="editSwipe(swipe)"
$scope.editSwipe = function(swipe) {
$scope.swipe = angular.copy(swipe); // I don't see where you use swipeInfo?
};
BTW: is there any need to deep copy the swipe? Can't you just pass the reference $scope.swipe = swipe;

Categories