Given the following code below where <v-input> is a custom input element,
<template>
<v-input id="username" type="text" v-model="username" />
</template>
<script>
export default {
data() {
return {
username: "",
};
},
};
</script>
How would one go about modifying the value of the input element through the browser console? I have tried the following code below but it does not bind to the Vue component.
const element = document.querySelector("#username");
element.value = "foobar"
I've also tried emitting custom events but those don't seem to work for me either. Any advice regarding this would be much appreciated.
I figured it out, I need only dispatch a new input event so that Vue.js catches the value. See below for an example.
const inputBox = document.querySelector("#inputBox");
inputBox.value = "hello";
inputBox.dispatchEvent(new Event('input'));
Related
I'm having trouble to figure out how to update an input element's value from the parent accurately. Below I have a simplified code to explain my problem more clearly. Hope someone can give me a hand to solve this in a correct way!
ParentComponent:
<script setup>
const form = useForm({
payment_number: '',
});
const cardNumberChecker = value => {
const cleaned = value.toString().replace(/\D/g, '');
const trimmed = cleaned.slice(0, 16);
const grouped = trimmed ? trimmed.match(/.{1,4}/g).join(' ') : '';
updateCardNumber.value++; // Current fix
form.payment_number = grouped;
};
<script>
<template>
<InputComponent
:update="updateCardNumber" // Current fix
:text="form.payment_number"
#input-trigger="cardNumberChecker"
></InputComponent>
</template>
InputComponent:
<script setup>
const props = defineProps({
update: { type: Number, default: 0 }, // Current fix
text: { type: String, default: '' },
});
defineEmits(['input-trigger']);
</script>
<template>
<input
:value="text"=
type="text"
#input="$emit('input-trigger', $event.target.value)"
/>
</template>
On input event, cardNumberChecker is filtering and cleaning the input value. The issue is when a character inserted is not allowed, thus not changing form.payment_number variable and hence not updating the InputComponent causing to allow the keyboard inputted character to show in the input field anyway.
As a temporary fix I have added a counter that increments on each keyboard input to cause the InputComponent to rerender.
Is there a proper Vue way to sort this out? Thank you!
I'm trying to figure out how keydown and change events work together in Vue.js (Vue 3). The setup is pretty simple. I have a text input in a component, and I want to be able to enter a number in the text input field. From there, I want to be able to use the arrow keys (up and down) to increment or decrement the value in the input. When the input loses focus, it should trigger #change and then update the model.
This issue I'm having is that when I update the value in the input using #keydown, it doesn't behave the same way that a keypress for a printable character would. Updating the value on #keydown doesn't cause the #change event to be fired when the control loses focus.
I set up an example in the playground if that helps, but here's the code:
App.vue
<script>
import NumberInput from './NumberInput.vue'
export default {
components: {
NumberInput
},
data() {
return {
number: 100
}
}
}
</script>
<template>
<p>
Model should only be updated when focus leaves the text box.
</p>
<p>
This works fine when you type in a number and focus off the text box.
</p>
<p>
However, when you <b>only</b> use the arrow keys to modify a value, the `#change` event doesn't fire.
</p>
<br />
<div style="padding:10px; border:1px solid #aaaaaa">
<NumberInput v-model="number" />
Number: {{ number }}
</div>
</template>
NumberInput.vue
<script>
export default {
props: ['modelValue'],
data() {
return {
number: this.modelValue
}
},
methods: {
incrementOrDecrementNumber: function(e) {
const isUpKey = (e.key === "ArrowUp");
const isDownKey = (e.key === "ArrowDown");
let x = parseInt(this.number, 10);
if (x == NaN)
x = 0;
if (isUpKey || isDownKey) {
if (isUpKey)
x++;
else
x--;
this.number = x;
e.preventDefault();
}
},
numberChanged: function(e) {
this.$emit("update:modelValue", this.number);
}
}
}
</script>
<template>
<div>
Enter a number (press up or down to modify): <input type="text" v-model="number" #change="numberChanged($event);" #keydown="incrementOrDecrementNumber($event);" />
</div>
</template>
Is it possible to update this.number in the component and also have it fire a #change event when the input loses focus?
Thanks!
The problem that you're running into is not due to "how keydown and change events work together in Vue.js", and actually has nothing to do with Vue.js per se, but rather is a basic underlying issue with HTML and JavaScript. A change event will be triggered in an input field if the user changes the data of an input field by direct interaction with the field in the web page as this will set the element's "dirty" flag to true.
In your code, while the user's key press actions are triggering the changes, the input value is being changed internally, via JavaScript, and this will not trigger a change event, whether it is being changed through Vue.js or via plain vanilla JavaScript.
To trigger the change event, you can change the input value by a specific means, such as by using setAttribute(value,...) on the input element as per this answer or by directly calling a change event.
In your own code, #blur="numberChanged($event)" could work as noted in my comments:
<input
type="text"
v-model="number"
#change="numberChanged($event)"
#blur="numberChanged($event)"
/>
or you could make the input's value your component's "model" as per Vue.js documentation. I would also make the input type a number, so as to give the input up-down arrow key responsiveness natively:
<script>
export default {
props: ["modelValue"],
emits: ["update:modelValue"],
},
};
</script>
<template>
<div>
<p>
Enter a number (press up or down to modify):
<input
type="number"
:value="modelValue"
#input="$emit('update:modelValue', $event.target.value)"
/>
</p>
</div>
</template>
I have a range input that has a few things happening onChange. This works as I'd expect with manual click/drag usage. However, when I try to change the value with JavaScript, my onChange event doesn't seem to fire.
Here is my code:
const App = () => {
const [currentValue, setCurrentValue] = useState(0);
const setRangeValue = () => {
const range = document.querySelector("input");
range.value = 50;
range.dispatchEvent(new Event("change", { bubbles: true }));
};
return (
<div className="App">
<h1>Current Value: {currentValue}</h1>
<input
type="range"
min={0}
max={100}
step={10}
onChange={e => {
console.log("Change!");
setCurrentValue(+e.target.value);
}}
defaultValue={0}
/>
<button onClick={setRangeValue}>Set current value to 50</button>
</div>
);
};
And here it is (not) working in CodeSandbox:
https://codesandbox.io/s/divine-resonance-rps1n
NOTE:
Just to clarify. My actual issue comes from testing my component with jest/react testing library. The button demo is just a nice way to visualize the problem without getting into the weeds of having to duplicate all of my test stuff too.
const getMessage = (value, message) => {
const slider = getByRole('slider');
fireEvent.change(slider, { target: { value } });
slider.dispatchEvent(new Event('change', { bubbles: true }));
return getByText(message).innerHTML;
};
When the fireEvent changes the value, it doesn't run the onChange events attached to the input. Which means that getByText(message).innerHTML is incorrect, as that will only update when a set hook gets called onChange. (All of this works when manually clicking/dragging the input slider, I just can't "test" it)
Any help would be great!
The issue is that React has a virtual DOM which doesn't connect directly to the DOM. Note how the events from React are SyntheticEvents. They are not the actual event from the DOM.
https://github.com/facebook/react/issues/1152
If this is for a unit test, create a separate component for the slider and text and make sure they perform as expected separate from each other with props.
For a more in-depth article on how to specifically test a range slider, checkout https://blog.bitsrc.io/build-and-test-sliders-with-react-hooks-38aaa9422772
Best of luck to ya!
I have worked with VueJS for a while, and it is great. I have been able to integrate it with jQueryUI (for an old looking website) and I created a datepicker component, and a datetime picker component as well, both working correctly.
Now I am trying to create a simple phone number component, which simply provides an input with a mask that helps with the phone number format. The plugin for jquery that provides the masking, works correctly on it's own, but if I try to mask an input inside my component, it does not work.
Here is the example code in jsfiddle:
Simple Masked Phone input component for vuejs 2.4.0 - jsfiddle
Javascript:
Vue.component('phone', {
template: '#phone',
props: {
value : {
type : String,
default: ''
},
mask: {
type : String,
default: '(999) 999-9999'
}
},
data: function() {
return {
internalValue: ''
};
},
created: function() {
this.internalValue = $.trim(this.value);
},
mounted: function() {
$(this.$el).find('.phone:eq(0)').mask('(999) 999-9999');
},
methods: {
updateValue: function (value) {
this.$emit('input', value);
}
}
});
var vueapp = new Vue({
el: '#content',
data: {
myphone: ''
}
});
$('.phonex').mask('(999) 999-9999');
HTML:
<div id="content">
<script type="text/x-template" id="phone">
<input type="text" class="phone" v-model="internalValue" v-on:input="updateValue($event.target.value)" />
</script>
<label>Vue Phone</label>
<phone v-model="myphone"></phone>
<br />
{{ myphone }}
<br />
<label>Simple Phone</label>
<input type="text" class="phonex" />
</div>
This is what I see:
Dependencies:
jquery-2.2.4.min.js
jquery.maskedinput.min.js (1.4.1)
vue.js (2.4.0)
Is there anything I am doing wrong here? Thanks.
You don't need the .find('.phone:eq(0)') in your jquery, removing it seems to fix the masking (as shown here), though this does seem to mess with Vue's data binding.
After doing a bit more digging it looks like this is a known issue.
And is addressed here:
Vue is a jealous library in the sense that you must let it completely
own the patch of DOM that you give it (defined by what you pass to
el). If jQuery makes a change to an element that Vue is managing, say,
adds a class to something, Vue won’t be aware of the change and is
going to go right ahead and overwrite it in the next update cycle.
The way to fix this is to add the event handler when you call .mask on the element.
So for example:
mounted: function() {
var self = this;
$(this.$el).mask('(999) 999-9999').on('keydown change',function(){
self.$emit('input', $(this).val());
})
},
Here is the fiddle with the fix: https://jsfiddle.net/vo9orLx2/
I have a Materialize input like so:
<input type="date" className="datepicker" onChange={this.props.handleChange} />
It is being correctly initialised by Materialize, but not firing onChange when the value of the datepicker changes. What am I doing wrong here? This problem seems to extend to all Materialize inputs.
On componentDidUpdate() using a prop id
var elem = document.getElementById('date');
M.Datepicker.init(elem,{
onClose:()=>{
this.state.date = elem.value;
this.setState(this.state)
}
});
I'm pretty sure this solves the caveat if you put it in your componentDidMount component.
If the select is to be re-rendered on state change, this should as well be put in componentDidUpdate
// find the select element by its ref
const el = ReactDOM.findDOMNode(this.refs.ref_to_my_select);
// initialize the select
$('select').material_select();
// register a method to fireup whenever the select changes
$(el).on('change', this.handleInputChange)
To get the value of the datepicker in materialize they provide an onSelect option when initialising the component:
var instances = M.Datepicker.init(
elems,
{
onSelect:function(){
date = instances[0].date;
console.log(date);
}
}
);
https://codepen.io/doughballs/pen/dyopgpa
Every time you pick a date, onSelect fires, in this case console.logging the chosen date.
When you close the datepicker (which is actually a modal), that's when the onChange fires, in this case logging 'onChange triggered' to the console.
that's my solution. I use useRef hook, to identify datepicker input and when onClose is fired, we can capture the object and data value, through ref var.
import React, { useEffect, useState, useRef } from "react";
import M from "materialize-css";
export default function App() {
const fromref = useRef(null); //create reference
const [date, setDate] = useState({ fromdate: "" });
const { fromdate } = date;
useEffect(() => {
let elem = document.querySelectorAll(".datepicker");
M.Datepicker.init(elem, {
firstDay: true,
format: "yyyy-mm-dd",
onClose: function() { // when onclose datepicker, we can get the value and data through the ref
console.log(fromref.current.name, fromref.current.value);
setDate({ [fromref.current.name]: fromref.current.value });
}
});
}, []);
return (
<form class="col s12">
<div class="row">
<div class="input-field col s12">
<input
name="fromdate"
type="text"
class="datepicker"
placeholder="from date"
ref={fromref} //set reference to the input
/>
</div>
</div>
</form>
);
}
If you want to get the value or other attributes you can access them from instaces variable when initialized and then check before submitting your form.
var elems = document.querySelectorAll('.timepicker');
var instances = M.Timepicker.init(elems);
Then in order to get your value before submitting your form can do as follow:
var date = instances[0].el.value;
There are two things which might be stopping the execution of expected behaviour.
If the code which you have displayed question section is from
rendered html tree, then onchnage assigment needs to be called while
assignment itself.
<input type="date" className="datepicker" onChange=this.props.handleChange(event)/>
Note: Previously browser events use to expects event callback
handlers in string format as a value.
In MaterializeCss documentation there is no mentioning of onChange event, this means there cannot be direct way to get it.
https://materializecss.com/pickers.html
It looks like you're using materialize directly in your post but if it is possible, you could try using react-materialize as it wraps all the materialize components such that it's easier to use with React. Using react-materialize would probably be the cleanest way to handle state and event changes as they provide a convenience wrapper around each materialize component.
When using the date picker from react-materialize, you'll need to pass the handleChange method into the options prop like so:
<DatePicker
options={{
...,
onSelect: this.props.handleChange
}}
/>
In the case of using the materialize date picker independently, if you could provide more details on how you're initializing the date picker input, I could provide a more relevant answer. But I'll give it a shot in the dark.
From the materialize docs it looks like you'll also have to pass back some options when you initialize it to handle a callback function when a date is selected.
I've added a JSFiddle that has a working example as well as a code snippet below, notice that when you select a date, 'hello world' is logged in the console, and the date is the first argument passed into the callback.
class Datepicker extends React.Component {
constructor(props) {
super(props)
}
handleChange(date) {
console.log('hello world', date);
}
componentDidMount() {
var elems = document.querySelectorAll('.datepicker');
var instances = M.Datepicker.init(elems, {
onSelect: this.handleChange
});
}
render() {
return (
<div>
<input type="text" className="datepicker" />
</div>
)
}
}
Live Example Fiddle
So to answer your question of how to handle events and setting the state, you just need to pass your handleChange method into the provided options configs depending on how you're using materialize date picker. In regards to integrating with a form, you could use the other callback hooks like onClose to do form validation.