Why index-of not working correctly in vuejs? - javascript

I make a custom component in Vue.js .In My component, I have a list which has a delete button.On click of a button, it deletes the row.If I click any row it deletes the last row because the index is always -1 why?
here is my code
https://plnkr.co/edit/hVQKk3Wl9DF3aNx0hs88?p=preview
methods: {
deleteTodo:function (item) {
console.log(item)
var index = this.items.indexOf(item);
this.items.splice(index, 1);
}
}
below Whole code
var MyComponent = Vue.extend({
template:'#todo-template',
props:['items'],
computed: {
upperCase: function () {
return this.items.map(function (item) {
return {name: item.name.toUpperCase(),complete:item.complete};
})
}
},
methods: {
deleteTodo:function (item) {
console.log(item)
var index = this.items.indexOf(item);
this.items.splice(index, 1);
}
}
})
Vue.component('my-component', MyComponent)
var app = new Vue({
el: '#App',
data: {
message: '',
items: [{
name: "test1",
complete:true
}, {
name: "test2",
complete:true
}, {
name: "test3",
complete:true
}]
},
methods: {
addTodo: function () {
this.items.push({
name:this.message,
complete:true
});
this.message ='';
},
},
computed: {
totalCount:function () {
return this.items.length;
}
}
});

Instead of passing the whole object you should pass the index of the item.
Change the for loop to
<li v-for="(item, index) in upperCase" v-bind:class="{'completed': item.complete}">
{{item.name}}
<button #click="deleteTodo(index)">X</button>
<button #click="deleteTodo(index)">Toggle</button>
</li>
and the delete function to
deleteTodo:function (itemIndex) {
this.items.splice(itemIndex, 1);
}
Updated Code: Link

Your code is assuming that indexOf will return a valid index
deleteTodo:function (item) {
console.log(item)
var index = this.items.indexOf(item);
this.items.splice(index, 1);
}
If it's returning -1, it means that it's not finding the item in the list. Quite likely this.items is not what you think it is.
A bit of defensive code will help you solve this:
deleteTodo:function (item) {
console.log(item)
var index = this.items.indexOf(item);
if (index === -1)
console.error("Could not find item "+item in list: ",this.items);
else
this.items.splice(index, 1);
}
This will show you what this.items is in your console output

Related

Watch a select quantity selector with Vue 3

I'm trying to make a quick shopping cart with on existing project.
My list items is already is generated by php and I get work with html elements like that :
const billets = document.querySelectorAll(".card-billet");
var products = [];
billets.forEach(billet => {
products.push({
title: billet.querySelector('.card-billet-title').textContent,
price: billet.dataset.price,
qty: billet.querySelector('select[name="billet_quantity"]').value
});
});
const App = {
data() {
return {
items: products
}
},
watch: {
items: function () {
console.log("watched");
},
},
computed: {
total: function () {
console.log(this.items)
let total = 0.00;
this.items.forEach(item => {
total += (item.price * item.qty);
});
return total;
}
}
}
Vue.createApp(App).mount('#checkoutApp')
This works but only on page load but I'm trying to change the total when my select quantity changeno.
I'm a bit lost to achieve this, should I use watch but on what ? Or anything else ?
Finally I found how to achieve this, the problem was that my array was out of the vue instance so can't be updated.
I simplified the code like this :
const App = {
data() {
return {
items: []
}
},
methods: {
onChange: function (e) {
// console.log(this.items)
this.items = [];
document.querySelectorAll(".card-billet").forEach(billet => {
this.items.push({
title: billet.querySelector('.card-billet-title').textContent,
price: billet.dataset.price,
qty: billet.querySelector('.card-billet-qty-selector').value
});
});
}
},
computed: {
total: function () {
// console.log(this.items)
let total = 0.00;
this.items.forEach(item => {
total += (item.price * item.qty);
});
return total;
}
}
}
Vue.createApp(App).mount('#checkoutApp')

Why is my original state mutated when filtering with react redux

im using redux in an react app. Why is this filtering func mutating the original state.products? I cant understand why
state.products = [
{
type: "one",
products: [
{ active: true },
{ active: false }
]
}
]
function mapStateToProps(state) {
const test = state.products.filter((item) => {
if(item.type === "one") {
return item.products = item.products.filter((item) => {
item.active
});
}
return item;
});
return {
machineSearchWeightRange: state.machineSearchWeightRange,
filteredItems: test //This will have only products active
};
}
filteredItems will have only products that is active but the state.products is also updated containing only active products when trying to filter on the same data again.
Suggestions
Because you're assigning to a property on an existing state item:
function mapStateToProps(state) {
const test = state.products.filter((item) => {
if(item.type === "one") {
return item.products = item.products.filter((item) => { // <========== Here
item.active
});
}
return item;
});
return {
machineSearchWeightRange: state.machineSearchWeightRange,
filteredItems: test //This will have only products active
};
}
Instead, create a new item to return. Also, it looks like you need map along with filter, and you're not actually returning item.active in your inner filter (see this question's answers for more there):
function mapStateToProps(state) {
const test = state.products.filter(({type}) => type === "one").map(item => {
return {
...item,
products: item.products.filter((item) => {
return item.active;
})
};
});
return {
machineSearchWeightRange: state.machineSearchWeightRange,
filteredItems: test //This will have only products active
};
}
Side note: This:
products: item.products.filter((item) => {
return item.active;
})
can be simply:
products: item.products.filter(({active}) => active)

