I'm displaying 3 drop-down menus. Depending on what subject is chosen in the select menu one affects the subjects that should display in the other 2 select menus.
For example, if "Economics" is selected in drop-down one then "Marketing" and "Mathematics" should not display in the other menus. There are 7 different scenarios to cover in total, similar to this one. Can anyone help me get off the ground with this, please?
<template>
<select v-model="one">
<option v-for="subject in subjects">
{{ subject }}
</option>
</select>
<select v-model="two" :disabled="!one">
<option v-for="subject in subjects.filter(item => item.split(' ')[0].indexOf(this.one.split(' ')[0]))">
{{ subject }}
</option>
</select>
<select v-model="three" :disabled="!two">
<option v-for="subject in subjects.filter(item => item.split(' ')[0].indexOf(this.one.split(' ')[0]) && item.split(' ')[0].indexOf(this.two.split(' ')[0]))">
{{ subject }}
</option>
</select>
<div>
Selected: {{one}} {{two}} {{three}}
</div>
</template>
<script>
export default {
data() {
return {
subjects: [
"Education",
"Economics",
"English",
"English & Creative Writing",
"French",
"History",
"Law",
"Marketing",
"Mathematics",
"Psychology",
"Spanish"
],
one: "",
two: "",
three: "",
}
}
}
</script>
The general idea is to map up the relation between what I would call "top level subjects" or topics and the "sub level subjects" or sub-topics.
If you know you only ever will have three inputs (at max), then doing a mapping for a pre-populated array is ok. However, if you're looking at dynamically adding more levels, you would need to put down more work. That being said, I've looked at this briefly and added a subject <-> topic relation. Each entry has the parent attribute which defines if they have one or more. If they don't, they're a "top level subject" or topic. If they do, they're a sub-topic.
By handling the onChange event for each select, you can figure out what ID the subject you selected has and find the corresponding entry in the subjects object array. Once you found that, you filter all entries that have parents !== null and match every ID in their parent array. This is calculated using a property, which is ideal for such purposes.
HTML
<div id="app">
<select
v-model="selected[0].id"
#change="selectTopic($event)"
>
<!-- List first only the top level topics -->
<option
v-for="(subject, subjectKey) in topics"
:key="`subject-${subjectKey}`"
:value="subject.id"
>
{{ subject.name }}
</option>
</select>
<select
v-if="nextLevelSubjects"
v-model="selected[1].id"
#change="selectTopic($event)"
>
<!-- List first only the top level topics -->
<option
v-for="(subject, subjectKey) in nextLevelSubjects"
:key="`next-level-subject-${subjectKey}`"
:value="subject.id"
>
{{ subject.name }}
</option>
</select>
<div v-if="someSelectionHasBeenMade">
<pre>
All topics selected: {{ selected }}
</pre>
</div>
</div>
Vue Code (Excerpt)
data: {
someSelectionHasBeenMade: false,
nextLevelSubjects: null,
selected: [
{
id: null
},
{
id: null
},
{
id: null
}
],
subjects: [
{
id: 1,
name: "Education",
parent: null
},
{
id: 2,
name: "English",
parent: [1]
},
{
id: 3,
name: "English & Creative Writing",
parent: [1]
},
{
id: 4,
name: "French",
parent: [1]
},
{
id: 5,
name: "Economics",
parent: null
},
{
id: 6,
name: "Law",
parent: [5]
},
{
id: 7,
name: "Marketing",
parent: [5]
},
]
},
computed: {
topics () {
return this.subjects.filter(e => e.parent === null)
}
},
methods: {
selectTopic (event) {
this.someSelectionHasBeenMade = true
const parentId = Number.parseFloat(event.target.value)
this.nextLevelSubjects = this.subjects.filter(e => e.parent !== null).filter(i => i.parent.every(p => p === parentId))
}
}
})
I've made a JSFiddle here: https://jsfiddle.net/Coreus/md7ktj6e/2/
Making a sub-topic be parent to several topics
If you want a sub-topic to match either one of more subjects / topics, put in several IDs in the parent property for the sub-topic in question and change the filtering by parent to some. Like so:
subjects: [
{
id: 1,
name: "Education",
parent: null
},
{
id: 2,
name: "Languages",
parent: null
},
{
id: 3,
name: "English",
parent: [1, 2]
},
....
{
id: 4,
name: "French",
parent: [1, 2]
},
{
id: 5,
name: "History",
parent: [1]
},
By the example above, the relation tree looks like so:
Education
English
French
History
Languages
English
French
Then change the filtering to use some instead of every.
this.nextLevelSubjects = this.subjects.filter(e => e.parent !== null).filter(i => i.parent.some(p => p === parentId))
Edit: Code example has been updated
Please note this just illustrates purpose. You obviously need to put down more work for it to work on additional tiers (sub-topics) down the line. It should, however, give you an idea on how to approach this further.
Related
I have a select element that contains some options elements built from an array.
Right now, when I select an option it immediately calls the function and produces the output but what I want is that once I select any option, and if that option's value is "students" then only it should run the function instantly.
When I click on the add button it does not work at all.
<form name="Form1" >
<b>Field Name</b> : <p>Student Council</p>
<b>Field Type:</b> <p>List of Links</p>
<b>Range:</b><br>
<select name="selectOption" id="listSelect" v-model="selected">
<option hidden value="">Select a list</option>
<option v-for="List in fieldlist" v-if="List.ListName != 'student_council'"> {{ List.ListName}}</option>
</select>
<button #click="add"> Save </button>
<br><br>
<b> Student_Council list:</b>
{{fieldlist[5].student_council}}</v-list-item>
</form>
<script>
var vm = new Vue({
el : '#new_list' , // id of the elemnt we ware working on to call it
data: {
selected : null,
fieldlist: [
{
ListName: 'students',
Students: [
{
StudentName: 'Yousef',
StudentAge: '24',
isStudent: true,
},
{
StudentName: 'Baqir',
StudentAge: '23',
isStudent: true,
},
]
},
{
ListName: 'departments',
departments: [],
},
{
ListName: 'department_with_most_students',
Department_With_Most_Students: [],
},
{
ListName: 'active_students',
active_students: [],
},
{
ListName: 'dean',
dean: [],
},
{
ListName: 'student_council',
student_council: [],
},
],
},
methods: { // functions
add: function(option){
if(this.selectOption.option.target.value=== "students"){
if(!this.fieldlist[5].student_council.includes(this.fieldlist[0].Students)){
this.fieldlist[5].student_council.push(this.fieldlist[0].Students);
}else{
this.fieldlist[5].student_council.length = 0;
return false;
}
}
},
}
I tried to call the select element to check its option, but this is also not working so far.
I'm expecting if I select the option "students" and click on the add button, it should pass the data inside the students array to the student_council array
I have read your question I think you have to deal with
this.selected
instead of
this.selectOption.option.target.value
in vuejs we don't need to deal with name of tag element we deal with v-model
please update me if i miss something
I am having an issue whereby my results do not display when the page loads. It will only display if i make a selection on the drop-down menu.
I have tried adding the function to the mounted property but that doesn't seem to work. Any ideas what this might be?
<select v-model="sortResults"
#change="sortalphabetically"
class="col-4 col-lg-5"
aria-label="sortby"
id="sortby">
<option disabled value="" selected>Select</option>
<option value="alpha">Alphabetically</option>
<option value="relevance">Relevance</option>
</select>
methods: {
sortalphabetically() {
switch (this.sortResults) {
case "alpha":
this.theResults = [...this.results].sort((a, b) =>
a.metaData.title > b.metaData.title ? 1 : -1
);
break;
case "relevance":
this.theResults = [...this.results];
break;
}
},
}
data: function () {
return {
sortResults: "relevance"
}
import Result from "#/components/Result.vue";
mounted() {
this.dataFilters;
this.updateURL();
this.theResults();
},
};
I reproduced your code by adding some changes and it works correctly.
First, you do not sort your values in the second switch's case. It maybe is your business :-) It doesn't matter.
Second, in showing results if you are using v-if please do not use i as the index of the result array for its key, It won't work. Vue and even React do not recognise the changing order of an array if its index is being used as the key. So, use the items' unique ids.
<template>
<div>
<select
id="sortby"
aria-label="sortby"
class="col-4 col-lg-5"
v-model="sortResults"
#change="sortalphabetically"
>
<option disabled value="" selected>Select</option>
<option value="alpha">Alphabetically</option>
<option value="relevance">Relevance</option>
</select>
<div style="background-color: aquamarine">
<div
v-for="item in theResults" :key="item.metaData.id"
>
{{item.metaData.title}}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'BaseSelectTest',
data() {
return {
sortResults: 'relevance',
theResults: [],
results: [
{ metaData: { title: 'BBBB', id: 2 } },
{ metaData: { title: 'DDDD', id: 4 } },
{ metaData: { title: 'AAAA', id: 1 } },
{ metaData: { title: 'CCCC', id: 3 } },
{ metaData: { title: 'EEEE', id: 5 } },
],
};
},
methods: {
sortalphabetically() {
switch (this.sortResults) {
case 'alpha':
this.theResults = [...this.results]
.sort((a, b) => (a.metaData.title > b.metaData.title ? 1 : -1));
break;
case 'relevance':
this.theResults = [...this.results] // You may omit the next line
// .sort((a, b) => (a.metaData.title > b.metaData.title ? -1 : 1));
break;
default:
// nothing left to do
}
},
},
mounted() {
this.sortalphabetically(); // It's optional if you ignore sorting for 'relevance'
},
};
</script>
Finally, if your flaw persists, you need to check out the showing result codes. Of course, it's possible to observe data changes using the Vue Dev Tool for sure.
I have a list of selected products and every row contains the name of the product and dropdown containing array of tags suggested for that product. User can select only one tag for every product and then update all changes together. Here is the code:
<div class="columns" v-for="selectedProduct in selectedProducts">
<p class="left"> {{selectedProduct.name}} </p>
<b-dropdown aria-role="list">
<button class="button is-primary" slot="trigger" slot-scope="{ active }">
<span>Click me!</span>
<b-icon :icon="active ? 'menu-up' : 'menu-down'"></b-icon>
</button>
<b-dropdownItem
v-for="selectedProductTag in selectedProduct.tags"
:key="selectedProductTag.id"
#click=""
>
{{ selectedProductTag.name }}
</b-dropdownItem>
</b-dropdown>
</div>
selected products look like this:
// selectedProducts array
[
// selectedProduct 1
{
id:
name:
tags: [{
id: ...
name: ...
} {
id: ...
name: ...
}]
},
// selectedProduct 2
{
id:
name:
tags: [{
id: ...
name: ...
} {
id: ...
name: ...
}]
}
]
So the main question is how can I select one value from the selectedProduct.tags array and then modify the the main selectedProducts array by replacing the whole tags nested array with this selected single selectedProductTag value?
Update function where it should be passed:
async updateProducts (products) {
const submitedProductData = products.map(product => ({
product: {
productName: productName,
...
tag:
})
}
I have a form with two select inputs. What the user selects in the first select input (product_id) is going to determine what options to show in the second select input. Being sort of new to JavaScript, I am struggling with how to write the logic for this in my Vue app.
Let me explain...(also live demo here)
I have two data arrays:
Products:
[
{
product_id: 1,
product_name: 'Apple'
},
{
product_id: 2,
product_name: 'Banana'
},
{
product_id: 3,
product_name: 'Watermelon'
},
{
product_id: 4,
product_name: 'Potato'
}
]
Subjects:
[
{
product_id: 1,
subject_name: 'Granny Smith'
},
{
product_id: 1,
subject_name: 'McIntosh'
},
{
product_id: 2,
subject_name: 'Cavendish'
},
{
product_id: 3,
subject_name: 'Jubilee'
},
{
product_id: 3,
subject_name: 'Black Diamond'
},
{
product_id: 4,
subject_name: 'Russet'
},
{
product_id: 4,
subject_name: 'Yukon Gold'
}
]
Form template:
<label for="product_select_input">Product:</label>
<select
id="product_select_input"
v-model="form.product"
>
<option disabled value="">Select</option>
<option
v-for="(product, index) in products"
:key="index"
:value="product.product_id"
>{{ product.product_name }}</option
>
</select>
<label for="product_subject_input">Product Subject:</label>
<select
id="product_subject_input"
v-model="form.subject"
>
<option disabled value="">Select a Subject</option>
<option
v-for="(subject, index) in subjects"
:key="index"
:value="subject.subject_id"
>{{ subject.subject_name }}</option
>
</select>
I thought that I would add a computed property called showRelatedSubjs and bind it to #change on the initial product select, but that did not work and I get the following error in console: Cannot read property 'product_id' of undefined
computed: {
showRelatedSubj() {
if (this.form.product === this.subject.product_id) {
return this.subjects.filter(subject => subject.includes(this.subject_name))
} else {
return ''
}
}
}
Anyone have any tips or solutions ? Thank you.
My live demo is available here
A few issues:
The objects in subjects contain product_ids, yet your v-model is bound to subject.subject_id (nonexistent).
The name of your computed property suggests the intention is to return the related subjects based on the selected product. That should be done with only the filter() like this:
showRelatedSubj() {
return this.subjects.filter(
subject => this.form.product === subject.product_id
);
}
updated codesandbox
I have a dataset that looks like this:
[
{id: 1, name: 'Foo', is_primary: false},
{id: 2, name: 'Bar', is_primary: true},
{id: 3, name: 'Baz', is_primary: false},
]
Only one of the entries is allowed to have is_primary = true. I'm displaying these items in a list, and I'm trying to display a radio button for each that the user can select to indicate that this is the primary one.
<tr v-for="item in items">
<td><input name="primary" type="radio" v-model="item.is_primary"></td>
</tr>
However, I don't think I'm understanding how this is supposed to work, because it's not working for me. Is this possible or am I supposed to handle this situation another way?
A set of radio inputs should v-model the same variable: a scalar that takes on the value associated with the selected radio.
To translate that back and forth into your item list, you can use a settable computed.
new Vue({
el: '#app',
data: {
items: [{
id: 1,
name: 'Foo',
is_primary: false
},
{
id: 2,
name: 'Bar',
is_primary: true
},
{
id: 3,
name: 'Baz',
is_primary: false
},
]
},
computed: {
primaryItem: {
get() {
return this.items.find((i) => i.is_primary);
},
set(pi) {
this.items.forEach((i) => i.is_primary = i === pi);
}
}
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<div v-for="item in items">
<input name="primary" type="radio" :value="item" v-model="primaryItem">
</div>
<pre>{{JSON.stringify(items, null, 2)}}</pre>
</div>