vue focus goes back to the original input - vuejs2

I have custom directive focus and I only applied it on my first input
input v-focus v-model="fields.code" type="text
input v-model="fields.name" type="text"
the focus is doing ok, but when I try to edit the next field the focus changes on the first field
Here is my directive:
componentUpdated: function(el, binding){
el.focus();
}
Any suggestion on how can I make this work?

It's because the componentUpdated hook is being called every time the first input is being updated including when you're leaving it and trying to edit the second one. Use the inserted hook instead:
inserted: function(el, binding){
el.focus();
}

I have no tested but just reading Lukasz suggestion looks right. In fact there an example in vuejs2 documentation in custom directive guide section.

Related

How to sort vuetify datatable only on header click?

In a v-data-table, I have one column with a simple text field, this column containing the text field is sortable. The problem is that when I change the value in the text field, the data is immediately re-sorted and in my case the lines change and in the worst case the line change and the focused input changes too, like in this example: codepen reproduction
For reproduction:
click on the iron header to sort the iron column
change one input
see that the line changed its position and that you're not anymore in the same field
Expected behavior:
sort the iron column
change one input
see that the line didn't change its position and you can keep modify the input
click sort again to reorder the data once you're done.
Is there a way of behaving like in the expected behavior?
Thank you very much for the answers
It looks like you should use a different event on the <v-text-field> component to listen for changes instead of using v-model to bind changes automatically to props.items.iron. Text Field Event Docs here
You could do something like using the blur event so it only updates when your user focuses away from the text field:
<v-text-field
#blur="updateIron"
:rules="[max25chars]"
label="Edit"
single-line
counter
autofocus
></v-text-field>
then in your JS file you'd add a method like this:
methods: {
// ...other stuff here
updateIron(val) {
this.item.iron = val
}
}
Try experimenting with different events to update your values at the appropriate time.
So I found the answer to that, which consist of redefining the header:
I use the slot header.iron in order to redefine a sortIron method only on click : basic demo
What I'll have to do next is to redefine arrows, style, 'asc' and 'desc'
I'm still open to other ways of doing it!
But right now I can follow my wanted behavior which is :
sort the table via the iron column
change one field
press tab, on the next field enter a value
repeat previous step until you're done
finally re-sort the table only by clicking the header

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 = []
},

Vue: My initial data value is not accessible when used as a function param

I am having an issue when triggering the click handler in my button here. The error comes back as TypeError: Cannot read property 'discountProduct' of null when I click the button. I know for a fact that this.discountProduct.id has a value by inspecting it from the Vue Tools and by knowing that the button is rendering to begin with since it only conditionally shows if the id > 1.
I alternatively tried to remove this but it still throws the same error.
I also tried manually just inserting the product id as the param and that works so I am not sure what the issue is at this point.
<button v-if="this.discountProduct.id >= 1"
type="button"
v-on:click="addToCart(this.discountProduct.id, true)"
Is you button in the <template> tags?
If so, you do not need to use this delete from the click argument and v-if. However that should only throw a warning.
Can you also post the code for the method addToCart?
If you are not setting the full discountProduct object you will need to make sure setting the id is reactive. Like the example below:
this.$set(this.discountProduct, 'id', 1)
Nested Object/keys are not reactive without using $set. See https://v2.vuejs.org/v2/guide/reactivity.html for more details.
Hope this helps

VueJS Sibling Component Reload Challenges

I'm having a hard time figuring out how to get a component to reload after a sibling is updated. For instance, when I make a selection in the first component, I want the second component to "refresh" to account for the newly selected "state" data:
<c-select
dataEndpoint="/states.json"
errorMessage="Some error message..."
id="state"
message="Some message"
v-model="form.state"
:v="$v.form.state" />
Has the following dependency, so to speak:
<c-select
:dataEndpoint="`/${form.state}.json`"
errorMessage="Some other error message..."
id="county"
message="This field uses a local data source and **is required**"
v-model="form.county"
:v="$v.form.county" />
Once a state is selected or changed, I need to "dynamically" reload the appropriate endpoint to show the counties for that state in a second component. Right now, the only way I can make this work is with a v-if="form.state hack. But, if a user attempts to change the state again, the changes do not take effect in the "county" component. I would appreciate any help or advice on how to best solve this issue.
Here is a link to my vue codebase in Code Sanbox
Ok. Here is the result:
mounted is executed only ONCE, so after dataEndpoint has changed NO UPDATE action performed, that's why you should add watch to your c-list component and check if the entry has changed - update drop-down list:
watch: {
dataEndpoint() {
this.fetchAndSetJsonEndpoint();
}
},
Here is a working version of your code: https://codesandbox.io/s/64mj19r6zw

Add target blank to a link in a props in Vue.js

I use the ReadMore plugin to crop articles in a page. The plugin provides a props to redirect to a http link when the "read more" is clicked. But I need to display the link in a new tab. The props receives the link in a string.
I tried several ways to add the target blank attribute to this string before passing it to the props. With no success. Like:
http://www.example.com/page-to-see.html target=_"blank"
I used it with or without quotes but in any case, the link works but the attribute is skipped.
Is there a way to intercept this and add a target blank later?
I saw in other questions the use of router-link but I don't know how to manipulate the props content in the first place.
Any clue would be warmly welcomed
Edit: adding more code to give a clearer explanation of the problem I try to solve:
In the template:
<read-more more-str="read more" :text="dwId.texte" :link="dwId.link" less-str="less" :max-chars="540"></read-more>
I get the values from a DB with Axios. The props are specified by the plugin documentation.
The :link must be a string and it's what it gets from the DB. It works. But as I explained, I need to open in a new tab.
Edit: what I tried:
I try to make a computed property that would add the target blank to a string and use it in the read-more props:
computed: {
target: function() {
return this.dwIds.filter((dwId) => {
return dwId.link + target="_blank"
});
},
}
I have two issues here: first , the result is an object and the props requires a string. Furthermore, the way I add the target blank is not correct but I can't find the right syntax yet.
You need to use it as a directive, and override parts of the initial element you're passing. Otherwise there is no way to "intercept" it. Here's the code to the "component" version that won't do the trick for you.