Multiple <options> inside of logical && operator - javascript

I'm trying to create a dependent select box that has different listed depending on a parent select. I'm assuming that the code is failing because the options need to be wrapped inside of a container, but I'm not sure what container they can be wrapped in if I do not want to recreate the for each state.
To be honest, I'm not even sure this is the best way to go about it. I'm still very new to React and JS in general. This company has locations in more than two states as well, so perhaps there is a better way to scale this? Thank you
My code so far:
<select
className="select"
name="selectCity"
onChange={this.onCityChange}
>
<option value="" defaultValue>Select a City</option>
{
this.state.state === 'california' &&
<option value="los-angeles">Los Angeles</option>
<option value="san-diego">San Diego</option>
}
{
this.state.state === 'texas' &&
<option value="austin">austin</option>
<option value="dallas">Dallas</option>
}
</select>

You're right, even if you could have multiple elements without a parent element, that's still less than optimal for many reasons.
It's better to have a proper data structure and loop through the data set. For example:
const cities = {
california: [
{ value: "los-angeles", name: "Los Angeles" },
{ value: "san-diego", name: "San Diego" }
],
texas: [
{ value: "austin", name: "Austin" },
{ value: "dallas", name: "Dallas" }
]
// and so on
};
<select
className="select"
name="selectCity"
onChange={this.onCityChange}
>
<option value="" defaultValue>Select a City</option>
{cities[this.state.state].map( city => <option value={city.value}>{city.name}</option> )}
</select>

You can use React.Fragment to group elements together when you don't want a wrapping element in the DOM.
<select className="select" name="selectCity" onChange={this.onCityChange}>
<option value="" defaultValue>
Select a City
</option>
{this.state.state === "california" && (
<React.Fragment>
<option value="los-angeles">Los Angeles</option>
<option value="san-diego">San Diego</option>
</React.Fragment>
)}
{this.state.state === "texas" && (
<React.Fragment>
<option value="austin">austin</option>
<option value="dallas">Dallas</option>
</React.Fragment>
)}
</select>

There are a few approaches you could take here. Separation of concerns makes your code more readable and maintainable later. Not seeing the rest of your code, I'm not sure exactly how I would plug into your component. What do you think of something like
const cities = {
'california': {
'los-angeles': 'Los Angeles',
'san-diego': 'San Diego',
},
'texas': {
'austin': 'Austin',
'dallas': 'Dallas',
},
};
const getOptions = (state) => {
const cities = Object.keys(cities[state]);
return cities.map(city => <option value={city}>{cities[city]}</option>);
}
<select
className="select"
name="selectCity"
onChange={this.onCityChange}
>
<option value="" defaultValue>Select a City</option>
{
getOptions(this.state.state);
}
</select>

Related

Vue - Disable dropdown if other dropdown is selected

I have two dropdown menus and I want the user to only be able to select an option if the other is not selected. I'm converting from Razor pages to Vue so I need to do it in Vue.
This is what the Razor page looks like:
And this is what the Vue page I made currently looks like:
I've got it to sort of work - but I just want it to be temporarily disabled if one of the dropdowns is selected, not removed entirely.
Here's the fiddle
If I'm not wrong the example below will solve the problem. Also check here for demo
You can feel free to make selection from both dropdowns. If one is selected other one will be deselected.
<template>
<select #change="changeHandler('one')" v-model="one" >
<option v-for="subject in subjects">
{{ subject }}
</option>
</select>
<select #change="changeHandler('two')" v-model="two" >
<option v-for="subject in subjects">
{{ subject }}
</option>
</select>
<div>
Selected: {{one}} {{two}}
</div>
</template>
<script>
export default {
data() {
return {
subjects: [
"Education",
"Economics",
"English",
"English & Creative Writing",
"French",
"History",
"Law",
"Marketing",
"Mathematics",
"Psychology",
"Spanish"
],
one: "",
two: "",
}
},
methods: {
changeHandler(param) {
if(param === 'one') {
this.two = "";
} else {
this.one = ""
}
}
}
}
</script>
Instead of using v-if you can use select's disable attribute or conditional class binding. Here is a codesandbox example
<select v-model="one" :disabled="!!two">
<option v-for="(subject, index) in subjects" :key="index">
{{ subject }}
</option>
</select>

How to handle multiple select dropdowns in a React application with a single function?

