Splicing array does not re-render table row Vuejs - javascript

I have a user table generated with data from an ajax request. Table roughly looks like this: [Image of Table][1]
When an admin edits a user's username, I want the user's row to re-render with the changes (specifically the users firstname and lastname).
I create the table fine. The models work fine. My parent component receives the edited data just fine in my edit() method, but I can't seem to make my target row re-rendered with my changed data. How can I make my target row update?
I tried the following and it didn't work:
How to update a particular row of a vueJs array list?
https://v2.vuejs.org/v2/guide/list.html#Caveats
I have set key to my row
Tried setting my listOfUsers with Vue.set()
Tried using Vue.set() in place of splice
Here is my parent vue component with the following (I took out irrelevant details):
TEMPLATE:
<table>
<thead>
<tr>
<th>Name</th>
<th>Email Address</th>
<th>Created</th>
<th>Stat</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="(user, index) in listOfUsers" :key="'row' + user._id">
<td>{{user.first_name + ' ' + user.last_name}}</td>
<td>{{user.email}}</td>
<td>{{user.created}}</td>
<td>
<a v-if="user.confirmed" #click="determineButtonClicked(index, 'confirm')"></a>
<a v-else #click="determineButtonClicked(index, 'unconfirm')"></a>
</td>
<td class="buttonCase">
<a #click="determineButtonClicked(index, 'info')"></a>
<a v-if="user.blocked" #click="determineButtonClicked(index, 'blocked')"></a>
<a v-else #click="determineButtonClicked(index, 'block')"></a>
<a v-if="user.enforce_info === 'required'" #click="determineButtonClicked(index, 'enforceInfoActive')"></a>
<a v-else-if="user.enforce_info === 'done'" #click="determineButtonClicked(index, 'enforceInfoChecked')"></a>
<a v-else #click="determineButtonClicked(index, 'enforceInfo')"></a>
<modal v-if="usersList[index]" #toggleClickedState="setState(index)" #editUser="edit(index, $event)" :id="user._id" :action="action"></modal>
</td>
</tr>
</tbody>
</table>
SCRIPT
<script>
export default {
created: function() {
let self = this;
$.getJSON("/ListOfUsers",
function(data){
self.listOfUsers = data;
});
},
data: function() {
return {
listOfUsers: [],
}
},
methods: {
edit(index, update){
let user = this.listOfUsers[index];
user.firstName = update.firstName;
user.lastName = update.lastName;
// this.listOfUsers.splice(index, 1, user)
this.listOfUsers.$set(index, user)
}
}
}
</script>
Thank you for your time and help!
[1]: https://i.stack.imgur.com/lYQ2A.png

Vue is not updating in your edit method because the object itself is not being replaced. Properties of the object do change, but Vue is only looking for the object reference to change.
To force the array to detect a change in the actual object reference, you want to replace the object, not modify it. I don't know exactly how you'd want to go about doing it, but this hacked together fiddle should demonstrate the problem so you can work around it: http://jsfiddle.net/tga50ry7/5/
In short, if you update your edit method to look like this, you should see the re-render happening in the template:
methods: {
edit(index, update){
let currentUser = this.listOfUsers[index];
let newUser = {
first_name: update.firstName,
last_name: update.lastName,
email: currentUser.email,
created: currentUser.created
}
this.listOfUsers.splice(index, 1, newUser)
}
}

You can have a try like this
<script>
export default {
created: function() {
let self = this;
$.getJSON("/ListOfUsers",
function(data){
self.listOfUsers = data;
});
},
data: function() {
return {
listOfUsers: [],
}
},
methods: {
edit(index, update){
let user = this.listOfUsers[index];
user.firstName = update.firstName;
user.lastName = update.lastName;
// this.listOfUsers.splice(index, 1, user)
this.$set(this.listOfUsers,index, user)
}
}
}
</script>

Related

React updating state needs 2 clicks in order to update

