Datalist input showing options from another Vue component - html

I'm quite new with Vue, and webdev in general. Am creating a simple hiking pack list as a school project. Have a component 'PackItem' that consists of input fields for brand, model and weight. In the backend I have a database with some items that the user can pick from. When selecting a brand, the model input will show a list of models for that brand, and when a model is chosen the weight input gets filled automatically.
<template>
<div class="inputRow">
<h1> Brand: {{ this.brand }}</h1>
<input
type="text"
list="brands"
placeholder="Brand"
v-model="this.brand"
/>
<datalist id="brands">
<option
v-for="(suggested_brand, index) in getProductBrands"
:key="index"
:value="suggested_brand"/>
</datalist>
<input
type="text"
list="models"
v-model="this.model"
/>
<datalist id="models"> -->
<option
v-for="(suggested_model, index) in brandModels"
:key="index"
:value="suggested_model"/>
</datalist>
<input
class="product-inputs"
type="number"
name="Weight"
placeholder="Weight"
v-model="this.getProductWeight"
#change="this.updateItemOnStore($event.target.value);"/>
</div>
</template>
This is the component. The datalists and input gets their values from computed properties, that talk to vuex.
export default defineComponent({
name: 'PackItem',
props: {
},
data() {
return {
model: '',
brand: '',
weight: 0,
}
},
...
...
...
computed: {
getProductBrands(){
return this.$store.getters.productsBrandList
},
getProductWeight(){
let weight = this.$store.getters.productWeight(this.brand, this.model)
return weight
},
brandModels(){
if(this.brand === ''){
return []
}
console.debug('Item %d getting models for %s', this.uuid, this.brand)
let models = this.$store.getters.brandModels(this.brand)
return models
},
},
}
This all works great for the first PackItem, but when i spawn a second PackItem from the parent, the models list for the new PackItem will be the same as the first PackItem, regardless of choosing a different brand.
Image: One item spawned
Image: Two items spawned
If I change computed property brandModels() to a method, and save the returning array from the store in local data and print it, I can see that the model list looks like it should, but those values are not showing up in the list for the user?
Image: Two items spawned with printouts
I can also see in the logs that I'm getting the correct models for the selected brand.
Item 0 getting models for Hyperlite Mountain Gear
index.js:209 Hyperlite Mountain Gear models: Windrider 3400,Junction 3400
index.js:221 Got weight 907g for product Hyperlite Mountain Gear Windrider 3400
index.js:64 Creating item 1 in category 0
index.js:199 Brands list: ZPacks,Hyperlite Mountain Gear,The OMM
index.js:225 Did not find weight for product in database!
index.js:199 Brands list: ZPacks,Hyperlite Mountain Gear,The OMM
PackItem.vue:119 Item 1 getting models for ZPacks
index.js:209 ZPacks models: Arc Blast
index.js:225 Did not find weight for product ZPacks Windrider 3400 in database!
So seems to me like I'm fetching all the correct data, and as far as I can see it should be displayed in the browser, but for some reason is not. I have no idea what is going on here...
EDIT:
As suggested by #yoduh I've fetched the whole products table into the component, and do computed values there instead of the supposedly broken computed values from getters in vuex. This has unfortunately not fixed it. Can still see in logs that brandModels() creates a correct list of models, but the datalist still shows the wrong ones.
computed: {
getProductBrands(){
let brands = []
this.dbProducts.forEach(product => {
//var json = JSON.parse(product);
var brand = product.brand;
if(!brands.includes(brand)){
console.debug('Adding brand %s to brands list', brand)
brands.push(brand)
}
} )
console.log('Brands list: %s', brands)
return brands
},
brandModels(){
if(this.brand === '') {return }
let models = []
this.dbProducts.filter(
product => product.brand === this.brand)
.forEach(product => models.push(product.model)
)
console.debug('%s models: %s', this.brand, models)
return models
},
getProductWeight(){
if(this.brand === '' || this.model === ''){ return }
let product = this.dbProducts.find(
product => (
product.brand === this.brand && product.model == this.model
))
if(product){
let weight = product.weightGrams
console.debug('Got weight %dg for product %s %s', weight, this.brand, this.model)
return weight
}
console.debug('Did not find weight for product %s %s in database!', this.brand, this.model)
return 0
},
},

Turns out the actual issue was with the pair of elements <InputText> and <datalist> on every item having the same id and list values. The value for list and id connect the two elements together, but once a second item is added, another pair of <InputText> and <datalist> with the same id and list values is created. Because no two elements should ever share the same id, the connection between inputs and datalists becomes confused and broken.
The solution then is to bind unique values to the id/list attributes. I used the uuid of each item since it's a unique number:
PackItem.vue
<InputText
type="text"
:list="`models${uuid}`"
placeholder="Model"
class="p-inputtext"
v-model="this.model"
/>
<datalist :id="`models${uuid}`">
<option
v-for="(suggested_model, index) in brandModels"
:key="index"
:value="suggested_model"
/>
</datalist>
updated codesandbox

Related

Can't figure out a way to add two values from a select element in react

Scenario: I have a react component that allows users to select through different shipping rates. Once a user selects one I want the onChange to get the ID and the amount and I can only figure out how to get just the ID. The select element iterates through an array of rates that is structured like this:
rates = [ {id: 'r8f8hd8', amount: 45}, ...]
Here is the select element:
<select onChange={(e) => console.log(e.target.value)} className='w-8/12 h-14 rounded p-2'>
<option disabled selected hidden>
Pick a shipping rate
</option>
{rates.map((rate, index) => (
<option value={{ id: rate.rateId, amount: rate.amount }} key={index}>
{rate.service} {''}(${rate.amount})
</option>
))}
</select>
I tried setting the option value to an object with the rateId and amount but it just gives me a blank [Object object]
This should fix the issue. A word of advice I wouldn't set the key to just the index. Instead you should use the id that you assigned to the object.
The issue was you we're storing an object as the value. This lead to an issue because value automatically stores a string so your object is converted into a string hence [Object Object]. Mapping already sets its own pre defined object {value:someVal,key:someID}.
Taking your current case into account I believe this is a cleaner way to achieve what you wanted. I've added working code below. Also the extra item in rates was just for testing.
Feel free to comment any questions you have and i'll do my best to answer them.
let rates = [ {id: 'r8f8hd8', amount: 45},{id: 'r8f8hd', amount: 450}]
return (
<select onChange={(e) => console.log(e.target.value)} className='w-8/12 h-14 rounded p-2'>
<option disabled selected hidden>
Pick a shipping rate
</option>
{rates.map((rate) => (
<option value={rate.amount } key={rate.id}>
{rate.service} {''}(${rate.amount})
</option>
))}
</select>
)
Just incase your just dead set on storing multiple values here's a solution to that as well but I would go as far as saying this is just bad code but it does what you wanted. Since map has its own object as I mentioned we can mutate it and give it more characteristics hence id and trueVal that I added. We can use the index and store that as our value and instead of accessing our data through e.target.value we can instead utilize the e.target.options and simply cross reference the index value we stored to make sure we are always accessing the correct position in the array. However please take note of the +1 this is to take the first position into account which is held by the, "Pick a shipping rate".
let rates = [ {id: 'r8f8hd8', amount: 45},{id: 'r8f8hd', amount: 450}]
return (
<select onChange={(e) => console.log(e.target.options[e.target.value])} className='w-8/12 h-14 rounded p-2'>
<option disabled selected hidden>
Pick a shipping rate
</option>
{rates.map((rate, index) => (
<option value={index+1} trueVal={rate.amount} id={rate.id} key={index}>
{rate.service} {''}(${rate.amount})
</option>
))}
</select>
)
}
In html value prop is a string, so value={{ id: rate.rateId, amount: rate.amount }} is converted to string, that's why you are getting [Object object] .
Solution : change your component like this :
.....
const [option, setOption] = React.useState({});
const handlechange = (e)=>{
for (let i = 0; i < rates.length; i++) {
if (e.target.value === rates[i].id) {
setOption({ id: rates[i].id, amount: rates[i].amount });
}
}
}
return (
<select onChange={handlechange} className="w-8/12 h-14 rounded p-2">
<option disabled selected hidden>
Pick a shipping rate
</option>
{rates.map((rate, index) => (
<option value={rate.id} key={index}>
{rate.service} {""}(${rate.amount})
</option>
))}
</select>
);
Now your id and amount of selected option will be stored in option state
this is a demo in codesandbox