I have a forms with 3 select dropdowns that filter an array based on a selected value. Rather than having an onChange handler for each of these forms I was wondering if I could use a single function too handle all of them based on a parameter.
I tried passing a string into the function but it doesn't work since it is expecting an event to be passed as a parameter.
Here is the code for the react form:
const UserSearchBox = ({ handleChange, handleLocationChange, handleTeamChange, handleClientChange }) => {
return (
<form className="user-search">
<input className="user-input" placeholder="Search for Peeps" name="userSearch" onChange={handleChange} />
<div>
<select defaultValue="all" onChange={handleLocationChange}>
<option value="all">All</option>
<option value="boston">Boston</option>
<option value="chicago">Chicago</option>
<option value="tampa">Tampa</option>
</select>
<select defaultValue="all" onChange={handleTeamChange}>
<option value="all">All</option>
<option value="design">Design</option>
<option value="engineering">Engineering</option>
<option value="finance">Finance</option>
</select>
<select defaultValue="all" onChange={handleClientChange}>
<option value="all">All</option>
<option value="company">Company1</option>
<option value="company2">Company2</option>
<option value="company3">Company3</option>
</select>
</div>
</form>
)
}
And this is the code for the App:
const handleInputChange = (e) => {
e.preventDefault();
let searchTerm = e.target.value;
if(searchTerm){
let filteredUsers = users.filter((x) => x.name.toLowerCase().includes(searchTerm.toLowerCase()))
setUsers(filteredUsers)
} else{
setUsers(allUsers)
}
}
const handleLocationChange = (e) => {
e.preventDefault();
let searchTerm = e.target.value;
if(searchTerm !== "all"){
let filteredUsers = allUsers.filter((x) => x.location.toLowerCase().includes(searchTerm.toLowerCase()))
setUsers(filteredUsers)
} else{
setUsers(allUsers)
}
}
return (
<div className="container-fluid">
<Header name={loggedInUser.name} profile_photo={loggedInUser.profile_photo} />
<UserSearchBox handleLocationChange={handleLocationChange} handleChange={handleInputChange}/>
<Users users={users} />
</div>
)
You can pass additional parameters with the event by changing the onChange to this:
onChange={(e) => { handleLocationChange(e, param1, param2, param3...) }}
Or if you dont need the event you can skip it:
onChange{() => { handleLocationChange(param1, param2, param3...) }}
That way you still get your event inside, and then you add whatever other arguments you want.
Would that help you?
Another potential solution, however I believe #Darkbound had a more precise method.
Add data attributes to the options:
<select defaultValue="all" onChange={handleSelectChange}>
<option data-filter-type="location" value="all">All</option>
<option data-filter-type="location" value="boston">Boston</option>
<option data-filter-type="location" value="chicago">Chicago</option>
<option data-filter-type="location" value="tampa">Tampa</option>
</select>
Then use a switch statement:
if (searchTerm !== "all") {
switch (attr) {
case 'location':
setUsers(users.filter((x) => x.location.toLowerCase().includes(searchTerm.toLowerCase())));
break;
case 'team':
console.log('term: ', searchTerm);
setUsers(users.filter((x) => x.team.toLowerCase().includes(searchTerm.toLowerCase())));
break;
case 'client':
setUsers(users.filter((x) => x.client.toLowerCase().includes(searchTerm.toLowerCase())));
break;
default:
setUsers(allUsers)
}
} else {
setUsers(allUsers)
}

JS+React Object not conserved in value attribute of <option> tag

I have a drop down list that is fetched from an online DB, and loops through the list, allStaff as follows:
<select onChange={handleChange} className="select-css">
<option disabled defaultValue selected>Select Name</option>
{allStaff.map(person=>(
<option value={person} key={person._id}>{person.name}</option>
))}
</select>
My handleChange function sets my stateless state, person, as follows:
const handleChange = event => {
person = event.target.value;
}
So when I try to extract parts of person it seems to register as a string equal to [object Object], which I've checked through console logging, e.g. I'll try person.name and will get an error. What have I missed out the stops me from writing person as the person object?
for reference, allStaff comes in correctly as:
[
{
"_id": "5ec63e97516541c07c2b26d3",
"name": "Bob the builder",
"clockedIn": false
},
{
"_id": "5ec68b41307f0b002436234a",
"name": "Bobby turner",
"clockedIn": false,
"time": "1333"
},
{
"_id": "5ec68b4d307f0b002436234b",
"name": "Bobby timer",
"clockedIn": true
}
]
The option's value should be a string,
<select onChange={handleChange} className="select-css">
<option disabled defaultValue selected>Select Name</option>
{allStaff.map(person=>(
<option value={person._id} key={person._id}>{person.name}</option>
))}
</select>
Then in your handleChange you should have something like that :
const handleChange = event => {
personId = event.target.value;
person = allStaff.find(staff=>staff._id === personId)
}

show dynamic select items mapping over array of objects

