Select not rendering new :value when changing options dynamically with vuejs - vue.js

I've created a small jsfiddle which explains the error and how to reproduce it here: https://jsfiddle.net/4Leu9a6x/57/
I have a select on which I use #change and :value to update and show the value. I know I could use v-model but my app is rather complex so this is the only way to do it currently.
The problems appears when I change the options dynamically. As soon as I change them the value of the select (the one the user sees) is not the same as the real value (saved in data by vue). Even though I do have :value.
I don't understand why it doesn't show a gray select (with nothing selected) when :value is not inside the options.
The jsfiddle above will clearly show this problem. Any ideas on how to keep the two in sync?

v-model has specific behavior to deal with the situation when a <select>'s options are changed dynamically. Since you're not using v-model, you'll have to deal with these issues yourself.
Like other input elements, a <select> has its own internal state (value and selectedIndex) that the browser maintains (not Vue). If you change the options without changing the value, then when Vue re-renders it won't set the value of the select since the value didn't change, so you're left with whatever state the browser chooses, and potentially the bound value will be out of sync with the actual value of the select.
A couple of solutions:
Bind key of the <select> to recreate the element when the options change (fiddle).
Wrap the <select> in a component and manually set the selectedIndex of the element to the index of the selected option. Read the v-model source to see how Vue does it.
When you change the options data, also change the value data so that it corresponds to an actual option. But this doesn't solve the problem of having the <select> select no option when the value doesn't match any option after changing the options but not the value.

Add this.val = this.options[0]; while running shuffle method. This is because each time the shuffle method runs it resets the options, since your value is not two way bound, the value is not being updated.
Else you can refractor your code and use v-model.
new Vue({
el: "#app",
data: {
val: 2,
options: [1, 2, 3, 4]
},
methods: {
shuffle() {
this.options = [Math.random(), Math.random(), Math.random()]
this.val = this.options[0];
}
}
})

Related

How to conditionally set a bootstrap-vue select box value when another select box value selected in vuejs?

