create element (commenting box) in *ngFor loop - javascript

i've built a website i.e. a blog which runs at the moment locally with the full MEAN (MongoDB, Node, Angular and Express) stack. I've already implemented the backend, but have some problems for the frontend. Most of it works. I load the content dynamically i.e. every time the user reached the bottom of the page, i load the next three blog posts. I've also added some voting (up and down). I don't know if i did it right, but it works. I have an array which contains all blog post. Every time the user reached the bottom, i add them via .push to my array. This array is shown via *ngFor, so it contains all current blog post and "future posts". The question arise how i add the voting in an efficient way? I've added an array, every time a user clicks "vote up" i check if the post id is already in that array, if not i add it and increment the vote by one and store the value also in the database by a request. If the user regrets his decision he can anytime press the same button to bring the initial state back, because now the post id is in that array and therefore a click on the same button is not a vote up anymore rather we decrease the vote and remove the post id from the array (and store the new value in the database). The user can now again vote up, because the post id is no more in that array, etc.
Is there any better approach to implement the voting? I think if the user loads 1000 blog articles and votes many articles and afterwards regrets one, then the loading time can eventually very high (finding a value in a big array, etc.).
The point why i've written here is the following. The main problem is that i want to add a commenting box to each blog post. But how do i do that? Should i create each time the browser loads 3 article 3 commenting boxes? "This" is not really a problem, but what happened in the same scenario as already mentioned? What happened if the user reads 20 articles one after the other. Then 20 commenting boxes are created and are binded to some variables, etc. It cant be efficient in that way. I thought i could create one commenting box and every time the user clicks on an anchor tag (named "Reply") the commenting box will be opened. But there exists only one box. It will be shown in the appropriate place. But i don't know how to implement this with angular. :(
I'd appreciate any hint or help. :)
UPDATE 26.11.2017: i share some content from my project, so can probably better understand what i mean.
This is my blog.component.ts
import { Component, OnInit } from '#angular/core';
import { CarouselConfig } from 'ngx-bootstrap/carousel';
import { BlogPost } from '../shared/blogPost';
import { BlogPostFactory } from '../shared/blogPost-factory';
import { DataService } from '../data.service';
#Component({
selector: 'ca-blog',
templateUrl: './blog.component.html',
styleUrls: ['./blog.component.css'],
providers: [{provide: CarouselConfig, useValue: {interval: 1500, noPause: true}}]
})
export class BlogComponent implements OnInit {
private blogPosts: BlogPost[] = [];
private heartsUp: string[] = [];
private likesUp: string[] = [];
constructor(private dataService: DataService) {
window.onscroll = () => {
let windowHeight = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight;
let body = document.body;
let html = document.documentElement;
let docHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
let windowBottom = windowHeight + window.pageYOffset;
if(windowBottom >= docHeight) {
let urlSearchParams = { last: this.blogPosts.length };
this.dataService.getBlogPosts(urlSearchParams).subscribe( result => {
for(let i=0; i<result.length; i++){
this.blogPosts.push(BlogPostFactory.fromObject(result[i]));
}
});
}
};
}
ngOnInit() {
this.dataService.getBlogPosts().subscribe( result => {
for(let i=0; i<result.length; i++){
this.blogPosts.push(BlogPostFactory.fromObject(result[i]));
}
});
}
incrementHearts(index) : void {
let idIndex : number = this.heartsUp.indexOf(this.blogPosts[index].id);
if(idIndex != -1){
this.blogPosts[index].hearts--;
this.heartsUp.splice(idIndex, 1);
} else {
this.blogPosts[index].hearts++;
this.heartsUp.push(this.blogPosts[index].id);
}
this.dataService.editBlogPost(this.blogPosts[index].id, { hearts: this.blogPosts[index].hearts }).subscribe().unsubscribe();
}
incrementLikes(index) : void {
let idIndex : number = this.likesUp.indexOf(this.blogPosts[index].id);
if(idIndex != -1){
this.blogPosts[index].likes--;
this.likesUp.splice(idIndex, 1);
} else {
this.blogPosts[index].likes++;
this.likesUp.push(this.blogPosts[index].id);
}
this.dataService.editBlogPost(this.blogPosts[index].id, { likes: this.blogPosts[index].likes }).subscribe().unsubscribe();
}
}
and blog.component.html is given by
<div class="container">
<div *ngFor="let blogPost of blogPosts; let i=index">
<div *ngIf="i !== 0">
<hr class="my-5">
</div>
<div class="row">
<div class="col">
<article>
<section>
<header style="" class="mb-4">
<h1 style="display:inline" class="m-0 p-0">{{ blogPost.title }}</h1><small style="opacity:0.5" class="d-block d-sm-inline ml-sm-3">zuletzt bearbeitet am {{ blogPost.lastEdited | date }} von <strong>{{ blogPost.author.username }}</strong></small>
<p class="mt-1 mt-sm-auto"><i class="fa fa-tags mr-2"></i><a *ngFor="let hashtag of blogPost.hashtags" href="#" class="badge badge-secondary mr-2">#{{ hashtag }}</a></p>
</header>
<div class="m-0 p-0" [innerHTML]="blogPost.body">
</div>
<div>
<small class="heartUp"><a (click)="incrementHearts(i)" [ngStyle]="{'color':heartsUp.includes(blogPost.id) ? '#E63946' : 'rgba(0,0,0,0.5)'}"><i class="fa fa-heart mr-1"></i>{{ blogPost.hearts }}</a></small>
<small class="likeUp"><a (click)="incrementLikes(i)" [ngStyle]="{'color':likesUp.includes(blogPost.id) ? '#3b5998' : 'rgba(0,0,0,0.5)'}"><i class="fa fa-thumbs-up mr-1"></i>{{ blogPost.likes }}</a></small>
<small class="reply"><a><i class="fa fa-mail-reply mr-1"></i>Reply</a></small>
</div>
</section>
</article>
</div>
</div>
</div>
</div>
I hope this visualize my problem better.