I have an array that looks like this:
const teamsAndPlayers = [
{
team: 'Liverpool',
players: ['Salah', 'Henderson']
},
{
team: 'Man Utd',
players: ['Rashford', 'De Gea']
},
{
team: 'Chelsea',
players: ['Hazard', 'Willian']
}
];
I have 2 select boxes, the second of which is dynamic based on what the user selects. The issue I am having is that I am not sure the best way to find the related array based on the user choice. I know I could use find and other methods, but is there anyway I can do it all in the map function in the return statement?
My code looks like the following:
const Menu = () => {
const [selectedTeam, setSelectedTeam] = useState(null);
return (
<div>
<select
onChange={e => setSelectedTeam(e.target.value)}
id="team"
name="team"
>
{teamsAndPlayers(item => (
<option key={item.team} value={item.team}>
{item.team}
</option>
))}
<select id="players" name="players">
{teamsAndPlayers(item => (
// how can I get the related players in here
// based on first select choice?
))}
</div>
)
}
I would use object instead of array to define the input for the selects (if you really want to avoid using find).
So the teamsAndPlayers would look like this:
const teamsAndPlayers = {
liverpool: {
name: 'Liverpool',
players: ['Salah', 'Henderson']
},
manUtd: {
name: 'Man Utd',
players: ['Rashford', 'De Gea']
},
chelsea: {
name: 'Chelsea',
players: ['Hazard', 'Willian']
}
};
Then the options inside the fisrt select would look like this:
{Object.keys(teamsAndPlayers).map(key => (
<option key={key} value={key}>
{teamsAndPlayers[key].name}
</option>
))}
Then the options inside the second select would look like this:
{teamsAndPlayers[selectedTeam].players.map(player => (
<option key={player} value={player}>
{player}
</option>
))}
Simply combine find and map (if you don't want to change your data Array structure):
const playersByTeam = teamsAndPlayers.find(({ team }) => team === selectedTeam).players
<select id="players" name="players">
{ playersByTeam.map(player => <option key={player>{ player }</option>) }
</select>

How to get the text of the selected option using vuejs?

I want to get the text of a selected option input and display it somewhere else. I know how to do it using jQuery but I want to know how can we do it using Vuejs.
Here is how we do in jQuery. I mean the text of Selected Option not the value.
var mytext = $("#customerName option:selected").text();
Here is my HTML
<select name="customerName" id="">
<option value="1">Jan</option>
<option value="2">Doe</option>
<option value="3">Khan</option>
</select>
{{customerName}}
Now how can I display the selected option under it. like Jan, Doe, Khan ?
Instead of define the value only as the id, you can bind the selected value with an object with two attributes: value and text.
For example with products:
<div id="app">
<select v-model="selected">
<option v-for="product in products" v-bind:value="{ id: product.id, text: product.name }">
{{ product.name }}
</option>
</select>
</div>
Then you can access to the text through the "value":
<h1>Value:
{{selected.id}}
</h1>
<h1>Text:
{{selected.text}}
</h1>
Working example
var app = new Vue({
el: '#app',
data: {
selected: '',
products: [
{id: 1, name: 'A'},
{id: 2, name: 'B'},
{id: 3, name: 'C'}
]
}
})
<div id="app">
<select v-model="selected">
<option v-for="product in products" v-bind:value="{ id: product.id, text: product.name }">{{ product.name }}
</option>
</select>
<h1>Value:
{{selected.id}}
</h1>
<h1>Text:
{{selected.text}}
</h1>
</div>
<script src="https://unpkg.com/vue#2.4.4/dist/vue.js"></script>
I had this issue, where I needed to get a data attribute from a selected option, this is how I handled it:
<select #change="handleChange">
<option value="1" data-foo="bar123">Bar</option>
<option value="2" data-foo="baz456">Baz</option>
<option value="3" data-foo="fiz789">Fiz</option>
</select>
and in the Vue methods:
methods: {
handleChange(e) {
if(e.target.options.selectedIndex > -1) {
console.log(e.target.options[e.target.options.selectedIndex].dataset.foo)
}
}
}
But you can change it to get innerText or whatever. If you're using jQuery you can $(e).find(':selected').data('foo') or $(e).find(':selected').text() to be a bit shorter.
If you are binding a model to the select element, it will only return the value (if set) or the contents of the option if there is no value set (like it would on submitting a form).
** EDIT **
I would say that the answer #MrMojoRisin gave is a much more elegant way of solving this.
The below code worked to get the Text from the selected option. I added a v-on:change , which calls a function onChange() to the select element.
methods:{
onChange: function(e){
var id = e.target.value;
var name = e.target.options[e.target.options.selectedIndex].text;
console.log('id ',id );
console.log('name ',name );
},
<select name="customerName" id="" v-on:change="onChangeSite($event)">
<option value="1">Jan</option>
<option value="2">Doe</option>
<option value="3">Khan</option>
</select>
Assuming you have a customers list and a selected customer on your model, an example like below should work perfect:
<select v-model="theCustomer">
<option :value="customer" v-for="customer in customers">{{customer.name}}</option>
</select>
<h1>{{theCustomer.title}} {{theCustomer.name}}</h1>
I guess your values should be in the JS. Then you can easily manipulate it. Simply by adding:
data: {
selected: 0,
options: ['Jan', 'Doe', 'Khan']
}
Your markup will be cleaner:
<select v-model="selected">
<option v-for="option in options" value="{{$index}}">{{option}}</option>
</select>
<br>
<span>Selected: {{options[selected]}}</span>
Here is the update JSFiddle
As th1rdey3 pointed out, you might want to use complex data and values couldn't simple be array's indexes. Still you can use and object key instead of the index. Here is the implementation.
You can use Cohars style or you can use methods too. Data is kept in options variable. The showText method finds out the selected values text and returns it. One benefit is that you can save the text to another variable e.g. selectedText
HTML:
<div id='app'>
<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<br>
<span>Selected: {{ showText(selected) }}</span>
</div>
JAVASCRIPT:
var vm = new Vue({
el: '#app',
data: {
selected: 'A',
selectedText: '',
options: [{
text: 'One',
value: 'A'
}, {
text: 'Two',
value: 'B'
}, {
text: 'Three',
value: 'C'
}]
},
methods: {
showText: function(val) {
for (var i = 0; i < this.options.length; i++) {
if (this.options[i].value === val){
this.selectedText = this.options[i].text;
return this.options[i].text;
}
}
return '';
}
}
});
JSFiddle showing demo
I tried to use the following suggestion of MrMojoRisin's
v-bind:value="{ id: product.id, text: product.name }"
However for me this was resulting in that Object's toString() representation being assigned to value, i.e. [object Object]
What I did instead in my code was to call JSON.stringify on a similar object bound to the value:
v-bind:value="JSON.stringify({id: lookup[lookupIdFields[detailsSection]], name: lookup.Name})"
Then of course I can convert it back to an object using JSON.parse and get the requisite id and name values from the result
الفترة
اختر الفترة
{{ period.name }}
<label for="period_id" class="block font-medium text-sm text-gray-700">الفترة</label>
<select v-model="form.period_id" id="period_id" class="border-gray-300">
<option disabled value="">اختر الفترة</option>
<option v-for="period in periods" :value="period.id" :selected="building.period_id === period.id ? 'selected' : null">
{{ period.name }}
</option>
</select>
Outside of the template I access the name of the option like this:
let option_name = this.options[event.target.options.selectedIndex].name
To do this take this approach to set up your template:
Defined your options in an array like
[{"name":"Bar","value":1},{"name":"Baz","value":2}] ... etc
Put your options in the data function of the component
<script>export default {function data(){return { options }}}</script>
Load the options in the template using v:for:
<option v-for="option in options" v-bind:value="option.value">{{option.name}}</option>
Use #change="getOptionName" on the select element:
<select #change="getOptionName">
In your methods get the name:
getOptionName(event){ let option_name = this.options[event.target.options.selectedIndex].name }
Note in this case the object options in event.target.options does not have anything to do with your data named options ... hopefully that will avoid confusion
So a more advanced approach, but I believe when it is all set up correctly getting the name is not terrible, although It could be easier.
You can find it out in the Vue documentation here : http://vuejs.org/guide/forms.html#Select
Using v-model :
<select v-model="selected">
<option selected>A</option>
<option>B</option>
<option>C</option>
</select>
<br>
<span>Selected: {{ selected }}</span>
In your case, I guess you should do :
<select name="customerName" id="" v-model="selected">
<option>Jan</option>
<option>Doe</option>
<option>Khan</option>
</select>
{{selected}}
Here is a working fiddle : https://jsfiddle.net/bqyfzbq2/
I think some of the answers here are a bit too complex, so I'd like to offer an easier solution. I'm only going to use an event handler in the example and leave out things like model binding, etc.
<select #change="yourCustomMethod">
<option value="1">One</option>
<option value="2">Two</option>
</select>
Then in your Vue instance:
...
methods: {
yourCustomMethod: function(event) {
var key = event.target.value, // example: 1
value = event.target.textContent; // example: One
}
}
...

Categories

Resources