Vue loop array objects into form checkbox - javascript

I'm attempting to create an 'edit listing' page where someone's submitted information is displayed back. I'm a bit stuck on how to populate checkboxes in a form with a check on the boxes that were selected the first time.
I'm aware that checkboxes look for false/true in order to display a check, but my array of something like: [x,y,z] is displayed as just [true] or [false] which leads to all boxes being checked at once and vice versa when using v-model.
The form
<input
type="checkbox"
id="Set Photographer"
value="Set Photographer"
v-model="returnedListing[0].crew_positions"
/>
<label for="Set Photographer">Set Photographer</label>
<input
type="checkbox"
id="Producer"
value="Producer"
v-model="returnedListing[0].crew_positions"
/>
<label for="Producer">Producer</label>
<input
type="checkbox"
id="Production Designer"
value="Production Designer"
v-model="returnedListing[0].crew_positions"
/>
<label for="Production Designer">Production Designer</label>
returnedListing
const [actors, returnedListing] = await Promise.all([
$axios.$get(`/api/v1/actors/`, {
params: {
user: body
}
}),
$axios.$get(`/api/v1/listings/`, {
params: {
random_public_id: params.id
}
})
]);
return { actors, returnedListing };
Dummy API object
{
"id": 15,
"title": "NUmber 15",
"start_date": "2021-03-04",
"end_date": "2021-02-16",
"location": "The Bronx",
"overview": "sdfds",
"studio": "sdfdsf",
"poster": null,
"crew_positions": "Set Photographer, Producer, Production Designer",
"post_production_positions": "Editing, AD",
"random_public_id": null,
"date_submitted": null,
"user": 1
}
Essentially I'm looking to figure out how to loop through returnedListing[0].crew_positions if it's value is ['Set Photographer', 'Producer'] and have those 2 boxes checked while 'Production Designer' remains unchecked.

