Why nested array dynamic update is not working in my case - javascript

Hi i have a problem with nested array dynamic update as shown in below image
Here is a steps to re-produce the problem
click on Add Likes button it will add likes for eg New Apple
my default like is Mango
click on Add about button it will add next row (Mango is not appeared) click on Add Likes to see the problem.
For reducing code readability i have put helper function into a mixin file called mixin.js
Expectation: i can add any number of Add about and Add Likes(with default value Mango)
Here is my code: https://codesandbox.io/s/musing-hawking-di4d3?file=/src/App.vue

First, you don't need a nested array for the tags property.
Use:
getLikesTemplate() {
let year = this.year;
let template = {
id: this.getUniqueId(),
like: `I'm ${year} Old and like things like`,
tags: [this.getTagsTemplate("Mango")] //nesting removed
};
this.year++;
return template;
}
Secondly, in JS objects are passed by reference, so you can do this:
method:
addLikes(like) { //removed the extra code
like.tags.push(this.getTagsTemplate("New Apple"));
},
template:
...
<div style="text-align: left; display: flex">
<div> //nesting removed
<div class="tags" v-for="tag in like.tags" :key="tag.id">
{{ tag.name }}
</div>
</div> //passing the reference to the object
<button style="margin-left: 20px" #click="addLikes(like)">
Add Likes
</button>
</div>
Result img

Related

Reload Data with .sort from Dynamic Table with Jquery | JSON | Sort with New Data

I have been trying for a few days that when I use the .sort property the data is eliminated or modified instead of it being reloaded as new lines.
Attach Captures from the code Working
Image1 How work the code when i press the button, this sort to the highest price to lowest but how do you can see in the second image, the code appears up and this not delete the old data
Marked with "X" the data that does not have to show
this fragment is the one that generates the tables dynamically
const mostrarProductos = () => {
$.getJSON(URLJSON, (respuesta) => {
for (let z of respuesta) {
productosv2.push(z);
}
for (let x of productosv2) {
$("#fila").append(`
<tr class="deleteProductos">
<div class="card text-center" style="width: 18rem;" id='btnBorrarCarrito'>
<div class="card-body">
<input type="hidden" id="idProd" value="${x.id}"> </td>
<td class="card-title" id="${x.id}">${x.producto}</h2> </td>
<td class="card-text">$ ${x.precio}</p></td>
<div class="btn-group" role="group" aria-label="Basic mixed styles example">
<td><button type="button" class="btn btn-success" onclick="agregarCarrito(${x.id})">Agregar</button></td>
</div>
</div>
</div>
</tr>
`);
}
$("#fila").fadeIn("5000");
});
};
And this function is what orders them
function respuestaClickExpensive() {
$("#fila").html('');
let productosordenados = productosv2.sort((a, b) => {
if (a.precio > b.precio) {
return -1;
}
if (a.precio < b.precio) {
return 1;
}
return 0;
});
return productosordenados;
}
The one that orders them from smallest to largest is the same only that different signs and names.
As you can see I tried to use a ".html ("")" since previously in another cart attempt it used .innerHtml (Which does not work in this case either, Also the code of the cart is totally different from that moment that it worked for me
)
I tried the following:
$ ("#fila"). empty ();
Make another function to clean with .empty
Use Native JavaScript to generate the code.
$ ("#fila"). remove (); this removes all the content for me but does not regenerate it.
Change the HTML tag "Row" to a previous div which, since the div was not generated again, did not generate it again.
$ ("#fila tr"). remove ();
And some more things that I don't remember right now.
If you can guide me on what I did wrong or any suggestions to fix it, I appreciate it.
If I had to follow a template about posting on StackOverFlow or having chosen or named in a different way, I appreciate your comment since it is my first post
Project notes of possible relevance: The complete code outside of html and css is made with Native JavaScript, Jquery, Ajax, SASS and BootStrap.

Angular - Iterate and numerate elements without jQuery

