So I love Semantic UI and I just started working with Vue.js
Semantic UI dropdowns must be initialized using the dropdown() in semantic.js, it generates a complex div based HTML to show the dropdowns in a pretty way.
The problem comes when I bind Vue to a dropdown, it doesn't update the UI according to the model. Especially when I change the value of the parent dropdown.
For some reason the bidning works fine the first time an item is selected on the parent dropdown, but not after that :|
<div class="ui grid">
<div class="row">
<div class="column">
<div class="ui label">Vechicle Make</div>
<select class="ui dropdown" v-model="selected" id="vehicle-makes" v-on:change="onChange">
<option v-for="option in options" v-bind:value="option.id">
{{option.text}}
</option>
</select>
</div>
</div>
<div class="row">
<div class="column">
<div class="ui label">Vechicle Model</div>
<select class="ui dropdown" v-model="selected" id="vehicle-models">
<option v-for="option in options" v-bind:value="option.id">
{{option.text}}
</option>
</select>
</div>
</div>
</div>
var model_options = {
1: [{ text: "Accord", id: 1 }, { text: "Civic", id: 2 }],
2: [{ text: "Corolla", id: 3 }, { text: "Hi Ace", id: 4 }],
3: [{ text: "Altima", id: 5 }, { text: "Zuke", id: 6 }],
4: [{ text: "Alto", id: 7 }, { text: "Swift", id: 8 }]
};
var makes_options = [
{ text: "Honda", id: 1 },
{ text: "Toyota", id: 2 },
{ text: "Nissan", id: 3 },
{ text: "Suzuki", id: 4 }
];
var vm_makes = new Vue({
el: "#vehicle-makes",
data: {
selected: null,
options: makes_options
},
methods: {
onChange: function(event) {
vm_models.selected = null;
vm_models.options.splice(0);
for (index = 0; index < model_options[this.selected].length; index++) {
vm_models.options.push(model_options[this.selected][index]);
}
}
}
});
var vm_models = new Vue({
el: "#vehicle-models",
data: {
selected: null,
options: []
}
});
// Eveything works fine if I remove this...
$('.ui.dropdown').dropdown();
The exact code can be found here. https://codepen.io/adnanshussain/pen/KqVxXL?editors=1010
You are using a v-for directive on your option element without a key attribute. Without this key, Vue uses an "in-place patch" strategy to optimize rendering. This is most likely messing with what your Semantic UI dropdown is expecting as far as changes to the select element go.
Add a key attribute to your option tag, providing a unique id as the value:
<option v-for="option in options" :value="option.id" :key="option.id">
{{ option.text }}
</option>
To clear the value in the model's select element when the make changes, you can use $('#vehicle-models').dropdown('restore defaults').
Also, if you put all the logic in one Vue instance, things become a lot simpler: Here's an example codepen.
Related
I have a list of fields
fields: [
{
id:0,
label: 'dropdown 1',
value: 0,
options: [{ id: 0, value: 0, label: 'Option 1' }]
},
{
id: 1,
label: 'dropdown 2',
value: 0,
options: [
{ id: 0, value: 0, label: 'Hide dropdown 1' },
{ id: 1, value: 1, label: 'Show dropdown 1' }
]
}
]
that are filtered before being shown like so:
<div v-for="field in fields.filter(ffield => showField(ffield))" :key="field.id">
<b-form-select v-model="field.value" #change="changed(field)">
<option v-for="option in field.options" :key="option.id" :value="option.value">
{{ option.label }}
</option>
</b-form-select>
</div>
I can hide 'dropdown 1' based on the selected option in 'dropdown 2'. This is managed by the 'showField' function:
function showField(field) {
return field.value !== 'dropdown 1' || fields[1].value === 1
}
Lets say dropdown 1 is hidden and I select the second option Show dropdown 1. Vue's reactivity runs the showField function for all items in the list before running the changed event on the affected field. So now dropdown 1 is visible and then the eventhandler is called.
But what's really weird is that the parameter field in the changed function corresponds to the value of fields[0] even though I selected an option in 'dropdown 2' or fields[1].
What is going on?
I understand that this doesn't happen when dropdown 1 is after dropdown 2 in the list.
I tried modifying node_modules/bootstrap-vue/esm/components/form-select/form-select.js rendering function but this must be some internal thing that's causing it and I'm not familiar enough to keep digging.
Maybe I disregarded some pattern for this sort of thing?
The filter in the v-for came in because we used v-if="showField(field)" together with the v-for and that was incorrect.
How can I avoid this?
I have tricky challenge with vuejs, I want to have two select fields. the first one should select fruits for example, and the second should list all fruits. If I select vegetable from the first select field, the second select field should list all vegetable.
I stumble and find similar stuff online but I don't know how to make first item in the second select field selected.
anytime I select fruits, the first item on the list in second select first should be selected as default, and if I select vegetable, the first item in the second select field should be selected as default.
pls help me check the code here: https://jsfiddle.net/aj6g87dh/1/
new Vue({
el: '#test',
data: {
category: 'fruits',
list: '',
optionsData: {
fruits: [
{ text: 'Orange', value: 'orange' },
{ text: 'Banane', value: 'banana' },
],
vegetables: [
{ text: 'Brocolis', value: 'brocolis' },
{ text: 'Radish', value: 'radish' },
]
}
},
computed: {
options: function() {
let options = ''
switch (this.category) {
case 'fruits':
options = this.optionsData.fruits
break;
case 'vegetables':
options = this.optionsData.vegetables
break;
default:
options = this.optionsData.fruits
}
return options
}
},
methods: {
onChange: function() {
this.options = this.options
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.5/vue.js"></script>
<div id="test">
<select v-model="category" v-on:change="onChange" id="select1">
<option value="fruits">Fruits</option>
<option value="vegetables">Vegetables</option>
</select>
<select id="select2" v-model="list">
<option v-for="option in options" v-bind:value="option.value"> {{ option.text }} </option>
</select>
<span>{{ }}</span>
</div>
You can remove onChange method and add a watch property. This way you can handle changing logic there.
Also, you can simplify options retrieval to one line.
new Vue({
el: '#test',
data: {
category: 'fruits',
list: '',
optionsData: {
fruits: [{
text: 'Orange',
value: 'orange'
},
{
text: 'Banane',
value: 'banana'
},
],
vegetables: [{
text: 'Brocolis',
value: 'brocolis'
},
{
text: 'Radish',
value: 'radish'
},
]
}
},
computed: {
options: function() {
return this.optionsData[this.category]
}
},
watch: {
category: {
handler: function(newVal) {
this.list = this.optionsData[newVal][0].value;
},
immediate: true
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="test">
<select v-model="category" id="select1">
<option value="fruits">Fruits</option>
<option value="vegetables">Vegetables</option>
</select>
<select id="select2" v-model="list">
<option v-for="(option, i) in options" v-bind:value="option.value"> {{ option.text }} </option>
</select>
<span>{{ }}</span>
</div>
Basically I've written following snippet in which I am using v-model in input that has datalist. When I select one of the datalist, datalist immediately reappears again after I select a option as if I had manually typed the code.
new Vue({
el: '#app',
data: {
input: '',
list : [
{name:"Item 1", code:"i1"},
{name:"Item 2", code:"i2"},
{name:"Item 3", code:"i3"},
{name:"Item 4", code:"i4"},
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p>{{ input }}</p>
<input v-model="input" list="list" />
<datalist id="list">
<option v-for="item in list" :value="item.code">{{item.name}}</option>
</datalist>
</div>
But this does not happen when I remove vue directive from the input field (v-model,#input). I've added both code for convenience. I hope someone will help me because I need v-moder if not #input in my input field.
new Vue({
el: '#app',
data: {
input: '',
list : [
{name:"Item 1", code:"i1"},
{name:"Item 2", code:"i2"},
{name:"Item 3", code:"i3"},
{name:"Item 4", code:"i4"},
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p>{{ input }}</p>
<input list="list" />
<datalist id="list">
<option v-for="item in list" :value="item.code">{{item.name}}</option>
</datalist>
</div>
EDIT As suggested by #muka.gergely I used external method to blur the input field after value was selected. It is not perfect solution, but works as expected. (probably bug in chrome). Here is code
new Vue({
el: '#app',
data: {
input: '',
list: [{
name: "Item 1",
code: "i1"
},
{
name: "Item 2",
code: "i2"
},
{
name: "Item 3",
code: "i3"
},
{
name: "Item 4",
code: "i4"
},
]
},
methods: {
onSelect(event) {
let val = event.target.value;
let select = false;
let options = document.getElementById("list").childNodes;
for (var i = 0; i < options.length; i++) {
//check if value in input box is one of the options
if (options[i].value === val.trim()) {
//value was selected
//do something
select = true;
//bluring input field so as not to show datalist again
event.target.blur();
break;
}
}
//value was typed
if (!select) {
//this.fetchAutocomplete(val);
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p>{{ input }}</p>
<input v-model="input" list="list" #input="onSelect" />
<datalist id="list">
<option v-for="item in list" :value="item.code">{{item.name}}</option>
</datalist>
</div>
The code works as you wrote it:
<p>{{input}}</p> - this is the first line. On init it's empty, so now lines are shown.
<input v-model="input" list="list" /> this is the second line, but on init it is displayed first.
So, when your app reacts to data change, the input is pushed one line down. If you initialize your input data with something else than '', then you can see that nothing unexpected happens (OK, maybe unexpected, but not extraordinary :) ):
new Vue({
el: '#app',
data: {
input: '',
list: [{
name: "Item 1",
code: "i1"
},
{
name: "Item 2",
code: "i2"
},
{
name: "Item 3",
code: "i3"
},
{
name: "Item 4",
code: "i4"
},
]
},
methods: {
loseFocus(evt) {
evt.target.blur()
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p>{{ input || 'Data comes here' }}</p>
<input v-model="input" list="list" #input="loseFocus" />
<datalist id="list">
<option v-for="item in list" :value="item.code">{{item.name}}</option>
</datalist>
</div>
EDIT
The problem was not the "jumping" of the input, but that the datalist appeared open after picking an option.
The problem is that this element stays focused after the input/change event, so it behaves in an unwanted way. (But as it's supposed to behave.)
I added #input on the element, and created a method to blur the element (so it loses focus, and the datalist doesn't open/closes).
2nd EDIT
We discussed the question a bit more in the chat, and came up a with a snippet that is closer to the actual solution #KshitijDhakal sought:
new Vue({
el: "#app",
data: {
input: '',
list: [{
type: 'posts',
title: 'Posts',
code: 'i1'
},
{
type: 'albums',
title: 'Albums',
code: 'i2'
}
],
singleItem: {
title: ''
}
},
methods: {
fetchData: function(type, id) {
return fetch(`https://jsonplaceholder.typicode.com/${type}/${id}`)
.then(response => response.json())
.then(json => {
return json
})
},
onSelect: async function(e) {
if (this.list.map(el => el.code).includes(e.target.value)) e.target.blur()
this.singleItem = await this.fetchData(this.list.find(el => el.code === e.target.value).type, 1)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input v-model="input" list="list" #input="onSelect" />
<datalist id="list">
<option v-for="item in list" :value="item.code">{{item.title}}</option>
</datalist>
<div>
<h2>
{{singleItem.title}}
</h2>
</div>
</div>
When I try to show default selected value it doesn't show up i.e the option does not get selected in dropdown. The default value is coming from database and I'm setting that value in the model variable pageData.fields[content.id].keyFollowing is the sample JSON that I'm using to populate the dropdown:
$scope.dropdownFields = [{
groupName: 'PAGE',
isOptDisabled: false,
items: [{
name: 'PageName1',
type: 'page'
},
{
name: 'PageName2',
type: 'page'
},
{
name: 'PageName3',
type: 'page'
},
{
name: 'PageName4',
type: 'page'
}
]
},
{
groupName: 'COLOR',
isOptDisabled: false,
items: [{
name: 'COLOR1',
type: 'component'
},
{
name: 'COLOR2',
type: 'component'
},
{
name: 'COLOR3',
type: 'component'
}
]
},
{
groupName: 'OTHERS',
items: [{
name: 'Bold',
type: 'others',
isOptDisabled: false,
itemList: [{
name: 'Yes',
value: true
},
{
name: 'No',
value: false
}
]
},
{
name: 'Italic',
type: 'others',
isOptDisabled: false,
itemList: [{
name: 'Yes',
value: true
},
{
name: 'No',
value: false
}
]
}
]
}
];
<select id="{{content.id}}_filter" ng-model="pageData.fields[content.id].key" class="form-control" ng-change="onChangeFilter(pageData.fields[content.id].key, content.id)">
<option value="">[Select an option]</option>
<optgroup ng-repeat="header in dropdownFields" label="{{ header.groupName }}">
<option ng-repeat="item in header.items" value="{{ item.name }}" ng-disabled="isDisabled(header.groupName, item.name)">{{ item.name}}
</option>
</optgroup>
</select>
Is there any way to do this as I've researched a lot on this but the solutions were with ng-options and I cannot use ng-options for creating the dropdown due the disability functionality. The AngularJS version that I'm using is 1.2Any suggestion into the direction will be appreciated. Thanks.
EDIT: The code is successfully populating the dropdown and I'm able to get the value of selected option as well. But I'm not able to set an option by default into the dropdown. For eg. PageName3 is already selected in the dropdown.
EDIT2: So far now I'm able to show default value selected in this combobox on button click(here's the plnk) but I'm not able to set this value when I redirect from another page to this page.
So it goes like this, on this page(say PAGE1) I fill in these values there are multiple such dropdowns and textboxes in front of them for values, then I pass these values to the next page(say PAGE2); on PAGE2 I have a back button on the click of this button I return to PAGE1 with the same values passed before and I've to set all these values back as it were before. This is where I'm stuck! Not able to set selected values in dropdown while setting textboxes is done.
You can add default option into dropdown like so:
<input type="checkbox" ng-model="selected"></label><br/>
<select ng-model="item.name" ng-click="onChangeFilter(item.name)" class="form-control" >
<optgroup ng-repeat="header in dropdownFields" label="{{ header.groupName }}">
<option ng-selected="selected">Greetings!</option>
<option ng-repeat="item in header.items">{{item.name}}</option>
</optgroup>
</select>
plunker: http://plnkr.co/edit/vszVo3BHEA3T4yxGSUkA?p=preview
I have a group of selects but want them all to work differently with v-model without creating separate data properties for them all.
In the example, select 1 and select 2 are one group and select 3 and select 4 are another.
When one select select 1, the expected behavior should be that, it does not affect select 2, etc.
My guess is may be a computed property for this? but can't get my head around the proper implementation
Js fiddle: https://jsfiddle.net/uxn501h2/8/
Code snippet:
new Vue({
el: '#app',
template: `
<div>
Select 1: <select v-model="selectedPerson">
<option v-for="person in people" :value="person.key">{{person.name}}</option>
</select>
Select 2: <select v-model="selectedPerson">
<option v-for="person in people" :value="person.key">{{person.name}}</option>
</select>
<h4>Selected 1 person key : {{selectedPerson}}</h4>
<h4>Selected 2 person key: {{selectedPerson}}</h4>
<br/>
Select 3: <select v-model="selectedPersonTwo">
<option v-for="person in people" :value="person.key">{{person.name}}</option>
</select>
Select 4: <select v-model="selectedPersonTwo">
<option v-for="person in people" :value="person.key">{{person.name}}</option>
</select>
<h4>Selected 3 person Two key: {{selectedPersonTwo}}</h4>
<h4>Selected 4 person Two key: {{selectedPersonTwo}}</h4>
</div>
`,
data: {
people: [{
key: 1,
name: "Carl"
},
{
key: 2,
name: "Carol"
},
{
key: 3,
name: "Clara"
},
{
key: 4,
name: "John"
},
{
key: 5,
name: "Jacob"
},
{
key: 6,
name: "Mark"
},
{
key: 7,
name: "Steve"
}
],
selectedPerson: "",
selectedPersonTwo: ""
}
});
.required-field > label::after {
content: '*';
color: red;
margin-left: 0.25rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<body>
<div id="app"></div>
</body>
It looks to me that you'd like to use two-way binding, but with more control between them in the middle. My suggestion would be:
Do not use <select v-model="selectedPerson">
Instead split them to <select :value="selectedPersonValue" #input="selectedPersonOutput> (which is basically the same thing, but you can specify different input and outputs), where selectedPersonValue can be a computed property or a regular property in data(), and selectedPersonOutput should be a method that will be called when select value changes.
This way you can directly decide what happens on each step.
PS: If you want to influence your property selectedPersonValue from a method, you might consider changing it to data() property and add a watch to it. See what works best for you.