How to iterate two separate arrays in handlebars Nodejs with {{#each}}?

I am creating a Risk Management System using Nodejs and Express with Handlebar views. Using SQL, I am extracting two queries in my GET router and rendering them to the handlebar view. This is how it looks:
const parametro = await pool.query('select * from parametro where parid = ?', [parid]);
const tipoParametros = await pool.query('select distinct tipoparid from tipoparametro order by tipoparid');
res.render('parametros/modificar', {parametro, tipoParametros});
I consoled logged both arrays and they look like these:
- parametro: [
RowDataPacket {
parid: 22,
parcodigo: 'TEST',
tipoparid: 11,
pardescripcion: 'Just a Test',
parexplicacion: 'Test',
parclasificacion: 3,
parvalor: 'Test 1',
parusrcreaid: null,
parfechacrea: 2022-06-25T21:58:19.000Z,
parusrmodid: null,
parfechamod: null
}
]
- tipoParametros: [ RowDataPacket { tipoparcodigo: 'SYS', tipoparid: 11 } ]
However, since my POST for this view needs :id, I am iterating with each parametro because that's how I keep track of each row in my database, with parid. The .hbs view looks like this:
{{#each parametro}}
<form class = "row g-3 needs-validation" method = "POST" action = "/parametros/modificar/{{this.parid}}" novalidate>
{{> formparametro}}
</form>
{{/each}}
and inside "formparametro" there is a form-floating section where I try to iterate with each tipoParametro but nothing is coming out. Looks like this:
<div class="col-6">
<div class="form-floating">
<select class="form-select" id="floatingSelect" name="tipoparid" aria-label="Floating label select example" required>
{{#each tipoParametros}}
<option value={{this.tipoparid}}>{{this.tipoparcodigo}}</option>
{{/each}}
</select>
<label for="floatingSelect">Tipo Parametro</label>
</div>
</div>
How can I have access to the query and columns of tipoParametro?
As your formparametro partial is rendered within your {{#each parametro}} block, the context for each time the partial is rendered is the currently iterated item from parametro. Therefore, the partial has no access to tipoParametros.
What we want to do is provide a context to our partial that does contain tipoParametros. This is very simple. The Handlebars documentation covers Partial Contexts and it states that a context can be supplied to the partial as the second parameter within the mustache brackets, as in:
{{> myPartial myOtherContext }}
As your tipoParametros object belongs to your root context, I would recommend using the #root data variable to pass the root context to your partial. The line in your template invoking your partial thus becomes:
{{> formparametro #root}}
I have created a fiddle for your reference.

V-for from a nested array

I'm starting with vue.js and I need to populate selects with v-for that are inside another v-for.
I was reading questions about this subject but could not find a way to make it works in my case.
I have a nested array (tours) with title and description and I have a v-for inside another one to populate a select with tours' title:
html:
<div class="row" id="app">
<div v-for="(item, index) in hosters" v-bind:key="item.id" class="col-md-6 mb-50">
<h4 class="mb-0">{{ item.name }} {{ item.lastname }}</h4>
<div class="tour-options-select">
<select id="select-suggestions" name="tour-options-dropdown" class="tour-options-dropdown">
<option v-for="tour in tours">{{ tour.title }}</option>
</select>
</div>
</div>
</div>
vue.js:
let app = new Vue({
el: '#app',
data: {
city: 'mycity',
hosters: null,
tours: [],
title: [],
},
created: function () {
this.searchHoster()
},
methods: {
searchHoster: function () {
axios.post('searchHoster.php', { "city": this.city }).then((response) => {
this.hosters = response.data.hosters;
console.log(this.hosters);
this.tours = this.hosters.map(res => res.tours);
console.log(this.tours);
this.title = this.tours.map(res => res.title);
console.log(this.title);
}).catch((error) => {
console.log(error);
});
},
}
})
I’m getting undefined in console.log(this.title); line so i tried to use:
this.title = response.data.hosters.map(hoster => hoster.tours).flat().map(item => item.title);
but it gives me a simple array with all the titles of all users. So, all selects are populated with the same titles for everyone. Any tip about how to make it works?
Change tours to item.tours on second v-for:
<div v-for="(item, index) in hosters" v-bind:key="item.id" class="col-md-6 mb-50">
<h4 class="mb-0">{{ item.name }} {{ item.lastname }}</h4>
<div class="tour-options-select">
<select id="select-suggestions" name="tour-options-dropdown" class="tour-options-dropdown">
<option v-for="tour in item.tours">{{ tour.title }}</option>
</select>
</div>
</div>
Do not break out the various pieces of data you are interested in rendering. Instead, render the inner portions using the index value from the outer v-for. For example, assuming your data looks like the data you depicted, then...
V-for="item in data"
Render host info using item.name, etc.
v-for="tour in item.tours"
Render title and description from tour.title, etc.
Much faster and easier. I might also suggest using an accordion type control as well - render a table of all the tours, and allow user to select the desired row by check box (which would then display further details in a details area). - that way they can see all the options easily. Make the items collapsible using #click to toggle a boolean value which controls show=boolean on a nested div.
Good luck.
<div class="tour-options-select">
<select id="select-suggestions" name="tour-options-dropdown" class="tour-options-dropdown">
<option v-for="tour in ̶t̶o̶u̶r̶ item.tours">{{ tour.title }}</option>
</select>
</div>
Change the tours to item.tours
Since you are already iterating through the first v-for the context for the 2nd v-for is "item". and item.tours would give you the proper object to iterate over.

Join Not Displaying Selected Items - Using Svelte

What this code does: If no flavours are selected, "Please select at least one flavour" & "You ordered and undefined is displayed". Then if a flavour is selected, nothing is displayed.
What I'm trying to do: If no flavours are selected, "Please select at least one flavour" is displayed. Then only if something is selected, I want to display "You ordered (then list whatever they ordered)" ie: You ordered Mint choc chip, cookies and cream".
I'm confused because I thought that ${flavours.slice(0, -1).join(', ')}; & You ordered {join(flavours)} would list the flavours selected. This is the code that I'm playing around with. (credit: Svelte example from Svelte website)
<script>
let scoops = 1;
let flavours = ['Mint choc chip'];
let menu = [
'Cookies and cream',
'Mint choc chip',
'Raspberry ripple'
];
function join(flavours) {
if (flavours.length === 1) return flavours[0];
return `${flavours.slice(0, -1).join(', ')};
}
</script>
<h2>Flavours</h2>
{#each menu as flavour}
<label>
<input type=checkbox bind:group={flavours} value={flavour}>
{flavour}
</label>
{/each}
{#if flavours.length === 0}
<p>Please select at least one flavour</p>
<p>
You ordered {join(flavours)}
</p>
{/if}
You've got a few mistakes...
You're missing a quote (but that's a syntax error, I guess you would have noticed if you have this in your original code because it wouldn't work at all):
return `${flavours.slice(0, -1).join(', ')};
// fixed:
return `${flavours.slice(0, -1).join(', ')}`;
// you don't need the quotes here, anyway:
return flavours.slice(0, -1).join(', ');
You're also missing a else, that's why your code display nothing when some values are selected:
{#if flavours.length === 0}
<p>Please select at least one flavour</p>
{:else} <-- HERE -->
<p>
You ordered {join(flavours)}
</p>
{/if}
And, finally, why the slice? It drops the last value, is that really what you want?
// fixed
return flavours.join(', ');

Issue when trying to populate a drop-down with database values

I am using the following HTML to show a selected drop down with values from the database and rest of the others in the list. It shows the selected name correctly but the selected is also displayed in the list.
How to remove the second time show selected name in the drop down list? Is this a good way to use drop down menu? Here Jobcategory and Jobdetails are associated.
Im using Laravel 4.2 and this is the HTML:
// view drop down form to save data
<div class="large-6 columns">
<label>Job Category
<select name="category[]">
<option value="">Select Category</option>
#foreach(JobCategory::all() as $jcat)
<option value="{{ $jcat->id }}">{{ $jcat->name }}</option>
#endforeach
</select>
</label>
</div>
// Edit drop down form to update the selected value
<div class="large-6 columns">
<label>Job Category
<select name="category[]">
<option value="{{$jobedit->jobcategory->id}}">{{$jobedit->jobcategory->name </option>
#foreach(JobCategory::all() as $jcat)
<option value="{{ $jcat->id }}">{{ $jcat->name }}</option>
#endforeach
</select>
</label>
</div>
// database table for jobcategories
id | name
1 | Accounting/Finance
2 | Advertisement/Event Mgt.
3 | .....
// after save into jobdetails table
id | jobcategory_id | .......
1 | 5 | ...
I can retrieve the value of jobcategory in the edit form but it shows twice one in the selected value and other in the listed value of all jobcategory. This is the problem and i want only show the selected value and then rest of the others from jobcategory table without duplicate value of selected in the drop down. plz help.
// controller to to edit
public function getJobEdit($id)
{
$jobedit = JobDetail::find($id);
return View::make('employers.edit_single_jobs')->with('jobedit', $jobedit);
}
// JobDetail --model
public function jobcategory()
{
return $this->belongsTo('JobCategory');
}
// JobCategory --model
public function jobdetails()
{
return $this->hasMany('JobDetail', 'jobcategories');
}
Have a look at the Forms & HTML helper of laravel.
Generating A Drop-Down List With Selected Default
echo Form::select('size', array('L' => 'Large', 'S' => 'Small'), 'S');
where the first argument is the name of the select box. The second argument is an array of all entries in the box and the last argument determines which of the array elements is the selected one.
in your case it could look something like this:
{{ Form::select(
'categoryName',
array(
'Accounting/Finance' => 'Accounting/Finance',
'Advertisement/Event Mgt.' => 'Advertisement/Event Mgt.',
// to be continued ...
),
'$cat->category_name'
); }}
//Edit
<div class="large-6 columns">
<label>Job Category
{{ Form::select('category[]', ['' => 'Select a category'] + $all_categories, $jobedit->jobcategory->id) }}
</select>
</label>
</div>
where $all_categories should be an array of all categories as in the first example. You can get this from JobCategory::all().
This is how I set up drop-downs in my projects. I prepare the data in my controller (you'd obviously need a route and controller set up for this, I'm assuming you've done that):
Controller
public function index()
{
$categories = \Category::lists('name', 'id')->orderBy('name'); // assuming you have a Category model
view('categories', compact('categories'));
}
Then you can use the Forms and HTML helper as Peh mentioned, this isn't available as a default in Laravel 5 so you'd need to add it into your project using composer. To do so either run composer install illuminate/html or add "illuminate/html": "~5.0" to your composer.json file and then run composer install. You'd then need to add 'Illuminate\Html\HtmlServiceProvider' to config/app.php in the providers array then add 'Form' => 'Illuminate\Html\FormFacade' and 'HTML' => 'Illuminate\Html\HtmlFacade' to the aliases array in the same file. Once that's sorted you can use the Form and HTML helper in your view, as so:
View
{{ Form::select('categoryName', ['' => 'Select a category'] + $categories) }}
Your view would need to be saved as filename.blade.php if using the handlebars, otherwise echo it within <?php ?> tags.
Hope that helps you on your way.