Object has 3 properties:
public obj;
...
this.obj = {
height: 10,
width: 20,
weight: 30
};
Then I bind buttons with those properties, but I want to numerate buttons and show them only if the property value exist:
<button *ngIf="obj.height"> Button <span class="buttonNr">1</span> </button>
<button *ngIf="obj.width"> Button <span class="buttonNr">2</span> </button>
<button *ngIf="obj.weight"> Button <span class="buttonNr">3</span> </button>
However, if lets say width is missing, I would get "Button 1" and "Button 3"
and I want them to be numerated normally, no matter which properties exist or not, I want to have Button 1 then 2 then 3...
In case width doesn't exist, I want to have Button 1 for height, and Button 2 for weight.
Now I know how to do it with jQuery, to iterate and populate spans with class buttonNr counting from 1 till the end.
But I'd like to do it Angular way, without jQuery.
EDIT:
Sorry, when I saw the first answer, I realized I oversimplified my example: there are various objects.
So imagine there's also
public data;
public book;
...
this.data = {
color: 'blue',
price: 100
}
this.book = {
title: 'a',
author: 'b'
}
and then I just keep on adding buttons, but also their display depends.
<button *ngIf="data.color"> Button <span class="buttonNr">4</span> </button>
<button *ngIf="data.price"> Button <span class="buttonNr">5</span> </button>
<button *ngIf="book.title"> Button <span class="buttonNr">6</span> </button>
<button *ngIf="book.author"> Button <span class="buttonNr">7</span> </button>
If you want to be explicit about the order of the buttons, you can define a getButtons() method on the component:
getButtons() {
return [
this.data?.color,
this.data?.price,
this.book?.title,
this.book?.author
].filter(val => val !== undefined);
}
The method takes the ideal order of all the buttons, then filters out the values that don't exist.
Then you just iterate over the result in the template:
<button *ngFor="let value of getButtons(); let i = index">
Button <span class="buttonNr">{{i + 1}}</span>
</button>
Your solution could look as follows
<button *ngFor="let key of obj | keyvalue; let i = index">
Button <span class="buttonNr">1</span>
</button>
Let me explain what happens here
*ngFor is a directive that renders a template for each item in a collection
keyvalue is a special Pipe that allows iterating through object properties
let i = index - exporting iteration index into i variable
Update
According to the answer updated, I've updated my solution to the multiple objects including cumulative counter
Here you can find a simple example: https://stackblitz.com/edit/angular-ivy-qn8x38?file=src%2Fapp%2Fapp.component.html

Cannot assign to read only property 'bEditing' of object '#<Object >' error on lwc LWC

I'm getting the above error, when trying to edit a property value of an object inside list. Please refer below code for better understanding.
HTML:
<template>
<div class="container">
<div class="slds-grid slds-gutters">
<div class="slds-col" for:each={lstQuotes} for:item="objQuote" for:index="pindex" key={objQuote.Id}>
Quote : <span>{objQuote.sQuoteName}</span><br/>
Opportunity : <span>{objQuote.sOpptName}</span><br/>
<div>
<template for:each={objQuote.lstComments} for:item="objComment" for:index="index">
<div key={objComment.sRecordId}>
<span if:true={objComment.bEditing}>
<input type="text" value={objComment.sComment}/>
</span>
<span class="comment" if:false={bEditing}>{objComment.sComment}</span>
<span class="createdDate">{objComment.sCreatedDate}</span>
<span class="createdBy">{objComment.sCreatedBy}</span>
<span if:true={objComment.bShowEdit}>
<a onclick={handleEdit} data-pindex={pindex} data-index={index}>Edit</a>
</span>
</div>
</template>
</div>
<div>
<input type="button" name={objQuote.sQuoteId} value="Add Comment" onclick={showCommentBox} />
<input class="hide" data-id={objQuote.sQuoteId} data-index={index} value={objQuote.sComment} onkeyup={changeComment}/>
<input type="button" class="hide" data-index={index} data-id={objQuote.sQuoteId} value="Save" onclick={saveComment} />
</div>
</div>
</div>
</div>
</template>
The error is thrown when I click on Edit anchor tag, resulting in handleEdit js method being called. If you look at the html, you'll understand that I am displaying the comment for respective quote dynamically inside a span using {objComment.sComment} and on click of edit, I'll be displaying the same comment value inside an input field to allow edits to the comment. I use a boolean variable bEditing to hide and show input/span comment.
Below is the JS for better understanding:
#track lstQuotes =[];
#track lstQuotesTemp = [];
#track lstComments = [];
//sComment = '';
#wire(QUOTE_DATA)
quotesData({error, data}){
if(data){
console.log('data : ' ,data);
this.lstQuotes = data;
//Create copy of records
this.lstQuotesTemp = this.lstQuotes.map( obj => ({
...obj
}));
}
else if(error){
console.log('error : ',error);
}
}
The above wire method gets data from back-end which is displayed inside the web component.
Now lstQuotesTemp holds a list of records, and under each record there is a list of comments, lstComments.
I created lstQuotesTemp simply because lstQuotes is read only and changes to it's records would result in error.
Now, let's see what handleEdit method does:
handleEdit(event){
let parentIndex = event.target.dataset.pindex;
let index = event.target.dataset.index;
console.log('Comment Record : ' ,this.lstQuotesTemp[parentIndex].lstComments[index].bEditing);
this.lstQuotesTemp[parentIndex].lstComments[index].bEditing = true;
}
It simply finds the comment record using index to make it editable on click of Edit. However, it seems that the lstComments is still read only even after creation of a copy of it's parent list.
Can someone please suggest a way to fix this error?
I was able to solve the above. The issue was that lstComments under lstQuotesTemp was read-only and thus had to create their copy. Below is what I did :
for(let i=0;i<this.lstQuotesTemp.length; i++){
this.lstQuotesTemp[i].lstComments = this.lstQuotesTemp[i].lstComments.map( obj => ({
...obj
}));
}
It worked as expected after creating a copy of lstQuotesTemp.lstComment.