Let me start off by saying that I did look for other topics but I haven't found a solution yet so I'd like to walk through this with you guys.
I have a simple website with a search bar, a search result list and a div where I display the item I click in the result list.
The issue starts when I click an item in the results list. I need to click it twice for it to update the div where I display the item.
What happens when I click an item from the search results list:
const getProductById = (store) => {
if (store == "STORENAME") {
Axios.get(
`http://localhost:3001/get-storename-product-by-id/${productId}`
).then((response) => {
console.log(response.data);
setProductResultList(response.data);
setProductTitle(response.data[0].title);
setProductImg(response.data[0].imageSrc);
setFinalProductId(response.data[0].productId);
});
} else {
Axios.get(`http://localhost:3001/get-storename-product-by-id/${productId}`).then(
(response) => {
console.log(response.data);
setProductResultList(response.data);
setProductTitle(response.data[0].title);
setProductImg(response.data[0].imageSrc);
setFinalProductId(response.data[0].productId);
}
);
}
};
The function fetches all the data linked to the productId of the clicked product (this returns all the historic data I have on the item in an array with objects (1 object for each row)).
How I show the item on the page:
<div className="item">
<div>
<img
src={productImg}
alt={productTitle}
width="250px"
height="250px"
/>
<p className="product-id-span">
Product Id: {finalProductId}
</p>
<p className="m-0">Product name:</p>
<p>{productTitle}</p>
<div className="historical-info">
<span>latest prices:</span>
<div className="table-responsive">
<table class="table text-white">
<thead>
<tr>
<th scope="col">Price</th>
<th scope="col">Date</th>
</tr>
</thead>
<tbody>
{productResultList.map((val, key) => {
let parsedPrice = parseInt(val.price);
let parsedPriceInEuros = parsedPrice / 100;
const finalPrice = new Intl.NumberFormat(
"de-DE",
{ style: "currency", currency: "EUR" }
).format(parsedPriceInEuros);
return (
<tr>
<td>
Price:
{val.store == "STORENAME"
? finalPrice
: val.price}
</td>
<td>{val.date}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
</div>
What I've tried:
I tried to only set state to the productResultList in the getProductById function, and set
the other state when the useEffects note changes to the productResultList.
useEffect(() => {
setProductTitle(productResultList[0].title);
setProductImg(productResultList[0].imageSrc);
setFinalProductId(productResultList[0].productId);
}, [productResultList]);
Could someone perhaps explain what I'm doing wrong or what's the right way to do this?
Note:
I changed the real store names to STORENAME because it's not neccessary in this example.
resultList = the list with results when I search, productResultList is the list with objects of the clicked product.
So the solution to this problem was fairly different to what I expected.
The function that initializes getProductById() sets state to productId first so I can use that in my request as you can see.
Because setting state is asynchronious, the productId was not available on the first request.
I fixed this by passing the productId as a parameter to the getProductById function so it does not have to wait for the state to be changed.

Updating the DOM with arrays JavaScript

I am having a problem when I try to update the DOM with new information coming from an API.
Every time that I click to add new users, the array displays the old, and new information. Ideally, it would update the array first and then display only the new information. I will attach a picture of what is happening. I would like to every time the user click on add new user, the DOM update with only the information of that new user.
HTML part
<table class="table is-fullwidth table is-hoverable table-info">
<thead>
<tr">
<th title="Channel Name" class="has-text-left"> Channel Name </th>
<th title="View per week" class="has-text-right"> View per week </th>
</tr>
</thead>
<tbody id="body-table">
<tr id="tr-table">
</tr>
</tbody>
</table>
script.js
const trline = document.getElementById('body-table')
let usersList = [];
async function getnewUsers(){
const res = await fetch('https://randomuser.me/api')
const data = await res.json()
// create an instance of the results
const user = data.results[0]
// create the new user
const newUser = {
name:`${user.name.first} ${user.name.last}`,
social: Math.floor(Math.random() * 10000 )
}
// update the new user to the database...
addData(newUser)
}
function addData(obj) {
usersList.push(obj)
// update the information on the screen
updateDOM()
}
function updateDOM( providedData = usersList){
providedData.forEach(item => {
const element = document.createElement('tr')
element.innerHTML = `
<td class="has-text-left cname"> ${item.name} </td>
<td class="has-text-right cview"> ${item.social} k</td>
`
trline.appendChild(element)
})
}
addUser.addEventListener('click', getnewUsers)
Result picture:
I found the problem and the solution.
I didn't reset the HTML part to clear before adding a new item. I had to fix the function updateDOM with this: trline.innerHTML = ''
After that, the function works fine.
function updateDOM( providedData = usersList){
trline.innerHTML = '' // clear everything before adding new stuff
providedData.forEach(item => {
const element = document.createElement('tr')
element.innerHTML = `
<td class="has-text-left cname"> ${item.name} </td>
<td class="has-text-right cview"> ${item.social} k</td>
`
trline.appendChild(element)
})
}

Data Binding to Array in Polymer

I'm in the process of learning Polymer. I am trying to bind an array to my UI. Each object in the array has a property that will change. I need my UI to update when the property value changes. I've defined my Polymer component as follows:
my-component.html
<dom-module id="my-component">
<template>
<h1>Hello</h1>
<h2>{{items.length}}</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr repeat="{{ item in items }}">
<td>{{ item.name }}</td>
<td>{{ item.status }}</td>
</tr>
</tbody>
</table>
<br />
<button on-click="testClick">Test</button>
</template>
<script>
// element registration
Polymer({
is: "my-component",
properties: {
items: {
type: Array,
value: function() {
return [
new Item({ name:'Tennis Balls', status:'Ordered' }),
new Item({ name:'T-Shirts', status: 'Ordered' })
];
}
}
},
testClick: function() {
for (var i=0; i<items.length; i++) {
if (items.name === 'Tennis Balls') {
items[i].status = 'Shipped';
break;
}
}
}
});
</script>
</dom-module>
The component renders. However, the bindings do not work at all.
The line with {{ items.length }} does not show a count. Its basically just an empty h2 element.
The first item gets rendered in the list. However, the second one does not.
When I click the Test button, the update to the status is not reflected on the screen.
When I look at everything, it looks correct to me. However, it is clear from the behavior that the data-binding is not setup properly. What am I doing wrong? The fact that items.length and the initial rendering of all of the items in the array has me really confused.
Polymer data binding system works like this:
If the declared property changes (for example adding a new item) then it will detect the change and update your DOM.
However Polymer won't monitor changes inside your property (For performance/compatibility reasons).
You need to notify Polymer that something inside your property changed. You can do that using the set method or notifyPath.
E.g (From the polymer data binding section)
this.set('myArray.1.name', 'Rupert');
You can also add an observer if you want to do something when your array is updated.
Polymer 1.0 properties Documentation
And I think you should also add a notify:true to your property
items: {
type: Array,
notify:true,
value: function() {
return [
new Item({ name:'Tennis Balls', status:'Ordered' }),
new Item({ name:'T-Shirts', status: 'Ordered' })
];
}
}

How to update data from database with api call in angular

I have a api call who give me the list of data, and I am iterating data via ng-repeat (its a list of more than 100 items)
For getting list of data I have call an Api in App Controller in angularjs like this:
var path = serverUrl + 'api/getAllMails';
$http.get(path).then(function (result) {
$scope.mails=result
})
For Iterating the mails in Html file i have use table like the below
<table>
<tr class="header">
<th class="center">Id</th>
<th class="center">Mode of Payment</th>
<th class="center">Payment Collected</th>
<th class="center">Status</th>
</tr>
<tr ng-repeat="mail in mails">
<td>{{mail.id}}</td>
<td>{{mail.paymentType}}</td>
<td>Rs. {{mail.cost}}
<input type="text" ng-model="mail.cost">
<button ng-click="updateCost=(mail.id, mail.cost)">Update Cost</button>
</td>
<td>{{mail.status}}
<input type="text" ng-model="mail.status">
<button ng-click="updateStatus(mail.id, mail.status)">Update Status</button>
</td>
</tr>
</table>
Suppose in the first iterations the cost will be "100" and the status will be "pending". And I have to update this row only, change cost to "1000" and status will be "Delivered".
In my App controller of Angularjs I have create methods. These two methods are calling apis and updating data in database and return the list of updated mails.
$scope.updateStatus = function(mailId, mailStatus) {
var path = serverUrl + 'api/updateStatus';
$http.get(path, {
params: {
mailId: mailId,
mailStatus: mailStatus
}
}).then(function(result) {
$scope.mails = result
})
}
$scope.updateCost = function(mailId, mailCost) {
var path = serverUrl + 'api/updateStatus';
$http.get(path, {
params: {
mailId: mailId,
mailCost: mailCost
}
}).then(function(result) {
$scope.mails = result
})
}
These code are working fine but while it took lot of time to load a page. So what can I do to reduce the loading time or is there any better way to do the same thing.
Any help will be appreciable. Thank you
You are replacing the entire dataset when there is no reason for that, you should only update the row you change. Ensure your updateStatus return the object you update and update that item in $scope.mails
In example
$scope.updateCost = function(mailId, mailCost) {
var path = serverUrl + 'api/updateStatus';
$http.get(path, {
params: {
mailId: mailId,
mailStatus: mailCost
}
}).then(function(result) {
// result is the item you changed
for (var i = $scope.mails.length - 1; i >= 0; i--) {
if($scope.mails[i].id === mailId) {
$scope.mails[i] = result;
return;
}
};
})
}

Knockout JS Binding and Filter Data

I have the following Problem
I have this Code to load Json Data from a external Web api
and Show it in my site this works..
but my Problem is
I must FILTER the Data with a Dropdown List
When i select the Value "Show all Data" all my Data must be Show
and when i select the Value "KV" in the Dropdown only the Data
with the Text "KV" in the Object Arbeitsort must Show..
How can i integrate a Filter in my Code to Filter my Data over a Dropdown ?
and the next is how can i when i insert on each Item where in HTML Rendered a Button
to Show Details of this Item SHOWS his Detail Data ?
when i click Details in a Item i must open a Box and in this Box i must Show all Detail Data
of this specific Item ?
$(document).ready(function () {
function StellenangeboteViewModel() {
var self = this;
self.stellenangebote = ko.observableArray([]);
self.Kat = ko.observable('KV');
$.getJSON('http://api.domain.comn/api/Stellenangebot/', function (data) {
ko.mapping.fromJS(data, {}, self.stellenangebote);
});
}
ko.applyBindings(new StellenangeboteViewModel());
});
I'll give this a go, but there's quite a few unknowns here. My suggestions are as follows:
First, create a computed for your results and bind to that instead of self.stellenangebote
self.stellenangeboteFiltered = ko.computed(function () {
// Check the filter value - if no filter return all data
if (self.Kat() == 'show all data') {
return self.stellenangebote();
}
// otherwise we're filtering
return ko.utils.arrayFilter(self.stellenangebote(), function (item) {
// filter the data for values that contain the filter term
return item.Arbeitsort() == self.Kat();
});
});
With regards the detail link, I'm assuming you are doing a foreach over your data in self.stellenangeboteFiltered(), so add a column to hold a link to show more details:
<table style="width:300px">
<thead>
<tr>
<th>Id</th>
<th>Arbeitsort</th>
<th>Details</th>
</tr>
</thead>
<tbody data-bind="foreach: stellenangeboteFiltered">
<tr>
<td><span data-bind="text: Id"> </span></td>
<td><span data-bind="text: Arbeitsort"> </span></td>
<td>Detail</td>
</tr>
</tbody>
</table>
Add a control to show details:
<div data-bind="visible: detailVisible, with: selectedItem">
<span data-bind="text: Position"> </span>
<span data-bind="text: Arbeitsort"> </span>
</div>
In your JS add a function:
// add some observables to track visibility of detail control and selected item
self.detailVisible = ko.observable(false);
self.selectedItem = ko.observable();
// function takes current row
self.showDetail= function(item){
self.detailVisible(true);
self.selectedItem(item);
};
UPDATE
Here's an updated fiddle: JSFiddle Demo

Categories