Vue v-for on first item containing property X call a method

Let's say I have a component which repeats with a v-for loop like so:
<hotel v-for="(hotel, index) in hotels"></hotel>
And my hotels array would look like so:
[
{
name: false
},
{
name: false
},
{
name: true
},
{
name: true
}
]
How could I perform an action when my v-for loop encounters the property name set to true only on the very first time it encounters this truthy property?
I know I could probably cache a property somewhere and only run something once and not run again if it has been set but this does not feel efficient.
Use a computed to make a copy of hotels where the first one has an isFirst property.
computed: {
hotelsWithFirstMarked() {
var result = this.hotels.slice();
var first = result.find(x => x.name);
if (first) {
first.isFirst = true;
}
return result;
}
}
Just use computed source
HTML
<div v-for="(hotel, index) in renderHotels"></div>
JS
export default {
name: 'message',
data () {
return{
hotels:[
{
name: false
},
{
name: false
},
{
name: true
},
{
name: true
}
] ,
wasFirst : false
}
},
methods:{
},
computed:{
renderHotels(){
return this.hotels.map((hotel)=>{
if(hotel.name && !this.wasFirst){
this.wasFirst = true;
alert('First True');
console.log(hotel);
}
})
}
}
}
Best way is to use filter function.
data() {
return {
firstTime: false,
};
},
filters: {
myFunc: function (hotel) {
if (hotel.name && !this.firstTime) {
//perform some action
this.firstTime = true;
}
}
}
<hotel v-for="(hotel, index) in hotels | myFunc"></hotel>

How can i append table data value in ng-repeat using angularjs