The first problem (as mentioned in the comments) that the crew_positions is not an array, but a comma-separated string. Then you can iterate over them & set the checkboxes.
const returnedListingArray = [{
"id": 15,
"title": "NUmber 15",
"start_date": "2021-03-04",
"end_date": "2021-02-16",
"location": "The Bronx",
"overview": "sdfds",
"studio": "sdfdsf",
"poster": null,
"crew_positions": "Set Photographer, Producer, Production Designer",
"post_production_positions": "Editing, AD",
"random_public_id": null,
"date_submitted": null,
"user": 1
},
{
"id": 16,
"title": "NUmber 16",
"start_date": "2021-03-04",
"end_date": "2021-02-16",
"location": "The Bronx",
"overview": "sdfds",
"studio": "sdfdsf",
"poster": null,
"crew_positions": "Set Photographer, Production Designer",
"post_production_positions": "Editing, AD",
"random_public_id": null,
"date_submitted": null,
"user": 1
}
]
Vue.component("CrewPositionInput", {
props: ["id", "crewPosition", "checked"],
methods: {
handleCbClick() {
this.$emit("update:position-status", this.crewPosition)
},
},
template: `
<label
:for="id"
>
<input
type="checkbox"
:id="id"
:value="crewPosition"
:checked="checked"
#click="handleCbClick"
/>
{{ crewPosition }}
</label>
`
})
Vue.component("CrewPositions", {
props: ["id", "possibleCrewPositions", "crewPositions"],
methods: {
toggleCrew({
crew
}) {
const positions = this.crewPositions.includes(crew) ?
this.crewPositions.filter(item => item !== crew) : [...this.crewPositions, crew]
this.$emit("update:crewPositions", positions)
},
},
template: `
<div>
<crew-position-input
v-for="position in possibleCrewPositions"
:key="position + id"
:id="position + id"
:crew-position="position"
:checked="crewPositions.includes(position)"
#update:position-status="(crew) => toggleCrew({ crew })"
/>
</div>
`
})
new Vue({
el: "#app",
data() {
return {
returnedListings: [],
possibleCrewPositions: [],
}
},
mounted() {
this.returnedListings = returnedListingArray.map(({
crew_positions,
id,
// ...rest // commenting out - this is just a snippet, no need for large objects
}) => ({
id,
crew_positions: crew_positions.split(", "), // splitting string to array
}))
// just to make it a little more dynamic:
this.possibleCrewPositions = [...new Set(this.returnedListings.reduce((a, c) => {
return [...a, ...c["crew_positions"]]
}, []))]
},
template: `
<div>
<crew-positions
v-for="listing in returnedListings"
:key="listing.id"
:id="listing.id"
:possible-crew-positions="possibleCrewPositions"
:crew-positions.sync="listing['crew_positions']"
/>
{{ returnedListings }}
</div>
`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
SHORT EXPLANATION
CrewPositionInput is a stateless component that accepts an id, a crewPosition & a checked prop from its parent. The only thing it does is that on click it emits a custom event with the this.crewPosition as payload.
The CrewPositions component is actually a list of CrewPositionInput components, that passes down props to its children and handles the update:position-status custom event coming from them. On any update:position-status custom event, it re-emits an array (that is actually a crewPositions array) to its parent.
The topmost component handles data processing (like crew_positions splitting) & state management (like updating the crew_positions in the stored array of objects (returnedListings). The update is done via .sync (more on sync here) - this is a handy method of doing it, but it has its constraints (like naming of the variables & events must follow. a certain pattern).
possibleCrewPositions is just a way of creating the available checkboxes based on the data source (dynamically).

Related

Updating an Object in a multistep form deletes priors entries

This is the set up:
In App.js i have routes for each step. i have a main object in which im updating values as the steps progress using props.
Here is my object:
const [postData, setPostData2] = useState({
'meta': {
"originally_created": todaysDate,
"user_agent": navigator.userAgent,
"ip_address": ip,
"tcpa_compliant": true,
"tcpa_consent_text": "By clicking Get My Free Quote below, I am agreeing to receive text messages from InsurTech Groups and business partners. I provide my signature expressly consenting to recurring contact from InsurTech Groups or its business partners at the number I provided regarding products or services via live, automated or prerecorded telephone call, text message, or email. I understand that my telephone company may impose charges on me for these contacts, and I am not required to enter into this agreement as a condition of purchasing property, goods, or services. I understand that I can revoke this consent at any time. Terms & conditions & Privacy policy apply.",
"landing_page_url": ""
},
"contact": {
"first_name": "",
"last_name": "",
"email": "",
"phone": "",
"address": "",
"city": "",
"state": "",
"zip_code": "",
"ip_address": ip,
},
"data": {
"drivers": [
{
"first_name": "",
"last_name": "",
"birth_date": "",
"gender": "",
},
{
"first_name": "",
"last_name": "",
"birth_date": "",
"gender": "",
}
],
"vehicles": [{
"year": "",
"make": "",
"model": "",
},
{
"year": "",
"make": "",
"model": "",
}
],
"requested_policy": {
"coverage_type": "",
},
"current_policy": {
"insurance_company": "",
}
}
});
Here is some logic so i can update the object with props:
useEffect(() => {
const stringifiedData = sessionStorage.getItem('main-form-data')
if (stringifiedData) {
const jsonData = JSON.parse(stringifiedData);
setPostData(jsonData);
}
}, []);
useEffect(() => {
sessionStorage.setItem('main-form-data', JSON.stringify(postData));
}, [JSON.stringify(postData)]);
const setPostData = (obj) => {
console.log('in app state', obj);
setPostData2(obj)
}
const setPostDataForPage = (data) => {
setPostData({ ...postData, ...data });
}
and this is how im passing it into each route:
<Route
path="/car-year"
element={<CarYear setPostData={setPostDataForPage} />}
/>
<Route
path="/car-make"
element={<CarMake setPostData={setPostDataForPage} />}
/>
<Route
path="/car-model"
element={<CarModel setPostData={setPostDataForPage} />}
/>
So here is the issue:
As i updated Car Year, Car Make & Car Model it updates in the console log as it supposed to but everytime it console log it completely overwrites the prior object.
Example:
YEAR STEP:
....rest of console log from object
vehicels: year: 1991
MAKE STEP:
....rest of console log from object
vehicels: make: Honda
as you can see it just over writes the year and replaces it with just the make
Here is how im updating in each step:
CAR YEAR PROPS UPDATE:
props.setPostData({
vehicles:
{
year: year,
}
})
CAR MAKE PROPS UPDATE:
props.setPostData({
vehicles: [
{
make: make,
}
]
})
Why does it do this and how can i fix it?
Here is a *bonus question lol, if i have two vehicles how can i make sure that when the person enters a second vehicle it doenst overwrite the first vehicle data!
I know this one is super long and thank you in advance!!!! <3
You're spreading the original state in the setter. React won't mutate the state synchronously, so you end calling all 3 setters on the original state.
To use the actual state, pass a callback to the state setter with the previous state as the parameter.
const setPostDataForPage = (data) => {
setPostData(prevData => { ...prevData, ...data });
}

VueJS: JSON objects are not showing in my code

I have API that stores JSON data as shown in JSON body below... I wanted to show the data amount stored in installments but it didn't work good because its showing me each amount value two times and I couldn't figure out the problem here.
{
"response": [{
"floors": [{
"flats": [{
"status": "sold",
"price": "150000",
"currency": "USD",
"end_date": "Not Set",
"buyer": "ella",
"buyer_phone_number": "002822128",
"receipt_number_field": "553108012022",
"size_unit": "M",
"_id": "61d9b61397e87e39832a5abb",
"flat_number": 1,
"description": "This is a newly created flat.",
"city": "NY",
"payment": {
"installment_payment": {
"installments": [{
"amount": "1344",
"date": "2022-01-13",
"is_paid": false
},
{
"amount": "444",
"date": "2022-01-24",
"is_paid": false
},
{
"amount": "44444",
"date": "2022-01-17",
"is_paid": false
}
],
"remaining": "150000"
},
"paid_amount": "1234"
},
"floor": "61d9b61397e87e39832a5aba",
"building": "61d9b61397e87e39832a5ab9",
"size": "176.25",
"directions": " south",
"createdAt": "2022-01-08T16:04:43.557Z",
"updatedAt": "2022-01-08T16:22:29.220Z",
"__v": 0
},
my code:
<div v-for="(flat,index) in Flats" :key="index">
<div v-for="(find,indexT) in flat.payment" :key="indexT" >
<div v-if="flat.payment.installment_payment">
<div v-for="(find,indexT) in flat.payment.installment_payment.installments" :key="indexT">
<div v-if="find.amount >0">
<p> {{find.amount}}$ amount </p>
</div>
</div>
</div>
</div>
</div>
p.S: I stored my API data in array Flats
This will probably work, but it's untested.
You generally do not want to use v-if inside of v-for; instead, you should filter the data first and use the result in the v-for loop. [reference]
Also, since each flat has an _id field, you can use that instead of the index for the top level :key attribute.
<div v-for="flat in flatsWithPayments" :key="flat._id">
<div v-for="(installment, index) in getInstallmentsWithPaymentGTZero(flat.payment.installment_payment.installments)" :key="index">
<p> {{installment.amount}}$ amount </p>
</div>
</div>
Obviously, replace Flats with your data, but also note that in order to compare the payment amount, it needs to be converted with either Number(), parseInt() or parseFloat()
// Flats = { ... }
export default {
computed: {
flatsWithPayments() {
return Flats.filter(f => f.payment != undefined)
}
},
methods: {
getInstallmentsWithPaymentGTZero(installments) {
return installments.filter(i => Number(i.amount) > 0)
}
}
}

How to loop through and display API data containing a JavaScript Object

Live CodeSandbox link.
I'm trying to access and pull in data from an API, specifically the price text value below:
"price": {
"currency": "CAD",
"text": "500"
},
JS code (everything else pulls in fine, just the <p>${product.price.text}</p> I'm having trouble with):
// Fetch Data
async function getData() {
const res = await fetch(url);
const data = await res.json();
let output = "";
// Loop through first 'groups' array
data.groups.map(function (group) {
// Loop through each 'equipments' array
group.equipments.map((product) => {
// Define below variable to match cat products only
const catProducts =
product["dealer-name"] === "CATERPILLAR FINANCIAL SERVICES CORPORATION";
// If the dealer name is everything but cat products (aka only battlefield products)..
if (!catProducts) {
// Loop through each 'photos' array
product.photos.map(() => {
// Then output the data
// If year is undefined, replace with empty string
output += `
<div class="card">
<img class="img-fluid" src=${product.photos[0].text} alt=${
product.model
} />
<div class="card--body">
<h3>${product.year ?? ""} ${product.manufacturer} ${
product.model ?? ""
}</h3>
<p>${product.city ?? "City Not Available"}, ${product.state}</p>
<p>${product.hours} hours</p>
<p>${product.price.text}</p> <--- Not working
<a href='https://used.ca/en/${product["group-code"]}/${
product["serial-number"]
}' class="btn btn-primary">View Details</a>
</div>
</div>
`;
});
}
});
});
// Add to slider
$(".used-slider").slick("slickAdd", output);
}
getData();
Currently throwing a console error: "app.js:26 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'text')"
API structure:
{
"version": "5",
"company-code": "N001",
"language-code": "en-CA",
"total-count": 148,
"created-date": "2021-09-22T18:12:03.2387128+00:00",
"template-identifier": "4da31196-7f4b-4529-b832-90d40ef4a024",
"group-type-code": "ProductFamilyCategory",
"groups": [
{
"group-code": "Backhoe Loaders - Chargeuses-pelleteuses",
"group-name": "Backhoe Loaders",
"group-display-name": "Backhoe Loaders",
"count": 7,
"equipments": [
{
"id": "4536522",
"dealer-name": "DEALER NAME",
"GroupName": "Compact Track Loader",
"product-family-code": "CTL",
"product-family": "COMPACT TRACK LOADER",
"product-family-display-name": "Compact Track Loader",
"manufacturer-code": "CAT",
"manufacturer": "MANUFACTURER",
"model": "RUBBER TRACKS FOR CTL 259D ",
"serial-number": "XXXXX",
"year": "2016",
"hours": 0,
"city": "Ville St-laurent, Montréal",
"state": "QC",
"certification": "None",
"availability": "Available",
"price": {
"currency": "CAD",
"text": "500"
},
"product-family-categories": {},
"photos": [
{
"text": "https://s7d2.scene7.com/is/image/CatUsedProduction/wtk?JHNyYz04ZjRjN2UyYzJkMzFmZWNjY2NiZDQ1MTc2NTA4MGY3MiYkdHh0PUJBVFRMRUZJRUxEJTIwRVFVSVBNRU5UJTIwUkVOVEFMUyUyMCUyOFFVJUMzJTg5QkVDJTI5JjUxMTY2"
}
]
}
]
}
]
}
Anyone know why I'm unable to access the price text value but can access all the others?
The error implies that some products don't have a price property. You need to check for this before trying to access the text property. You can display a default placeholder instead.
You can use optional chaining to simplify this.
<p>${product.price?.text || "unknown"}</p> <--- Not working

How can I get access to the data entered by the user in these v-text-fields in VueJS?

I have an array called reducedGroups.
In response to Javas commetn below I have restructured this array so that it is now an Array of Objects, the number of Objects contained will always differ.
It may look something like this:
[
{ "0": { "value": "ccc" }, "1": { "value": "aaa" }, "2": { "value": "ddd" }, "3": { "value": "bbb" } },
{ "0": { "value": "eeee" } },
{ "0": { "value": "fff" } },
{ "0": { "value": "ggg" } }
]
The code below arranges the internal arrays into groups and a Vuetify Text Field is shown above which allows the user to name each group.
<div v-for="(group, index) in reducedGroups" :key="index">
<v-flex>
<v-text-field label="Group Name" />
<div
v-for="item in group"
:key="item.value"
>{{ item.value}}</div>
<div>
<v-btn icon color="error" #click="removeGroup(index)">
<v-icon>mdi-trash-can</v-icon>
</v-btn>
</div>
</v-flex>
</div>
The output of this is shown below.
I have 2 questions:
1) How can I know when the user has given each group a name? - this will be used to trigger the trash cans to appear on screen
2) How can I then get access to the names of the groups that have been given by the user? - Once the user has removed the least relevant groups and just 3 remain, I want to print to screen the names of the remaining groups.
UPDATE: Restructured the data in response to Javas
I'm not sure your data structure is appropriate for the model, even after refactoring into objects. It seems like what you really have is an array of groups. And each group has (a) a name (initially empty), and (b) an array of values. If that's the case, a natural structure for your data would be something like
[
{ name: "", values: ["ccc", "aaa", "ddd", "bbb"] },
{ name: "", values: ["eeee"] },
{ name: "", values: ["fff"] },
{ name: "", values: ["ggg"] }
]
With that structure, the template becomes more straightforward. (I don't know the details of <v-text-field /> but assuming it's the same as a standard <textarea />:
<div v-for="(group, index) in reducedGroups" :key="index">
<v-flex>
<v-text-field label="Group Name" v-model="group.name"/>
<div
v-for="value in group.values"
:key="value"
>{{value}}</div>
<div>
<v-btn icon color="error" #click="removeGroup(index)">
<v-icon>mdi-trash-can</v-icon>
</v-btn>
</div>
</v-flex>
</div>
Edited to Add
In response to the comments, use computed properties to extract the names and to check for any empty
computed: {
groupNames() {
return reducedGroups.map(group => group.name);
},
allNamesPresent() {
return reducedGroups.every(group => group.name);
}
}
I might also suggest that working through a basic online Vue tutorial might be helpful to you.

Mapping and binding nested objects and arrays

I have an object and within this object I have items and one of the items is an array which also contains objects. A sample of the data is shown below.
I am using knockout to bind this data to the view so I think I need to implement a double loop for returning the objects and the objects within the child array to be able to bind them in the view.
Sample data:
"singers": {
"ijiyt6ih": {
"id": ObjectId('ijiyt6ih'),
"name": "John",
"songs": [
{
"id": ObjectId('okoiu8yi'),
"songName": "Hello There",
"year": "1980"
},
{
"id": ObjectId('sewfd323'),
"songName": "No More",
"year": "1983"
}
]
},
"98usd96w": {
"id": ObjectId('98usd96w'),
"name": "Jack",
"songs": [
{
"id": ObjectId('iew342o3'),
"songName": "Hurry Up",
"year": "1985"
}
]
}
}
I need to find a way to appropriately loop through this so that I can modify the returned data to bind it to the viewModel using knockout.
Here is how my viewModel looks like:
singersViewModel = function(data) {
var self = {
singerId: ko.observable(data.id),
singerName: ko.observable(data.name),
songName: ko.observable(...),
songYear: ko.observable(...)
};
I am not sure if I will have to return two different sets of data or not.
As for the looping. I was able to loop and return the list of singers to display on the page but I am not able to get the list of songs displayed within each singer.
Here is my loop so far:
var self = {},
singer,
tempSingers = [];
self.singers = ko.observableArray([]);
for (singer in singers) {
if (singers.hasOwnProperty(singer)) {
tempSingers.push(new singersViewModel(singers[singer]));
}
}
self.singers(tempSingers);
I tried to duplicate the same type of loop for songs within this loop but i would get an error using hasOwnProperty because songs is an array.
In the included snippet you can see how you can map the original data to a viewmodel that can be bound to a view.
I've left the ids as regular properties, and converted the names into observables, so thatthey can be edited. At the bottom you can see the current viewmodel state.
There is also a sample view which iterates the list of singers, and also the list of song within each singer.
As you can see I'm implementing the solution using mapping. For mapping you need to implement a callback that receives each original object and returns a new one with a new structure. For example this part of the code
_.map(_singers, function(singer) {
return {
id: singer.id,
name: ko.observable(singer.name),
// ... songs:
})
iterates over each singer (the sample data in the question), and for each one creates a new object with the id, an observable which includes the name (and the mapping of songs, which I don't show in this fragment).
NOTE: I'm using lodash, but many browsers support map natively as an array function
var ObjectId = function (id) { return id; }
var singers = {
"ijiyt6ih": {
"id": ObjectId('ijiyt6ih'),
"name": "John",
"songs": [
{
"id": ObjectId('okoiu8yi'),
"songName": "Hello There",
"year": "1980"
},
{
"id": ObjectId('sewfd323'),
"songName": "No More",
"year": "1983"
}
]
},
"98usd96w": {
"id": ObjectId('98usd96w'),
"name": "Jack",
"songs": [
{
"id": ObjectId('iew342o3'),
"songName": "Hurry Up",
"year": "1985"
}
]
}
};
var SingersVm = function(_singers) {
var self = this;
self.singers = _.map(_singers, function(singer) {
return {
id: singer.id,
name: ko.observable(singer.name),
songs: _.map(singer.songs, function(song) {
return {
name: ko.observable(song.songName),
id: song.id
};
})
};
});
return self;
};
var vm = new SingersVm(singers);
//console.log(vm);
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div data-bind="foreach: singers">
<div>
<input data-bind="value: name"/> (<span data-bind="text: id"></span>)
<ul data-bind="foreach:songs">
<li>
<input data-bind="value: name"/> (<span data-bind="text: id"></span>)
</li>
</ul>
</div>
</div>
<pre data-bind="html: ko.toJSON($root,null,2)">
</pre>

Categories

Resources