angular checkboxes with 2 dimensional array - javascript

I have a template:
<mat-card *ngFor="let cargo of cargos" class="cont-mat">
/*...*/
<form [formGroup]="cargoSignupForm" (submit)="onSignupForCargo(cargo.id)" *ngIf="!isLoading">
<p>Odaberite kamione s kojima želite izvršiti prijevoz - Težina robe: {{cargo.weight}} kg</p>
<div *ngFor="let truck of (trucksByCargoId.get(cargo.id))">
<input type="checkbox" (change)="onChange(truck._id, $event.target.checked, cargo.id)">{{truck.regNumber}}
</div>
<input type="submit" value="GO" class="button-basic">
</form>
and 2 component functions:
truckFormArray = [[]];
ngOnInit() {
/*...*/
this.cargoSignupForm = new FormGroup({
trucksId: this.fb.array([])
});
/*...*/
}
onChange(truckId: string, isChecked: boolean, cargoId: string) {
this.truckFormArray[cargoId] = <FormArray>this.cargoSignupForm.controls.trucksId;
if (isChecked) {
this.truckFormArray[cargoId].push(new FormControl(truckId));
} else {
let index = this.truckFormArray[cargoId].controls.findIndex(x => x.value == truckId)
this.truckFormArray[cargoId].removeAt(index);
}
}
onSignupForCargo(cargoId: string) {
console.log(this.truckFormArray[cargoId]);
}
I just want to console_log(this.truckFormArray[cargoId]). There must be different truckFormArray for every cargoId. With that solution I'm getting trucksFormArray from previus cargoId checkboxes also. I hope you understand what I mean. Somewhere is a small mistake, but also if you think there is a better solution to do that you are welcome. Thanks in advance

truckFormArray should be an object
It is safe to assume that cargoId is not a sequentially increasing number starting from 0, hence there is no sense in declaring it as an array, declare it as an object instead:
truckFormArray = {};
Reason: Arrays in Javascript are always indexed by numbers starting from 0 and increasing sequentially until the last index.
truckFormArray is not a data member of the instance of your object
Since it is not initialized as this.truckFormArray, you do not have the this in front of it. So change all occurrences of this.truckFormArray to truckFormArray.
Reason: You will always need to be consistent when you refer to your resources.
Initializing truckFormArray
You have
this.truckFormArray[cargoId] = <FormArray>this.cargoSignupForm.controls.trucksId;
and it seems to be incorrect. Your trucksId seems to be a number and you try to assign it to a resource which is treated as an array, so there is a type discrepancy. If you want to store each trucksId into an array identified by the cargo that is present on the trucks, then you need to do a push, but only if the checkbox is checked. So, instead of the above, you will need something like this:
if (!truckFormArray[cargoId]) {
//Here I created a JS array for the sake of simplicity, but you can
//create a FormArray instance instead if that's more helpful for you
this.truckFormArray[cargoId] = [];
}
Reason: if the array you need to refer to does not exist yet, then you need to create it.
Summary
You will need to
fix the initialization of truckFormArray
ensure that you refer it consistently with the way it is defined
initialize each of its cargo array when needed

Related

Can't get mutator logic to correctly add the proper amount of items into a cart