I am trying make a conditional select-box. There are 2 select-boxes. When select-box1 1's value changes then the 2nd select-box value automatically need to get selected to a value and it will also be disabled.
So far , i am able to do the conditional select where select-box 1's option changes value in select-box2 but it shows error in vue.
Error is--
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
Also , i can disable the 2nd select-box but when i use dynamic disabled the value doesn't get set.
Question in brief:
1st selectbox has value-- a)one time payment & b)subscription and
2nd selectbox has value--a)held & b) Immediate.
Now, if 1st selectbox's subscription is selected then in the 2nd selectbox, it should be set to immediate and also get disabled.
Below is the Code Sandbox link--
https://codesandbox.io/s/conditional-select-forked-h61po?file=/src/components/HelloWorld.vue
If there is a better way of achieving this, then feel free to share ...
You don't need a ref, you can change the value of selected2 directly because it's not a prop, but a data field. Remove the reference in your component, like so:
<b-form-select
v-model="selected2"
:options="options2"
:disabled="isDisabled"
></b-form-select>
and change your onChange method to this (you don't need the else block as well):
onChange(event) {
if (event === "subscription") {
this.selected2 = "Immediate";
this.isDisabled = true;
}
},
Try it. I've tested it and it works.
I think the best way is to watch the 2-way-binding (selected) with a watcher.
See: https://v3.vuejs.org/guide/reactivity-computed-watchers.html#watch
And if the value changes you can check if the new value is 'subscription'.
And to select the wante option in the 2nd select you just have to asign the wanted value to the 2-way-binding (selected2). And for disabling set isDisabled to true.

How to programmatically expand all rows in a v-data-table component (Vuetify version 1.5)

v-data-table component has a propery "expanded" that allows to show some additional info for each row.
It works fine and I need to expand all the rows immediately when a page is loaded.
Is there a way to do that?
It is used version 1.5 of Vuetify Framework.
https://v15.vuetifyjs.com/en/components/data-tables
This is fairly easy in the new Version of Vuetify, you just use the property expanded which holds an Array of the items that are currently expanded.
<v-data-table
id="issues-table"
:value="selectedIssues"
:expanded="expandedIssues"
:items="issues"
>
<v-switch #change="(v) => expandAllIssues(items, v)" />
expandAllIssues (items, status) {
if (status) {
this.expandedIssues = items
} else {
this.expandedIssues = []
}
}
In V1.5.xx of Vuetify you don't have that luxury, but when I took a look, Vuetify works with a similar system under the hood. Firstly you should set the expand prop on the table to true, so it can expand multiple rows.
You can define a reference on your data table and then access it via this.$refs.myDataTableRef. You then realise that they store the expanded rows in an Object called expanded. You can set the rows either true by the id/name of the row in this object or you simply set the whole row objects in the vuetify expanded object to true.
I have pasted the Codepen example of their old data table and made a simple expand all button, that you can obviously change to your desires, but it should make the concept clear.
Codepen example

Dynamically updating v-combobox items list

Is it possible somehow to update Vuetify v-combobox items as the user is typing? I want to change the list of available items depending on what the users started typing, to create an address input with suggestions from a geolocation API.
This is what I tried: #update:search-input='fetchAddresses'
And in fetchAddresses: this.items = newListOfItems
However, while #update:search-input fires as expected, the combobox list will only be updated after losing focus. Can I somehow trigger it to be updated? (This may very well be an X/Y problem, so any hints about other approaches are welcome)
My current, ugly, hack is to force the whole combobox component to re-render with the current value set, and then refocus on it. (There is an activateMenu() method on the combobox that I could use to make sure the list reopened:
this.$nextTick(() => {
if (this.$refs.addressCombobox) {
this.$refs.addressCombobox.focus()
this.$refs.addressCombobox.activateMenu()
}
})
I'm using Vue 2.
In my case no-filter solved this problem
<v-combobox no-filter ... >

Initially hide first group in Vue-Formulate repeatable group

I'm using Vue-Formulate's Repeatable Groups. For my requirements:
A group is optional to add
If a button is pressed to add a group, then the fields in the group must be validated
The form should not initially show the group; it should show the button to add a group
For example, the desired initial appearance is in the following screenshot, which I generated after clicking the "remove" / X button in the linked codesandbox:
I've mocked this up at codesandbox here: Vue Formulate Group with Button to Start
Is this possible? If so, how?
May 2021 UPDATED WORKAROUND
In #braid/vue-formulate#2.5.2, the workaround below (in Research: A hack that seems to UPDATE: USED TO work) no longer works, using a slot to override the close button, save a ref, and trigger a click does. See also the related feature request at https://github.com/wearebraid/vue-formulate/issues/425.
<script>
export default {
// ... fluff omitted
async mounted() {
await this.$nextTick();
if (!this.hasMessages) {
// See also feature request at https://github.com/wearebraid/vue-formulate/issues/425
this.$refs.closeButton.click();
}
},
};
</script>
<template>
<FormulateInput
type="group"
name="rangeMessages"
:minimum="0"
repeatable>
<!-- See https://vueformulate.com/guide/inputs/types/group/#slots -->
<template #remove="{removeItem}">
<button ref="closeButton" #click.prevent="removeItem"/>
</template>
</FormulateInput>
</template>
Research - Vue-Formulate's Docs
In Vue-Formulate's docs on input with type="group"'s props and slots, there is a minimum prop. I've set that to zero, but that doesn't change the initial appearance. I do see multiple slots, but I'm not quite sure which one to use or if I could use any of them to achieve what I want; it seems like default, grouping, and repeatable might be useful in preventing the initial display of the first group.
Research - Vue-Formulate's Tests
I see that FormulateInputGroup.test.js tests that it('repeats the default slot when adding more', so the default slot is the content that gets repeated. According to the docs, the default slot also receives the index as a slot prop, so that could be useful.
Research - Vue Debugger
The item which I want to initially remove is at FormulateInputGroup > FormulateGrouping > FormulateRepeatableProvider > FormulateRepeatable > FormulateInput:
When I remove the initial item to match the desired initial layout, the group hierarchy changes to:
<FormulateInput><!-- the input type="group" -->
<FormulateInputGroup>
<FormulateGrouping/>
<FormulateAddMore>...</FormulateAddMore>
</FormulateInputGroup>
</FormulateInput>
Based on this change, I would expect that I need to modify FormulateGrouping to get the desired initial appearance, but I haven't found in the source what items are available to me there.
Research: A hack that seems to UPDATE: USED TO work
This hack worked in v2.4.5, but as of 2.5.2, it no longer works. See top of post for an updated workaround.
In the mounted hook, when I first render the form, I can introspect
into the formValues passed to v-model to see if the group lacks an
initial elements that is filled out. If so, then I can make use of a
ref msgs on the FormulateInput of type group to then call
this.$refs.msgs.$children[0].$children[0].removeItem(), which
triggers an initial remove of the (empty) item. This is super hacky,
so I'd prefer a better way, but it kind of works. The only problem is
that it validates the fields when clicking on the button, before any
input has been entered.
This is a fair question, and Vue Formulate used to support the behavior of just using an empty array to begin with, however it became clear that it was confusing to users that their fields would not show up without an empty object [{}] when they bound the model, so a change was made to consider an initial value of an empty array an "empty" field and pre-hydrate it with a value. Once that initial hydration is completed however, you can easily re-assign it back to an empty array and you're good to go. This is easily done in the mounted lifecycle hook:
...
async mounted () {
await this.$nextTick()
this.formData.groupData = []
}
...
Here's a fork of your code sandbox: https://codesandbox.io/s/vue-formulate-group-with-button-to-start-forked-32jly?file=/src/components/Reproduction.vue
Provided solutions weren't working for me but thanks to previous answer I've managed to find this one:
mounted(){
Vue.set(this.formData, "groupData", [])
},
which does same effect as
data(){
formData: {
groupData: [],
},
},
mounted(){
this.formData.groupData = []
},

Multiple Select in Select2 for VueJS. Sometimes return blank selected

Here Select2.vue comp source: https://pastebin.com/zKvaaDS9
Here ItemDetail.vue page source: https://pastebin.com/gL7a75hH
I use select2 in VueJS, which I created based on VueJS documentation and other resource. Everything seems work fine. But, sometimes tag or selected is not selecting correct which after I debug I found it sometimes undefined, [], or ''(empty).
I highly suspect this because async. Here what I want to archive: I want to show all units that support an item.
AJAX: api for get units
AJAX: api for get an item detail. Data item, include current units that support for this item.
Load select2 units based on item.units and units. Sometimes is workfine but sometimes is not.
Here some explaination what I do
// Calling item detail(ASYNC)
// api/item/detail/:id
var item = getItemDetail()
// Calling ajax units(ASYNC)
// api/category?group_by=units
var units = getUnits();
Hopely would arrange select2 type=tags would render correctly based on item.units(for v-model) and units for available in dropdown menu.
It seems not render correctly when:
Select2 render without options and value(ajax not complete for item and units)
AJAX Item finish first
AJAX Units finish last
When I debug, it seems select2 updated v-model with empty value because nothing match in select items with v-model value. So for example:
item.units = [1,2,3];
units = []; // Because not ready yet
$(this.$el).val(); // Return empty array or something
I tried correct the order something like this:
Select2 render without options and value(ajax not complete for item and units)
AJAX Units finish last, wait until finish and go to step 3
AJAX Item finish first
This work perfectly.
The question is: Is it possible to run async and render select2 perfectly without thinking about ajax order or wait to complate first?
I think you could solve this in two ways.
You could store the value in a separate property of your select2 wrapper component, when the options changed, make sure to reapply your value to select2. This way, even if select2 sets value to empty, you will reset the original value after ajax finishes loading.
When the value changes and select 2 have no options loaded, create one option just so it won't set the value to empty. When your options finish loading, it will replace all items from select2 with ajax loaded ones.
// Option 2: this would go inside Watch for value property
// Note that this works for a single select, for tags, the idea is the same
// but you will have to create all the tags.
const select = $(this.$refs.select2);
if (select.find(`option[value="${value}"]`).length === 0) {
select.append(
$("<option>")
.val(value)
.text(this.displayText || "")
);
}
I've used option 2 at work (sorry, can't share the full code) and it worked. If it is unclear for you with just the snipped above, I can try and provide more details.