How to display just one element with same id

This is my first question at stack overflow
i just wanted to know a simple solution for the following case
<div *ngFor="let d of w.event">
<div class="date" id="d.date" >
<p>
<span style="font-size:1.75em">{{d.date | date:'dd'}}</span>
<br>
<strong> {{d.date | date:'EEE'}}</strong>
</p>
</div>
the looped div can have the same id
I just want to display the first div with a particular date and ignore the rest
can this be achieved with CSS or JavaScript
You can't use the same id on two elements. It's one of the few restrictions on ids.
You can use a class:
<div class="show">Yes</div> <div class="show">No</div>
...and then show either the first or second by using index 0 or index 1 after getting a list of matching elements:
var list = document.querySelectorAll(".show");
list[0].style.display = "none"; // Hides the first one
// or
list[1].style.display = "none"; // Hides the second one
Some other thoughts:
1. Rather than using style.display as I did above, you might add a class that hides the element.
2. You might use separate ids (or classes) for the elements so you don't need to index, e.g.:
<div id="show-yes">Yes</div> <div id="show-no">No</div>
then
document.getElementById("show-yes").style.display = "none";
// or
document.getElementById("show-no").style.display = "none";
On all browsers in my experience, you can do the first thing above (with querySelectorAll) with your invalid HTML with a selector like "[id=show], but don't. Fix the HTML instead.
In your question update, you show:
<div *ngFor="let d of w.event">
<div class="date" id="d.date" >
...
You've said you're aware of the fact you can't have multiple elements with the same id, so why code that? You can easily give them unique ids:
<div *ngFor="let d of w.event; let i = index">
<div class="date" id="d.date{{i}}" >
...
First of all, in HTML ID is a unique selector so one ID can be associate with only one element. if you want to achieve your desired functionality you have to assign different id for both DIV. and use javascript to hide and show DIV
<div id="showYes">Yes</div> <div id="showNo">No</div>
If you want to show one at a time you can go with *ngIf , as it will show only one at a time
<div id="show" *ngIf='your_status'>Yes</div>
<div id="show" *ngIf='!your_status'>No</div>
After your question update , you can create custom filter that will only return unique date , so only first unique date will be shown
// CREATE A PIPE FILTER :
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({name: 'checkUniqueDate'})
export class UniqueDatePipe implements PipeTransform {
transform(dataArray) {
let dates = [];
return dataArray.filter(data => {
return if(dates.indexOf(data.date) === -1) {
dates.push(data.date);
return true;
} else {
return false;
}
});
}
}
// TEMPLATE SIDE :
<div *ngFor="let d of (w.event | checkUniqueDate )">
Add the date in class also, then you can try below code
.YOUR_DATE{
display:none
}
.YOUR_DATE:first-child{
displany:inline
}