for your first question it depends how you think about it, you want to disable the vote up? if yes then you need to treat it on the front end side, if not, you can the user let press the vote up and then in the server check for the user id and send a error message back ("you already voted"), for your second question i don't know very well what you mean with there is just 1 box? you mean you have 1 box component? if that makes you confusion, just think on the component as a class that you can instantiate in objects, same for components you can create a lot of since very one is a diferent instance.

Related

How can I pass variable into function when the script text on prams

function domHTML(script, contact = {}, user = {}) {
console.log(contact.first_name);
console.log(user.name);
let dom = `
<div class="main py-3 w-100">
<div class="container">
<div class="content">
${script}
</div>
</div>
</div>`;
console.log(dom);
//return dom;}
let script = `Hey ${contact.first_name}, ${contact.first_name} this is ${user.name} giving you a quick call from the benefits office here in ${contact.state}. I’ll have you have the phone here sec but they have me giving you a quick call because of a form you filled out requesting more information about the life insurance programs through the state of ${contact.state}. They have me, the field underwriter, calling to verify the information, I have DOB as ${contact.birthday} is that correct? Perfect, so ${contact.first_name} like I said I’m just the field underwriter they have calling to let you know your request has been processed.
We’re doing everything virtually now due to covid. Takes about 10-15 min to get you the info. Grab a pen and paper for notes and will knock this out so you don’t get any more calls.`
I want to do every variable will be replaced with function arguments
You can turn your script into a function that accepts arguments and pass it to domHTML, that way script function can get the variables of contact and user passed to it.
function domHTML(fn ,contact = {}, user = {}) {
let theScript = fn(contact, user)
let dom = `
<div class="main py-3 w-100">
<div class="container">
<div class="content">
${theScript}
</div>
</div>
</div>`;
return dom;
}
function script(contact, user){
return `Hey ${contact.first_name}, ${contact.first_name} this is ${user.name} giving you a quick call from the benefits office here in ${contact.state}.
I’ll have you have the phone here sec but they have me giving you a quick call because of a form you filled out requesting more information about the life insurance programs through the state of ${contact.state}.
They have me, the field underwriter, calling to verify the information,
I have DOB as ${contact.birthday} is that correct? Perfect,
so ${contact.first_name} like I said I’m just the field underwriter they have calling to let you know your request has been processed.`
}
const dom = domHTML(script, contact, user)
console.log(dom)

Vue JS - Apply certain classes to elements in a v-for loop according to data from previous elements in the loop

Sorry for the lengthy title, I have a feeling this is an oddly specific edge case not many people have to deal with.
Some background, I'm working on a webapp to track PC repairs for our shop. We have one currently, which we purchased and have access to the source code thanks to the author's method of distribution. Each repair is signified by a Work Order, all of which have notes. In the old app, the notes have the users name, the date it was posted, and the edit and delete buttons (if you are either the admin or the author) on the left, and the note on the right. If the user that posted the note changes when going down the list, it swaps them around, so the note is on the left and user is on the right, i.e.
user1 - note text
note text - user2
user3 - note text
user3 - note text
note text - user1
The old app did this with plain PHP, in a php file filled to the brim with echo statements. The new app I was working on has a laravel backend (to make use of things like Eloquent) with a Vue JS frontend (to assist with live updates in websockets). So on the Work Order page, there is a component for the list of notes, which takes the list of notes assigned to that Work Order as a prop, and iterates over all the notes with a v-for. I wanted to mimic the orientation switching feature from the previous setup. I can acheive the switch by setting up each note as a grid container, and applying either order-first or order-last to the column containing the user info. What I'm struggling with is trying to find a way to toggle which class is applied when the user changes.
At first I had a data attribute keeping track of the current class, so when the user changed I could check what the class was currently and switch it to the opposite. However, this caused an infinite render loop, as the entire list of notes would re-render whenever that attribute was changed. It did accomplish what I wanted to do visually, but it caused severe performance issues. Then I tried using refs, so when the user changed I could get the previous entry in the list from the refs array and examine its classes to see what order class it had to set the next elements order class appropriately. However this didn't work because the refs array would not be populated until the list was done rendering, and I needed to set the class as it rendered. I tried using a computed property, but it can't take arguments (i.e. the index of the array I was currently on to compare with index-1) and even if it could there is no way I could find to check the current cached value of that property while calculating the new one.
Here is the code I am working with for reference, currently with any of the previous approaches I tried removed, so currently no user switching happens.
<template>
<ul class="list-unstyled">
<li class="row no-gutters mb-2" v-bind:key="index" v-for="(note, index) in this.notes">
<note-form-modal :modal-id="'note'+note.noteid+'editModal'" :populate-with="note"></note-form-modal>
<div class="col-md-1 d-flex flex-column mx-md-3">
<div class="text-center p-0 m-0">{{note.noteuser}}</div>
<div class="text-muted text-small text-center p-0 m-0">{{getHRDate(note.notetime)}}</div>
<div class="btn-group justify-content-center p-0 m-0">
<template v-if="authusername === note.noteuser || authusername === 'admin'">
<button class="btn btn-sm btn-primary m-1" data-toggle="modal" :data-target="'#note'+note.noteid+'editModal'"><i class="fas fa-fw fa-edit"></i></button>
<button class="btn btn-sm btn-danger m-1"><i class="fas fa-fw fa-trash-alt"></i></button>
</template>
</div>
</div>
<div class="col-md-10">
<div class="card m-2">
<div class="card-body p-2">
{{ note.thenote }}
</div>
</div>
</div>
</li>
</ul>
</template>
<script>
import dateMixin from '../mixins/dateMixin'
export default {
mixins:[dateMixin],
props: ['initialnotes', 'authusername', 'noteType', 'woid'],
data () {
return {
notes: Object.values(this.initialnotes),
currentOrder: 'order-first',
newNote: {
notetype: this.noteType,
thenote: '',
noteuser: this.authusername,
woid: this.woid
}
}
},
mounted () {
Echo.channel('wonotes.'+this.noteType+'.'+this.woid)
.listen('WorkOrderNoteAdded', (e) => {
this.notes.push(e.note)
})
.listen('WorkOrderNoteEdited', (e) => {
let index = this.notes.findIndex((note) => {
return note.noteid === e.note.noteid
})
this.notes[index] = e.note
})
.listen('WorkOrderNoteDeleted', (e) => {
let index = this.notes.findIndex((note) => {
return note.noteid === e.noteid
})
this.notes.splice(index, 1)
})
},
methods: {
createNote () {
axios.post('/api/workorders/notes', this.newNote)
.then((response) => {
$('#note'+this.noteType+'add').collapse('hide')
this.newNote.thenote = ''
})
}
}
}
</script>
noteType is there because we have two different types of notes, one that a customer can see, and one that only techs can see.
Is there something obvious I'm missing, have I just architected this thing wrong, am I trying to do something impossible? Any assistance would be greatly appreciated, I'm at the end of my rope here with this one.
I ended up figuring out a solution to this, not sure if its the best one but here goes.
Basically, I maintain an array parallel to the notes array that determines which order- class each array index should have. As it is dependent on the notes attribute, I make it a computed property, so that I can use the notes attribute and it automatically updates when the notes list changes. The file now looks like this (with some code related to posting new notes and editing existing ones removed for clarity)
<template>
<ul class="list-unstyled">
<li class="row no-gutters mb-2" v-bind:key="index" v-for="(note, index) in this.notes">
<div class="col-md-1 d-flex flex-column mx-md-3" :class="noteOrders[index]">
<div class="text-center p-0 m-0">{{note.noteuser}}</div>
</div>
<div class="col-md-10">
<div class="card m-2">
<div class="card-body p-2">
{{ note.thenote }}
</div>
</div>
</div>
</li>
</ul>
</template>
<script>
export default {
props: ['initialnotes'],
data () {
return {
notes: this.initialnotes,
}
},
computed: {
noteOrders () {
return this.getNoteOrders(this.notes)
}
},
methods: {
getNoteOrders(notes) {
let noteOrders = []
notes.forEach((note,index) =>{
if (index === 0) {
noteOrders[index] = 'order-first'
} else if (note.noteuser !== notes[index-1].noteuser) {
if (noteOrders[index-1] === 'order-first') {
noteOrders[index] = 'order-last'
} else {
noteOrders[index] = 'order-first'
}
} else {
noteOrders[index] = noteOrders[index-1]
}
})
return noteOrders
}
}
}
</script>
There may very well be better solutions and I encourage anyone who happens upon this to post theirs if they have one, but as I found a solution that works for me I decided to post it for anyone else running into the same issue.

Angular 5 w/Angular Material - Extracting an object property from ngFor to use in component

<div *ngFor="let player of players">
<h4 mat-line>{{player.firstName}} {{player.lastName}} - {{player.id}}</h4>
</div>
I'm doing a HTTP get call from my player.service.ts file, and then looping through the player object that gets returned, printing out the firstName, lastName and id properties in a massive player list.
I need to extract a specific player ID at a given point in the loop so that I can pass that down to a child Edit Player component that opens a modal with that specific player's information pre-filled in the form (using NgModel and a getbyId call to the API to get the player object). How would I go about doing this?
It looks like you're using #angular/material. If so, you should be able to use a click handler that loads the player data and opens up a dialog with their provided dialog service.
eg:
Template:
<div *ngFor="let player of players">
<h4 (click)="handlePlayerClick(player.id)"
mat-line>
{{player.firstName}} {{player.lastName}} - {{player.id}}
</h4>
</div>
Component:
constructor(private dialogService: MatDialog, private playerApi: PlayerApiService) { }
handlePlayerClick(playerId: string): void {
// potentially open a MatDialog here
this.playerApi.getById(playerId).subscribe((playerData: PlayerInterface) => {
const dialogConfig = {
data: {
playerData: playerData
}
} as MatDialogConfig;
this.dialogService.open(EditPlayerComponent, dialogConfig);
});
}
Documentation: https://material.angular.io/components/dialog/api
You'd want your child component to have a property like #Input() playerId: any; and then simply pass it in square brackets into the child tag like so:
<div *ngFor="let player of players">
<h4 mat-line>{{player.firstName}} {{player.lastName}} - {{player.id}}</h4>
<edit-player [playerId]="player.id"></edit-player>
</div>

passing user input to another component in vue.js

I'm fairly new to vue.js but I couldn't seem to find a concrete answer for what I'm looking for but I also am new enough that I might have been looking right at the answer and not known it. So.
What I'm trying to do is create a 1 page application that tracks turns basically.
It will:
takes user input on how many users are participating
ask for the names of all the users
use the names which I currently have saved in an array, to build user cards, just showing the name of each user.
You will click your name when your turn is over and the next persons card/button will 'raise' and then they click it, etc.
I'm currently having a hard time with that 3rd part. I need to figure out how I can pass this array of users to my other component's data object so I can use vue.js to loop and just spit the cards out.
initial-start.vue - template
<template lang="html">
<div>
<div>
<h1>How many users?</h1>
<input type="number" id="myNumber" value="0">
<button v-on:click="getUsers">Submit</button>
<app-area v-if="users > 0 && users < 5"></app-area>
</div>
</div>
</template>
initial-start.vue - script
<script>
export default {
methods:{
getUsers(){
var counter = 0;
var users = [];
var x = document.getElementById("myNumber").value;
if (x <= 0){
alert('Please choose a number greater than 0.');
}else if(x > 4){
alert('Maximum 4 users!');
}else{
alert('thank you for that.')
}
while(counter < x){
name = prompt('Please enter your names.');
users.push(name);
counter++;
}
return users;
}
}
}
</script>
app-area.vue - template
<template lang="html">
<div>
<h1>Turn: {{ turn }}</h1>
<userCards></userCards>
</div>
</template>
app-area.vue - script
<script>
export default {
data() {
return{
turn: 0,
users: []
}
},
props: ['users']
}
</script>
The question is, "How do I get the array from the getUsers() function, into the app area where I would be able to loop like
<userCards v-for="user in users"></userCards>
and have a card/button for each person entered ?"
You should save the value of getUser in your data, and then you can pass that to your app-area component.
data: {
users: 0
}
methods:{
getUsers(){
#users = 2;
}
And then in your html:
<app-area v-if="users > 0 && users < 5" v-bind:users="users"></app-area>
Now your app-area template has access to users. So you can pass each user to userCard component
<userCards v-for="user in users" v-bind:user="user"></userCards>

Update list in Aurelia using EventAggregator

I am using app-contacts demo to learn Aurelia, yes I know, it's incomplete as mentioned by #Eisenberg, but then I thought to use EventAggregator to notify the app.js, when I save the contact or create a new contact. Till now everything works fine. I am able to receive the contact object in app.js, but now I would like to update the contact list, which is not working and when I save a contact and update the contacts list in app.js, it gets removed.
Code added in app.js and subscribe method is called in constructor.
subscribe(){
this.ea.subscribe('contact_event', payload => {
console.log(payload);
var instance = JSON.parse(JSON.stringify(payload));
let found = this.contacts.filter(x => x.id == payload.id)[0];
if(found){
let index = this.contacts.indexOf(found);
this.contacts[index] = instance;
}else{
instance.id = this.contacts.length + 1;
this.contacts.push(instance);
}
});
}
No changes made to app.html
<li repeat.for="contact of contacts" class="list-group-item ${contact.id === $parent.selectedId ? 'active' : ''}">
<a href="#" click.delegate="$parent.select(contact)">
<h4 class="list-group-item-heading">${contact.firstName} ${contact.lastName}</h4>
<p class="list-group-item-text">${contact.email}</p>
</a>
</li>
How to update the list?
Update
This worked for me, but not sure what is the right approach
subscribe(){
this.ea.subscribe('contact_event', payload => {
return this.api.getContactList().then(contacts => {
this.contacts = contacts;
});
});
}
Eric L. Anderson does a great job of explaining how this works in his blog post:
Aurelia's Event Aggregator. It's event the same type of code you're trying to do!
Note: it's probably far too late to answer Kishore's question, but I'm putting the link in for others who end up here from a search.

Categories

Resources