In a textarea, i want to customize the line break action. the default behavior is defined by click on enter, i want to program the line break action to be done when i click on Alt+Enter.
you can use (keydown.alt.enter) and (keydown.enter) events. If your textarea is using [(ngModel)] you can has some like
<textarea #textArea #textAreaModel="ngModel"
[(ngModel)]="value"
(keydown.alt.enter)="altEnter(textArea,textAreaModel)"
(keydown.enter)="$event.preventDefault();enter()">
</textarea>
See that you pass to the function "altEnter" the "textArea" (the HTMLElement) and the "textAreaModel" (the "model")
If you use ReactiveForms you can has some like
<textarea #textArea [formControl]="control"
(keydown.alt.enter)="altEnter(textArea,control)"
(keydown.enter)="$event.preventDefault();enter()">
</textarea>
See that in this case you pass to the function the "textArea" (the HTMLElement) and the formControl "control"
Your functions are like
enter(){
console.log("enter")
}
altEnter(element:any,control:any){
const start=element.selectionStart; //get teh Selection start and end
const end=element.selectionEnd;
let value:string[]=element.value.split('')
value.splice(start,end-start,'\n')
if (control.control)
control.control.setValue(value.join('')) //give value to the ngModel
else
control.setValue(value.join('')) //give value to the formControl
setTimeout(()=>{
element.selectionStart=element.selectionEnd=start+1 //position the cursor
})
}
You can see both cases in this stackblitz
Related
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 two inputs that use the same function to set a new value like this
<b-input
v-model="ownerProps.approver2ExtraCost"
#blur="onClick($event)"
class="inputBuefy"
></b-input>
</div>
<b-input
class="inputBuefy"
#blur="onClick($event)"
v-model="ownerProps.approver3ExtraCost"
></b-input>
</div>
they have different v-models but I'm using the same function to change their values on my methods property.
onClick(e) {
console.log(e.target.value);
e.target.value = "dfg";
}
The thing is, when I change one of the following inputs it gets the previously value that I typed.
for example:
if typed 'ABC' in the first input
replaces it with 'dfg'
But when I go to the next input, and I type something like 'HIJ'
the first input get its previous value = 'ABC'
But it should have remained with 'dfg'
Your problem is that you are setting the value of the element and not changing the value of the bounded variable which you should do.
so when vue "refreshes" it updates the input
do this instead
<b-input
v-model="ownerProps.approver2ExtraCost"
#blur="onClick(2)"
class="inputBuefy"
></b-input>
</div>
<b-input
class="inputBuefy"
#blur="onClick(3)"
v-model="ownerProps.approver3ExtraCost"
></b-input>
</div>
and change your function to
onClick(id) {
if (id === 2) ownerProps.approver2ExtraCost = "dfg";
if (id === 3) ownerProps.approver3ExtraCost = "dfg";
}
you should ideally have those elements in a array instead and send the index of what you want to change
I am trying to remove everything except numbers, colon (:) and a new line. After having entered values with colon say 12:30, when I press enter, I am not able to submit using custom directives. So as of now, I am able to allow numbers and colon but not the enter key.
My custom directive looks something like this :
export class NumberDirective {
constructor(private _el: ElementRef) { }
#HostListener('input', ['$event']) onInputChange(event) {
const initalValue = this._el.nativeElement.value;
this._el.nativeElement.value = initalValue.replace(/[^0-9:\n]*/g, '');
if ( initalValue !== this._el.nativeElement.value) {
event.stopPropagation();
}
}
Please let me know how this can be fixed and thanks in advance.
Bellow I show two approaches, one by updating the text after it is changed, the second by preventing the default behavior of specific keys. The first is more general, but for this case I prefer the second because it prevents the text from going there. The other detail is that by the piece of code you shared I don't understand why you call stopPropagation
const [txt1, txt2] = document.querySelectorAll('textarea')
txt1.addEventListener('input', () => {
txt1.value = txt1.value.replace(/[^0-9:\n]+/g, '')
})
txt2.addEventListener('keypress', (event) => {
if(!(/[0-9:]|Enter/i).test(event.key)){
event.preventDefault();
}
})
textarea { min-height: 5cm }
<textarea placeholder="Type here"></textarea>
<textarea placeholder="Type here"></textarea>
This issue hasn't been touched in a while but i'm running into something confusing on my end when using a computed property for a textarea value.
I have a textarea where you give input text and on input it updates the input text in vuex:
<textarea
ref="inputText"
:value="getInputText"
#input="setInputText"
class="textarea"
placeholder="Your message goes in here"
></textarea>
On the click of a button to translate the text I call a handleInput method.
handleInput() {
this.$store.dispatch("translateEnglishToRussian");
},
In my store I have my translateEnglishToRussian action:
translateEnglishToRussian({ commit }) {
const TRANSLATE_API = "https://XXXXXXXX.us-east-1.amazonaws.com/prod/YYYY/";
const data = JSON.stringify({ english: this.state.inputText });
this.$axios
.$post(TRANSLATE_API, data)
.then(data => {
commit("setOutputText", data.translatedText);
commit("setMP3Track", data.mp3Path);
})
.catch(err => {
console.log(err);
});
}
I see it call setOutputText mutation with the payload of translated text, in my vue dev tools I see the correct state with the translated text. However, my second text area that's being used purely to display the translated text never updates!
Output textarea:
<textarea
disabled
ref="outputText"
:value="getOutputText"
class="textarea"
></textarea>
Its value is bound to a computed property called getOutputText:
getOutputText() {
return this.$store.state.outputText;
}
What am i doing wrong here! Any advice is appreciated. I thought this should be fine since i'm using commit in my vuex action in a synchronous way (inside the then() block).
Edit: I have the same result if I also try using v-model. The initial output text from vuex state is rendered there on page load. When I translate, I see the update in Vue Dev Tools correctly, but the text in the text area never re-renders.
Edit #2: Here is my setOutputText mutation:
setOutputText(state, payload) {
console.log(`state - set output - ${payload}`);
state.outputText = payload;
},
After looking at the vue docs for Multiline text, you should replace :value="getOutputText" with v-model="getOutputText".
Because it's computed property, to use it in v-model you need to add get and set to your computed property
<textarea
disabled
ref="outputText"
v-model="getOutputText"
class="textarea"
></textarea>
EDIT: Per #Stephen Tetreault comment, v-model didn't work for him, but :value did solve the problem at the end.
computed: {
getOutputText: {
// getter
get: function () {
return this.$store.state.outputText;
},
// setter
set: function (newValue) {
// there is no need to set anything here
}
}
}
In the spec for my app it says (developerified translation): When tabbing to a time element, it should update with the current time before you can change it.
So I have:
<input type="time" ref="myTimeEl" onFocus={this.handleTimeFocus.bind(null, 'myTimeEl')} name="myTimeEl" value={this.model.myTimeEl} id="myTimeEl" onChange={this.changes} />
Also relevant
changes(evt) {
let ch = {};
ch[evt.target.name] = evt.target.value;
this.model.set(ch);
},
handleTimeFocus(elName, event)
{
if (this.model[elName].length === 0) {
let set = {};
set[elName] = moment().format('HH:mm');
this.model.set(set);
}
},
The component will update when the model changes. This works well, except that the input loses focus when tabbing to it (because it gets rerendered).
Please note, if I would use an input type="text" this works out of the box. However I MUST use type="time".
So far I have tried a number of tricks trying to focus back on the element after the re-render but nothing seems to work.
I'm on react 0.14.6
Please help.
For this to work, you would need to:
Add a focusedElement parameter to the components state
In getInitialState(): set this parameter to null
In handleTimeFocus(): set focusElement to 'timeElem` or similar
Add a componentDidUpdate() lifecycle method, where you check if state has focusedElement set, and if so, focus the element - by applying a standard javascript focus() command.
That way, whenever your component updates (this is not needed in initial render), react checks if the element needs focus (by checking state), and if so, gives the element focus.
A solution for savages, but I would rather not
handleTimeFocus(elName, event)
{
if (this.model[elName].length === 0) {
let set = {};
set[elName] = moment().format('HH:mm');
this.model.set(set);
this.forceUpdate(function(){
event.target.select();
});
}
},
try using autoFocus attrribute.
follow the first 3 steps mention by wintvelt.
then in render function check if the element was focused, based on that set the autoFocus attribute to true or false.
example:
render(){
var isTimeFocused = this.state.focusedElement === 'timeElem' ? true : false;
return(
<input type="time" ref="myTimeEl" onFocus={this.handleTimeFocus.bind(null, 'myTimeEl')} name="myTimeEl" value={this.model.myTimeEl} id="myTimeEl" onChange={this.changes} autoFocus={isTimeFocused} />
);
}