VueJs: How to Edit an Array Item

Simple Todo-App. Please excuse my ignorance for making a rather basic question.
But how would you go about and edit a certain item on an array?
Normally I would try to bind the value of my input to a new property on my data object and then assign this new property to the old property on click throuch Vue's two way databinding.
Like this: http://jsfiddle.net/z7960up7/
Well in my case I use the v-repeat directive, which loops through my data array but I can't use the v-model directive to use the two way databinding, because the values of the properties get corrupted if I do so. (See here: http://jsfiddle.net/doL46etq/2/)
And now I wonder, how I would go about updating my array of tasks:
My idea is to pass the VueObject (this) through my method on click, and then define the index on my event handler and then updating the tasks array, using the index, like this:
HTML:
<input v-el="editInputField" type="text" value="{{ task.body }}" v-on="keyup: doneEdit(this) | key 'enter'"/>
<button v-on="click: editTask(this)">
Edit
</button>
JS:
methods: {
editTask: function (task) {
var taskIndex = this.tasks.indexOf(task.task);
this.tasks[taskIndex] = {
'body': document.querySelector('input').value,
'completed': false
};
console.log(task.task.body);
},
}
Here is my fiddle about it:
http://jsfiddle.net/doL46etq/3/
But the data object is not updated at all and I wonder how I would go about it and update it.
What is the best way to edit an element on the array, using Vue?
Edit: An easy way, would just be to delete the element, and add the new to the array, using the push method, but I really want just to update the element, because I like to keep the dataobject in sync with my backend.
The short answer: Use a component in an extended constructor, then pass the index to that component in HTML as property and use computed properties to link back and forth to your data.
But don't be satisfied with just the short answer. Here is the long one:
Situation: I am using your JSFiddle as base for this answer.
in HTML you have:
<td>{{ task.body }}</td>
<td>
<div>
<input v-el="editInputField" type="text" value="{{ task.body }}" v-on="keyup: doneEdit(this) | key 'enter'" v-model="newEdit"/>
</div>
</td>
<td>
<button v-on="click: editTask(this)" class="mdl-button mdl-js-button mdl-button--icon"> <i class="material-icons">create</i>
</button>
</td>
We want to replace this code with the component. Using this component allows us to identify the index/row we are working on in your set of data.
<td v-component="listitem" index="{{$index}}"></td>
Next step: defining the component.
In order not to cloud our instance with the component, we will create a separate constructor for the vue object, so we can assign the new element to our new object.
This is done using extend.
window.newVue = Vue.extend({
components:
{
'listitem': {
props: ['index'],
computed: {
// both get and set
body: {
get: function () {
return this.$parent.tasks[this.index].body;
},
set: function (v) {
this.$parent.tasks[this.index].body = v
}
}
},
template: '<td>{{ body }}</td><td><div><input type="text" v-model="body" value="{{ body }}"/></div></td><td></td>',
}
}
});
Since we can't define our data properly using an extend, we'll just assume the data already exists while writing the component.
Step 3: defining the actual data:
Instead of using Vue as our object constructor, we'll now use our newly created instantiator.
new newVue({
el: '#todoapp',
data: {
tasks: [{
'body': 'Eat breakfast',
'completed': false
}, {
'body': 'Drink milk',
'completed': false
}, {
'body': 'Go to the store',
'completed': false
}],
newTask: '',
},
});
That's it!
In case you couldn't follow what happened: Here's the Fiddle!
PS: More information about the working of these code can be found on vuejs.org
Actually the simplest way to update an array item, is to two-way bind the task body with the v-model directive.
Example:
http://jsfiddle.net/z7960up7/2/
<div id="demo">
{{ message }}
<div class="edit">
<input type="text" v-model="message">
<button v-on="click: editMessage">Edit</button>
</div>
<pre>{{ $data | json }}</pre>
</div>
And fire an event whenever you blur out of the input box or the edit button is hit.
Also hide the input field with css, by using the v-class directive.

Categories