I have two table. dynamicaly data will come for table one after I click the dynamic table data button. after I click the done button I want to
append the data in $scope.notiData.
now after I click done button $scope.notiData data is gone. every time data is coming from tableTwo what is there in presently. so how can i use concat Iin $scope.notiData.please help.
http://jsfiddle.net/A6bt3/127/
Js
var app = angular.module('myApp', []);
function checkBoxCtrl($scope) {
$scope.notiData = [];
$scope.tableOne = [{
firstname: 'robert',
value: 'a'
}, {
firstname: 'raman',
value: 'b'
}, {
firstname: 'kavi',
value: 'c'
}, {
firstname: 'rorank',
value: 'd'
}
];
$scope.tableOne1 = [{
firstname: 'robvzxcvert',
value: 'a'
}, {
firstname: 'ramsdgan',
value: 'b'
}, {
firstname: 'kasdgsdgvi',
value: 'c'
}, {
firstname: 'rordggank',
value: 'd'
}
];
$scope.tableTwo = [];//the table to be submitted
function removeitems(tableRef) { //revmove items from tableRef
var i;
for (i = tableRef.length - 1; i >= 0; i -= 1) {
if (tableRef[i].checked) {
tableRef.splice(i, 1);
}
}
}
$scope.btnRight = function () {
//Loop through tableone
$scope.tableOne.forEach(function (item, i) {
// if item is checked add to tabletwo
if (item.checked) {
$scope.tableTwo.push(item);
}
})
removeitems($scope.tableOne);
}
$scope.btnAllRight = function () {
$scope.tableOne.forEach(function (item, i) {
item.checked = true;
$scope.tableTwo.push(item);
})
removeitems($scope.tableOne);
}
$scope.btnLeft = function () {
$scope.tableTwo.forEach(function (item, i) {
if (item.checked) {
$scope.tableOne.push(item);
}
})
removeitems($scope.tableTwo);
}
$scope.btnAllLeft = function () {
$scope.tableTwo.forEach(function (item, i) {
item.checked = true;
$scope.tableOne.push(item);
})
removeitems($scope.tableTwo);
}
$scope.done = function () {
angular.extend($scope.notiData, $scope.tableTwo);
$scope.tableTwo = [];
}
$scope.removeRow = function (item) {
var index = $scope.notiData.indexOf(item);
$scope.notiData.splice(index, 1);
}
$scope.dynamicTable= function () {
$scope.tableOne1.forEach(function (item, i) {
item.checked = true;
$scope.tableOne.push(item);
})
}
};
I want to append the data in $scope.notiData. now after I click done button $scope.notiData data is gone
The angular.extend replaces existing entries. To append, use array.concat:
$scope.done = function () {
//angular.extend($scope.notiData, $scope.tableTwo);
$scope.notiData = $scope.notiData.concat($scope.tableTwo);
$scope.tableTwo = [];
}
Also to avoid duplicate in ng-repeat use track by errors, use angular.copy to push items:
$scope.dynamicTable= function () {
$scope.tableOne1.forEach(function (item, i) {
item.checked = true;
//$scope.tableOne.push(item);
$scope.tableOne.push(angular.copy(item));
});
};
The ng-repeat directive tracks array objects by reference. Pushing duplicate objects will result in tracking errors. The angular.copy will create a new unique object reference for the item and will avoid the tracking errors.
The DEMO on JSFiddle.

Filter in knockout displaying empty Select List

I am doing some filtering with Knockout. I have written this code Please have a look.
$(function() {
var viewmodel = (function () {
var filter = ko.observable("");
var productsList = ko.observableArray([
{
ProductName: "Sunsilk",
ProductCategory:"Shampo"
},
{
ProductName: "Badminton",
ProductCategory: "Sports"
},
{
ProductName: "Chicken",
ProductCategory: "Meat"
},
{
ProductName: "Head and Shoulder",
ProductCategory: "Shampo"
},
{
ProductName: "Book",
ProductCategory: "Education"
},
{
ProductName: "Pen",
ProductCategory: "Education"
}
]);
return {
productsList: productsList,
filter: filter,
};
}());
viewmodel.filteredItems = ko.computed(function () {
var filter = this.filter().toLowerCase();
if (!filter) {
return this.productsList();
} else {
return ko.utils.arrayFilter(this.productsList, function (item) {
return ko.utils.stringStartsWith(this.item.ProductCategory.toLowerCase(), filter);
});
}
}, viewmodel);
ko.applyBindings(viewmodel);
});
and below is the HTML
<h4> << Decision based on filter >> </h4>
<p><span>Filter: </span><input data-bind="value:filter" type="text" name="filterbox"/> <button name="filter">Filter</button></p>
<select data-bind="options:filteredItems,optionsText:'ProductName'" multiple="multiple" size="3"></select>
The filter doesn't work . I am stuck can anyone help me out please. If the given filter value is null or empty all of the products are returned which is okay. But when I write the specific category for the products none of the products are returned.
Fiddle here
There is small mistake in code that under computed "if" condition always returning true whenever you enter any value and i have also changed the filtering logic.
viewmodel.filteredItems = ko.computed(function () {
var filter = viewmodel.filter().toLowerCase();
if (!filter && filter === "") {
return viewmodel.productsList();
} else {
return ko.utils.arrayFilter(viewmodel.productsList(), function (item) {
if (item.ProductCategory) {
return item.ProductCategory.toLowerCase().indexOf(filter) !== -1;//will return true if ProductCategory contains the filter string
}
});
}
}, viewmodel);
Fiddle Demo
try this code
viewmodel.filteredItems = ko.computed(function () {
var filter = viewmodel.filter().toLowerCase();
if (!filter) {
return viewmodel.productsList();
} else {
return ko.utils.arrayFilter(viewmodel.productsList, function (i,item) {
if(item.ProductCategory)
{
return ko.utils.stringStartsWith(item.ProductCategory.toString().toLowerCase(), filter);
}
});
}
}, viewmodel);

Categories