I'm trying to make an add to cart function that first checks if the item being added is already in the cart. If it's in the cart, update its quantity property. If not in the cart, add the entire object to the cart. I think my problem is I'm getting the logic wrong inside my "ADD_ITEM_TO_CART" mutator function.
This is my store with some console.logs() from when I click "addToCart()"
state: {
checkoutCart: [],
},
actions: {
cartAdd({ commit }, payload) {
commit("ADD_ITEM_TO_CART", payload);
},
},
mutations: {
ADD_ITEM_TO_CART(state, payload) {
//CONSOLE.LOG()'s
console.log("state.checkoutCart[0]", state.checkoutCart[0]);
// eslint-disable-next-line
console.log("state.checkoutCart[0].item", state.checkoutCart.item);
console.log("state.checkoutCart", state.checkoutCart);
//IF ITEM ALREADY IN checkoutCart, UPDATE IT'S QUANTITY
if (state.checkoutCart.includes(payload.item)) {
state.checkoutCart.quantity += payload.quantity;
console.log("Item already in cart");
}
//IF ITEM NOT IN checkoutCart, UPDATE THE QUANTITY PROPERTY AND ADD ITEM TO CART
else {
payload.item.quantity = payload.quantity;
state.checkoutCart.push(payload);
}
https://i.imgur.com/rjOOljN.png
I thought this code would work, but it ALWAYS executes the ELSE condition and adds to cart like the
if (state.checkoutCart.includes(payload.item))
isn't being recognized or working at all.
https://i.imgur.com/LLB790Z.png
VueX devtools shows the same thing. An "item" object inside an object inside an array.
I also tried:
ADD_ITEM_TO_CART(state, payload) {
console.log("add_item_to_cart"); <---ONLY PART THAT SHOWS UP IN CONSOLE.LOG() WHEN EXECUTED
//LOOP THROUGH ALL ARRAY ENTRIES TO GAIN ACCESS TO state.checkoutCart.item
for (let i = 0; i < state.checkoutCart.length; i++) {
console.log("i=", i);
console.log("state.checkoutCart.item", state.checkoutCart.item);
//IF ITEM ALREADY IN checkoutCart, UPDATE IT'S QUANTITY
if (state.checkoutCart[i].item.includes(payload.item)) {
state.checkoutCart.quantity += payload.quantity;
console.log("Item already in cart");
return;
};
}
//IF ITEM NOT IN checkoutCart, UPDATE THE QUANTITY PROPERTY AND ADD ITEM TO CART
payload.item.quantity = payload.quantity;
state.checkoutCart.push(payload);
},
because I figured I needed to loop through all the array entries. BUT the for loop doesn't even run, and with this code nothing gets added to the cart at all.
I can't figure out what I'm doing wrong here. Can somebody help? Is my syntax wrong? Or is my logic? Am I accessing the arrays/objects incorrectly? How do I write the "ADD_ITEM_TO_CART" mutator function correctly? I've literally spent all day on this and my brain is shutting down.
EDIT:
https://i.imgur.com/bkU8YSo.png
PAYLOAD
<div v-for="item in items"> <--ACTUALLY PROP FROM PARENT COMPONENT BUT SAME IDEA
<p>
Qty
<select v-model="quantity">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</p>
<p>
<button type="button" #click="addToCart()">
Add to Cart
</button>
</p>
</div>
let quantity = ref("1");
const addToCart = () => {
console.log("addToCart Running");
store.dispatch("cartAdd", { item: item.value, quantity: quantity.value });
};
That is because your if condition is not checking for what you think.
Array.prototype.includes checks if a value is in the array but there are two cases:
the value is a primary type (string, number, boolean, ...). It compares by value.
the value is an object. Then it compares by reference.
So here, you are checking if the reference of your item object is already included in the array. But it's not, since it's a new object.
Solution: check if there is an object with the same values, not reference.
You can use the some method, and you have to write a condition that checks if two items are equals.
Here is an example if your items have an id:
if (state.checkoutCart.some(item => item.id === payload.item.id))
The problem is indeed inside ADD_ITEM_TO_CART mutation.
As Kapcash has pointed out, two objects having the same properties and the same values are not the same.
In other words, .includes() checks for identity, not equality. To better understand this, consider this example:
const a = { foo: 'bar' }
const b = [{ foo: 'bar' }]
const c = [a]
const d = [{ ...a }]
console.log(b.includes(a)) // false
console.log(c.includes(a)) // true
console.log(d.includes(a)) // false
To get past this, use Kapcash's answer.
I'll just mention the standard way of dealing with this problem is using unique identifiers on objects (e.g: uuids).
Once you fix the above, it's still not going to work, because you'll run into the following problem: inside the if you're attempting to alter state.checkoutCart's quantity. And an array does not have a quantity property.
The proper logic to achieve the desired functionality is (assuming the unique identifier on checkoutCart members is item._id, from the pictures you posted):
ADD_ITEM_TO_CART(state, payload) {
// find the index of a cart element having same `item._id` value as the payload
const index = state.checkoutCart.findIndex(
(el) => el.item._id === payload.item._id
)
if (index > -1) {
// if the index is higher than `-1`, an element was found
// create a copy, update its quantity and then
// replace the original element with the copy
const copy = { ...state.checkoutChart[index] }
copy.quantity += payload.quantity
state.checkoutCart.splice(index, 1, copy)
} else {
// no element found, add payload to `checkoutCart`
state.checkoutCart.push(payload)
}
}
Side note: None of your items should contain a quantity property. That's the cart's responsibility, not the item's. By adding it to the item you end up with two sources of truth which you'd need to keep in sync. You don't want this type of problem.

count(): Parameter must be an array or an object that implements Countable (laravel getting error)

I want to enter an multiple field entered data in table with for loop
but i am getting an error in the post method.
error is
count(): Parameter must be an array or an object that implements Countable
controller code :-
$degree = $request->degree;
for($i=0;$i<count($degree);$i++){
$edu = new education;
$edu->degree = $request->degree[i];
$edu->clg = $request->clg[i];
$edu->yoc = $request->yoc[i];
$edu->save();
}
so, please suggest me what i can do.
Here not at all big problem,
you can not use count for the one value the array is required for
that i think that you have not been enterd dynamically many values it can be 0
so replace code in your controller:-
$degree = $request->degree;
if($degree > 0)
{
for($i=0;$i<count($degree);$i++){
$edu = new education;
$edu->degree = $request->degree[i];
$edu->clg = $request->clg[i];
$edu->yoc = $request->yoc[i];
$edu->save();
}
}
here i have used $degree if its value is greater then 0 that means
if it has value count grater then one then you can only go for loop and add value to database
otherwise it will be not go in for loop

For loop a number to create buttons inside template literal

Getting a bit stuck with this one.
I'm looping through an object (dataLayer.Tests) and I'm displaying the values of my content, in a DIV. Here's an example of how this object looks:
I'm doing this by looping through my object with forEach. (And in this example, I'm just console.logging my result result3).
The problem I'm having, is that within my forEach, I want to display create/display buttons, depending on what the number is in the totalVariants key/value.
So for example, if the totalVariants === 2, I want to create 2 buttons. If it is one, I want to create 1 button.
I know I need to for loop through this particular value, but I'm not sure how to do this, within a template literal.
Here's my code.
dataLayer.Tests.forEach((element, index, array) => {
let result3 = '';
let numberOfVariants = element.totalVariants;
if (numberOfVariants >= 1) {
for (i = 0; i < numberOfVariants; i++) {
console.log("The number is ", i + 1);
}
result3 += `
<div class="CRO-variant-result">
<p>Date Launched: ${element.date}</p>
<p>Test ID: ${element.id}</p>
<p>Test Description: ${element.name}</p>
<p>Variant Active: ${element.variant}</p>
<p>Total Variants: ${element.totalVariants}</p>
${(function () {
for (i = 0; i < element.totalVariants; i++) {
return `<button>${i}</button>`
}
})()}
</div>
`
console.log("result3", result3);
};
});
I've seen solutions which include .map and object.keys, but this doesn't seem to work/return anything. (Probably as I just need to loop through a number and not array etc.
Any ideas/pointers, would be appreciated.
Basically, I'm not sure how to loop through a number, within a template literal and return x number of elements.
Thanks,
Reena
numberOfVariants is an number, not an object, so one way you could do this is create a new incrementing array of that length (Array.from(Array(numberOfVariants).keys()) does this nicely) and then map over that array as you're doing.
${Array.from(Array(numberOfVariants).keys()).map(i => (
`<button value="${i}">${i}</button>`
)).join('')}
I'm not quite sure what you want to appear inside the button (maybe the integer of the current number as you increment)?

The value of one input is affecting another

I have a simple app which allows someone to add a numbers into an input, and have those numbers render onto the page (as inputs) which can be edited.
addSiblingValue(evt) {
this.setState({
currentObject: {
...this.state.currentObject,
numberOfSiblings: evt.target.value
}
});
add() {
const array = [...this.state.array, this.state.currentObject];
this.setState({
array
});
}
siblingCountChange(rowIndex, event) {
const array = [...this.state.array];
array[rowIndex].numberOfSiblings = event.target.value;
this.setState({ array });
}
So when I add a number it renders a new input with the value set to the number I've just added, but when I go to change that value, it now is affecting the first input.
The first row of inputs are using their own object currentObject which pushes to to the this.state.array, so I'm not sure why editing anything in that array would affect the currentObject?
Expected behaviour:
User enters a number into the input and clicks add
That input is rendered and can be edited independently
How do I achieve this or what is it I'm doing wrong here?
CodeSandbox
Thank you
When you add this.state.currentObject to the array, it works as an reference, so that the added object in the array and this.state.currentObject are the same object. You can prevent that by adding not the object itself, but a copy of the object into the array:
add() {
const array = [...this.state.array, {"numberOfSiblings": this.state.currentObject.numberOfSiblings}];
this.setState({
array
});
}
siblingCountChange(rowIndex, event) {
const array = [...this.state.array];
array[rowIndex].numberOfSiblings += parseInt(event.target.value);
this.setState({ array });
}
You were not adding the actual number to the current state. I also removed the value from the add like so:
<input
type="text"
onChange={this.siblingCountChange.bind(this, rowIndex)}
/>
You will need to put error handling on the state as a string plus a number leads to NaN error. As you can see the number is parsed before addition.
Thanks to dieckie for pointing me in the right direction. Unfortunately that particular solution did not work, but using Object.assign to create a reference and pushing that to the array did?
Posting here so it helps others/myself in future:
add() {
let copyOfCurrentObject = Object.assign({}, this.state.currentObject);
const array = [...this.state.array, copyOfCurrentObject];
this.setState({
array
})
}
This question was also helpful.

How to use angular filter on multiple properties of the object

I have a simple ng-repeat like that:
<input type="text" ng-model="vm.filter">
<div ng-repeat="tenant in vm.tenants | filter : vm.filter : vm.contains">
</div>
Now I want to filter tenants based on the value of the filter, e.g. to find if a name of a tenant contains the filter expression like that
function contains(actual, expected) {
return actual.name.indexOf(expected) >= 0;
}
What I do not understand is why I get a tenant.name in the contains function instead of the tenant itself. I know for a simple case I can do something like filter | {name: vm.filter} | vm.contains, but what if I want to do filtering based on multiple properties (e.g. name, phone, etc.)
What I do not understand is why I get a tenant.name in the contains
function instead of the tenant itself.
What's happening is that angular is providing the comparator all levels of the object in an attempt to do a deep filtering i.e. it attempts to match your filter to all hierarchy levels in the object. If you console.log all the values, you'll see that you get the full object too (after all the properties).
One way to make this work would be something like
...
<div ng-repeat="tenant in vm.tenants | filter : vm.contains(vm.filter)">
...
and then
...
contains: function(filter) {
return function(tenant) {
return tenant.name.indexOf(filter) !== -1;
}
}
...
Fiddle - https://jsfiddle.net/81384sd7/
You can use an object to store the filters you want to apply. For example:
$scope.filterBy = {
name: "Bob",
phone: "07"
};
Then configure fields to edit the filters:
<input type="text" ng-model="filterBy.name" />
<input type="text" ng-model="filterBy.phone" />
Then just filter by this object.
<div ng-repeat="tenant in vm.tenants | filter : filterBy">
Take a look at my previous answer which is very similar except also allows selections to be made before a filter being applied manually with a button:
Applying a angularjs filter after "Apply" button click
I hope this helps.
You'll want to test for the presence of both those conditions and return the value if both are present. Something like this:
function contains(actual, expected) {
if (actual.name.indexOf(expected) >= 0 && actual.phone.indexOf(expected) >= 0) {
return actual;
}
}

Categories

Resources