HTML
<span :style="{ display : displayTitle }" #dblclick="showInput()">
{{ node.title }}
</span>
<input :style="{ display : displayTitleInput }" type="text"
#blur="hideInput1" #keydown="hideInput2"
#input="changeTitle(node.id , $event.target.value)"
:value="node.title">
JS
data() {
return {
displayTitle: "inline-block",
displayTitleInput: "none"
};
},
showInput() {
this.displayTitle = "none"
this.displayTitleInput = "inline-block"
},
hideInput1() {
this.displayTitle = "inline-block"
this.displayTitleInput = "none"
},
hideInput2(event) {
if (event.keyCode === 13) {
this.hideInput1()
}
},
I am a beginner Japanese web developer. I am not good at English, sorry.
HTML is in "v-for" (v-for="node in list").
When double click text, it turns to <input>.
I want to make it possible to focus on input when it appears.
I tried this but it didn't work.
HTML
<span :style="{ display : displayTitle }" #dblclick="showInput(node.id)">
{{ node.title }}
</span>
<input :ref='"input_" + node.id' :style="{display:displayTitleInput}" type="text"
#blur="hideInput1" #keydown="hideInput2"
#input="changeTitle(node.id , $event.target.value)"
:value="node.title">
JS
showInput(id) {
this.displayTitle = "none"
this.displayTitleInput = "inline-block"
this.$nextTick(this.$refs["input_" + id][0].focus())
},
There was no error on console, but didn't work.
Your primary problem is that $nextTick takes a callback function but you are executing
this.$refs["input_" + id][0].focus()
immediately. You could get your code working correctly with
this.$nextTick(() => {
this.$refs["input_" + id][0].focus()
})
However, I think you'll run in to further problems and your code can be made much simpler.
One problem you'll find is that all your node inputs will become visible when double-clicking on any of them due to your style rules.
You could instead store an "editing" flag somewhere either on the node or in a separate object.
Below is an example that simplifies your code by...
Using the array-like nature of ref when used within a v-for loop, and
Using the enter modifier on your #keydown event binding
new Vue({
el: '#app',
data: {
list: [
{id: 1, title: 'Node #1'},
{id: 2, title: 'Node #2'}
],
editing: {}
},
methods: {
showInput(id, index) {
this.$set(this.editing, id, true)
this.$nextTick(() => {
this.$refs.input[index].focus()
})
},
hideInput(id) {
this.editing[id] = false
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<ul id="app">
<li v-for="(node, index) in list">
<span v-show="!editing[node.id]" #dblclick="showInput(node.id, index)">
{{ node.title }}
</span>
<input v-show="editing[node.id]" type="text"
ref="input" :value="node.title"
#blur="hideInput(node.id)" #keydown.enter="hideInput(node.id)">
</li>
</ul>
The way you use this.$nextTick(); is incorrect. You should pass it a callback function.
this.$nextTick(function () {
this.$refs["input_" + id].focus()
})
https://jsfiddle.net/un65e9oc/7/
I'm not however sure how that array access is working for you, because as I notice, $refs is an object with the keys referring to the ref name.
[Edit: Thanks to #Phil's comment, above is clear.]
The above is the correct solution for your problem. Since you have already got that answer, I'll add something other than that.
The reason why you see this behavior is that because the reference you hold in $refs doesn't get updated when you change the visibility of the text box in your showInput() method. So when you call this.$refs["input_" + id].focus();, it's actually trying to set focus on a hidden element (because the current reference is not updated).
That's why you need to call the $nextTick() to update it. But if you wanted a quick fix to your problem, without calling $nextTick(), you could update it manually like this:
this.displayTitleInput = "inline-block"
this.$refs["input_" + id].style.display = this.displayTitleInput
this.$refs["input_" + id].focus();
This would also work :) Hope it helps!!
if you want to set focus after click on something and show input text box with set focus with vue js
directives: {
focus: {
// directive definition
inserted: function (el) {
el.focus()
}
}
}
and use custom directive for it. In case you need it should work on click then set with click
directives: {
click: {
// directive definition
inserted: function (el) {
el.focus()
}
}
}
and use it
<input v-focus> or <input v-click>
enter code here
The autofocus attribute is your friend:
<input type="text" autofocus />
It's work for me when we validate the form and want to set dynamically focus on each field
this.$validator.validateAll("FORM_NAME").then(valid => {
var errors = this.$validator.errors;
if (valid) {
console.log('All Fields are valid')
} else {
const errorFieldName = this.$validator.errors.items[0].field;
console.log(errorFieldName);
this.$refs[errorFieldName].focus();
}
});
Related
The later versions of Vue JS 2 are complaining about using v-model on a button. It shows the error:
<button v-model="settings.primary_color">: v-model is not supported on this element type. If you are working with contenteditable, it's recommended to wrap a library dedicated for that purpose inside a custom component.
Here is my button:
<button type="button" class="colorpicker" v-model="settings.primary_color" :data-color="settings.primary_color" :style="{'background-color' : settings.primary_color}"></button>
Is it possible to achieve two way data binding using something like the data-color property rather than an input value?
Off course it is possible, that's the goal of modern JS frameworks.
What the error says is that binding data on "button" tag is not possible. The reason is that "button" is a native html element.
So if you want to bind datas on a button, just create a new component like "my-button", then you could achieve ti !
<my-button
class="colorpicker"
:type="button"
v-model="settings.primary_color"
:data-color="settings.primary_color"
:style="{'background-color' : settings.primary_color}"
></my-button>
By better understanding your problem, I propose you this solution
Just change your button by an input[type="color"]
The component :
<template>
<div>
<input type="color" v-model="color"/>
{{ color }}
</div>
</template>
<script>
export default {
name: "ColorPicker",
data: () => ({
color: "#000000",
}),
};
</script>
And because we have now an input, v-model is possible
EDIT :
So, stay with your button (i guess, that your colorpicker class is important because you already did some work on it)
According to VueJs documentation, to create your own two ways data binding, you have to get the prop "value" and emit the signal "input" on the component
(I just made a generateRandomColor() function to be faster)
Create a button component :
<template>
<div>
<button class="colorpicker" #click="triggered">Click me</button>
</div>
</template>
<script>
export default {
name: "ColorPicker",
props: {
value: String,
},
data: () => ({
value_: "#000000",
}),
methods: {
triggered: function () {
this.value_ = this.getRandomColor();
this.$emit("input", this.value_);
},
getRandomColor: function () {
var letters = "0123456789ABCDEF";
var color = "#";
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
},
},
};
</script>
Then, call it in your parent :
<color-picker v-model="colors.primary"></color-picker>
For your use, you just have to update the value_ property in custom button component and value will automatically update in parent
When trying to create a login form with outlined text fields in Vutify, the chrome autocomplete overlap with labels,
<v-text-field
v-model="email"
label="e-mail"
name="email"
outlined
prepend-icon="mdi-account"
type="text"
required
>
</v-text-field>
you can regeneare here please fill and submit, then go back.
This is how I have fixed this issue.
It seems our main problems are the following:
The autofill from Chrome, at loading page, not made interface react, letting the design like in your image.
So at the time of injection, we should do a fix ourself but no event, from Chrome, can inform us when login/password are auto-filled.
It's interesting to see any click from the browser window FROM USER automatically inform reactivity and all work fine again but it's not work FROM trigger/dispatch internal way.
So first, we need to find a way to react after login/password autofill.
And second, we need to fix ourself the design because only a FROM USER action made the design work fine again.
1. React after autofilling at loading page
Like Harkness mention it, we can try to check :-webkit-autofill at regular interval while X second after code was mounted to see if an autofilling was injected (work fine for Chrome/Firefox/Edge from my test)
Another solution is to use the animationstart event (see here: https://github.com/material-components/material-components-web/issues/4447#issuecomment-580401216)
I use the first solution:
export default {
//...
data() {
return {
//...
autofillFix: false,
}
},
//...
mounted() {
this.autoLoginCheckingInterface()
},
//...
autoLoginCheckingInterface() {
// each 100ms we check if the issue was produced
let intervalDetectAutofill = setInterval(() => {
if (
// we target at least one of the stuff that will be affected by autofill
// to do our checking
document.querySelectorAll('input[type="password"]:-webkit-autofill')
.length > 0
) {
// and we inform the system about the issue if it is produced
this.autofillFix = true
// we stop to check if issue was produced
clearInterval(intervalDetectAutofill)
}
}, 100)
// if after 3s nothing appear, means no autofill was made
setTimeout(() => {
if (intervalDetectAutofill) {
clearInterval(intervalDetectAutofill)
intervalDetectAutofill = null
}
}, 3000)
},
//...
}
<!--
we will inject `.autofill-fix` class to be able fix design ourself at time of this bug occur
-->
<v-text-field
...
:class="{ 'autofill-fix': autofillFix }"
...
label="Email address or username"
...
dense
outlined
#focus="autofillFix = false"
/>
<!--
we use #focus to let the normal behavior take again the lead
because we know this USER ACTION will made Chrome work well again
-->
<v-text-field
...
:class="{ 'autofill-fix': autofillFix }"
...
label="Password"
type="password"
...
dense
outlined
#focus="autofillFix = false"
/>
2. Fix ourself the design
We can see what are the change when v-text-field is filled. Without content, we can see this:
And after autofilling, we can see this:
So from the red part, we can see the following code need to be injected at time
of .autofill-fix presence to fix the design in the proper way
.autofill-fix.v-text-field--outlined.v-input--dense .v-label {
left: -28px!important;
transform: translateY(-16px) scale(.75);
}
Note: You need change the CSS selector if you not use outlined or dense. Be careful about the specificity of selector https://specificity.keegan.st/. In fact, you need adapt the fixed change to your design
Another way is to defined like #elazard suggest here an autofill variable like this
data () {
return {
login: null,
password: null,
autofill: false,
intervalDetectAutofill: null
}
},
<v-text-field
v-model="password"
type="password"
label="Password"
:placeholder="autofill ? ` ` : null"
/>
With the solution given by #adam-reis, in the mounted() of the login page
mounted () {
// search for autofill every 100ms
this.intervalDetectAutofill = setInterval(() => {
if (document.querySelectorAll("input[type=\"password\"]:-webkit-autofill").length > 0) {
this.autofill = true
}
}, 100)
// clean interval if needed after 3s
setTimeout(() => {
if (this.intervalDetectAutofill) {
clearInterval(this.intervalDetectAutofill)
this.intervalDetectAutofill = null
}
}, 3000)
},
And of course setting autofill to false if user input
watch: {
password () {
this.autofill = false
},
autofill () {
// clean interval if autofill detected or user input
if (this.intervalDetectAutofill) {
clearInterval(this.intervalDetectAutofill)
this.intervalDetectAutofill = null
}
}
},
I believe I've achieved a good result with very generic few lines of coding.
mounted() {
setTimeout(() => {
const els = document.querySelectorAll("input:-webkit-autofill")
els.forEach((el) => {
const label = el.parentElement.querySelector("label")
label.classList.add("v-label--active")
})
}, 500)
},
If the browser autofill the v-text-field, this code will add the "active" class to the Label. The v-text-field behaves have no change.
ok so what i did is something like this :
on the input
:placeholder="!autofilled ? ' ' : ''"
in the script
data() {
return {
form: {
email: '',
password: '',
},
error: null,
autofilled: false,
};
},
watch: {
'form.email'() {
this.autofilled = true;
},
},
What it does : basically setting placeholder to one blank space always "raises" the label. the unfortunate side is that setting it statically will make the label unable to go back down even if you empty the input after it is filled. so what i did is make the placeholder dynamic and only set it as a blank space before any change is made to the input after that placeholder goes back to nothing.
it isnt perfect because on initial load before the user has a password saved the labels will be raised but i havent found anything much better than that.
The autofill feature on browsers usually works by straight away setting the value of the fields in question. In this case, the label of the fields, moves out of the way only when the input field is focused, and stays away when it blurs with a value in the field. In case of autofill, the focus event isn't triggered, so the label stays where it is.
To fix this behaviour, you would have to (or get someone to) make changes in Vuetify.
You could give your input an id and read the input's value when the component is mounted and if it's anything else than empty then you could set your data value to that value that the input is holding, that way the label will go up as you would expect. Based on your more recent comments it seems like you would also need to wait for the DOM to be updated so the best thing we can do is to do our check with the help of nextTick:
mounted() {
this.$nextTick(() => {
const emailValue = document.getElementById('email').value;
this.email = emailValue || '';
});
}
I'm wondering what I am doing wrong here, from what I can see this is the solution: Vue: method is not a function within inline-template component tag
However the method is still not triggering.
<b-table
:items="filtered"
:fields="fields"
sort-icon-left
responsive="sm"
#card-click="setUpdate"
>
<template v-slot:head()="data">
<span class="text-info">{{ data.label.toUpperCase() }}</span>
<button #click="$emit('card-click', data)">filter</button>
<input
v-show="data.field.showFilters"
v-model="filters[data.field.key]"
:placeholder="data.label"
/>
</template>
</b-table>
methods: {
setUpdate(field) {
console.log("hello");
console.log(field);
this._originalField = Object.assign({}, field);
field.showFilters = true;
}
}
Update
So the #click allowed me to to trigger the event but this lead to the table wouldn't update with the changed data with showFilters. Thanks to MattBoothDev I found event-based-refreshing-of-data, however this oddly now prevents the data from changing. I.e. if field.showFilters is true it's true if I click the button.
methods: {
setUpdate(field) {
this._originalField = Object.assign({}, field);
field.showFilters = !field.showFilters;
this.refreshTable();
console.log(field.showFilters);
},
refreshTable() {
this.$root.$emit("bv::refresh::table", "my-table");
}
}
It looks like you're using Bootstrap Vue?
What you're essentially doing here is putting a listener on the <b-table> tag for card-click but that event is essentially not happening within a child component of <b-table>.
Regardless, I'm not even sure you need the event.
<button #click="$emit('card-click', data)">filter</button>
can easily just be
<button #click="setUpdate(data)">filter</button>
EDIT:
It is good practice to use MVVM for Vue.js as well.
Rather than: #click="$emit('card-click', data)"
Should be: #click="onFilterClicked"
Then:
methods: {
onFilterClicked (data) {
this.$emit('an-event', data.some.property)
}
}
This will make testing your code a lot easier.
I have a component called TextInput.vue, and inside I created a div.
<div ts-text-input :ts-text-input-filled="setFilledAttribute && !!value"
:ts-text-input-not-valid="!valueValid">
<input :value="value" #input="setValue" #keyup.enter="enterClicked" :placeholder="placeholder" :title="title">
what I wanted to do now is that to disable some spaces inside the input box so that the user is unable to type in with spaces/spacebar (like, e.g., username input box)
Here is what I have done; I try to use the function trim(), but it seems I can't still fix it.
in the computed function
computed: {
value: function() {
const {valueGetter, valueGetterOptions} = this,
getter = this.$store.getters[valueGetter];
value.trim();
return valueGetterOptions ? getter(valueGetterOptions) : getter;
},
Any hints would be helpful. thanks. (sorry for my bad English)
You can directly prevent that the user adds a white space to your input field. preventDefault() tells the user agent that the default action should not be taken as it normally would be.
<input #keydown.space="(event) => event.preventDefault()">
Or to make it even shorter (as Sovalina pointed out):
<input #keydown.space.prevent>
To prevent spaces on all input events - keypress, paste or drag and drop text:
const removeEventSpaces = e => {
e.preventDefault();
const left = e.target.value.substring(0, e.target.selectionStart);
const right = e.target.value.substring(e.target.selectionEnd, e.target.value.length);
const pasted = (e.dataTransfer || e.clipboardData).getData('text').replace(/ /g, '');
e.target.value = left + pasted + right;
}
<input #keydown.space.prevent #paste="removeEventSpaces" #drop="removeEventSpaces"/>
#keydown.native.space didn't work for me. #keydown.native.space.prevent did.
In this case, you can use Regular Expressions
value.replace(/\s/, '')
or to be sure the data is stored without any capital letters
value.replace(/\s/, '').toLowerCase()
u could use get and set
var inputData = ''
export default {
name: 'yourFromComponent',
computed: {
inputValue: {
get () {
return inputData
},
set (value) {
inputData = value.replace(/\s/g,'')
}
}
}
}
if you use vuex just change the inputData to your store referenz
I'm kinda a noob at vue js, however I can't seem to understand why the allValid event isn't being emitted from the following code: (snippet here, full code in the js fiddle)
http://jsfiddle.net/sTX7y/674/
Vue.component('password-validator', {
template: `
<ul>
<regex-validation regex='.{6,}' v-on:valid='v => { isLongEnough = v }' :input='input'>
Is Long Enough
</regex-validation>
<regex-validation regex='[A-Z]' v-on:valid='v => { hasUppercase = v }' :input='input'>
Has Upper
</regex-validation>
</ul>
`,
props: [ 'input' ],
data: function(){
return {
isLongEnough: false,
hasUppercase: false,
}
},
computed:{
isValid: function(){
var valid = this.isLongEnough && this.hasUppercase;
this.$emit('allValid', valid);
return valid;
}
}
});
When viewing this using the vue chrome extension I can clearly see that isLongEnough and hasUppercase both flip from true to false, (and the validation is reflected on the output). It's just that the last isValid computed function just never seems to run...
Thanks for the help and if you see any other noob mistakes feel free to chime in on how I could do this better.
The computed function is defined correctly in the password-validator component. The only problem is you have referenced it ouside of the component scope. i.e. {{ isValid }} is in the html outside of the template. To correct this you can change the password-validator template thus:
template: `
<ul>
<regex-validation regex='.{6,}' v-on:valid='v => { isLongEnough = v }' :input='input'>
Is Long Enough
</regex-validation>
<regex-validation regex='[A-Z]' v-on:valid='v => { hasUppercase = v }' :input='input'>
Has Upper
</regex-validation>
Is Valid: {{ isValid }}
</ul>
now that the reference to the computed property isValid is inside the template, it should update accordingly.
Updated the fiddle here: jsfiddle