I am looking for the command in a program that filters only when 3 letters are entered - vue.js

I am looking for the command in a program that filters only when 3 letters are entered. Does anyone know what command or code I need to look for to find this?
Maybe it's also a Vue-Command, because my program is written in Vue.js.
Thank you

Pass the search input to a function first then validate the input. If passes the validation, proceed with searching.
Assuming this is your search input
<input v-model="searchInput"/>
Add an input event handler
<input v-model="searchFor" #input="searchHandler"/>
Then validate the search input with searchHandler method
new Vue({
methods: {
searchHandler (text) {
if(text.length > 2){
// Write your code on here
}
}
}
})

You can make use of debouncing to perform some functionality after some time, here you can also add a condition to check for the length of the input and then execute the logic.
const input = document.getElementById("myInput");
function callApi() {
if(input.value.length >= 3) {
console.log("Hello JS")
}
}
function debounce( callback, d ) {
let timeout;
return function() {
clearTimeout(timeout);
timeout = setTimeout( callback, d );
}
}
myInput.addEventListener(
"keyup",
debounce(callApi, 500 )
);
<label for="myInput">Type something in!</label>
<input id="myInput" type="text">

Related

How can I implement v-model.number on my own in VueJS?

I have a text field component for numeric inputs. Basically I'm just wrapping v-text-field but in preparation for implementing it myself. It looks like this.
<template>
<v-text-field v-model.number = "content" />
</template>
<script>
export default {
name: 'NumericTextField',
props: [ 'value' ],
computed: {
content: {
get () { return this.value },
set (v) { this.$emit('input', f) },
},
}
}
</script>
This has generated user feedback that it's annoying when the text field has the string "10.2" in it and then backspace over the '2', then decimal place is automatically delete. I would like to change this behavior so that "10." remains in the text field. I'd also like to understand this from first principles since I'm relatively new to Vue.
So I tried this as a first past, and it's the most instructive of the things I've tried.
<template>
<v-text-field v-model="content" />
</template>
<script>
export default {
name: 'NumericTextField',
props: [ 'value' ],
computed: {
content: {
get () { return this.value },
set (v) {
console.log(v)
try {
const f = parseFloat(v)
console.log(f)
this.$emit('input', f)
} catch (err) {
console.log(err)
}
},
},
}
}
</script>
I read that v-model.number is based on parseFloat so I figured something like this must be happening. So it does fix the issue where the decimal place is automatically deleted. But... it doesn't even auto delete extra letters. So if I were to type "10.2A" the 'A' remains even though I see a console log with "10.2" printed out. Furthermore, there's an even worse misfeature. When I move to the start of the string and change it to "B10.2" it's immediately replaced with "NaN".
So I'd love to know a bunch of things. Why is the body of the text body immediately reactive when I change to a NaN but not immediately reactive when I type "10.2A"? Relatedly, how did I inadvertently get rid of the auto delete decimal place? I haven't even gotten to that part yet. So I'm misunderstanding data flow in Vue.
Lastly, how can I most simply provide a text box that's going to evaluate to a number for putting into my data model but not have the annoying auto delete of decimal places? The existing functionality doesn't auto delete trailing letters so I'm guessing the auto delete of decimal places was a deliberate feature that my users don't like.
I'm not 100% sure of any of this, but consider how v-model works on components. It basically is doing this:
<v-text-field
v-bind:value="content"
v-on:input="content = $event.target.value"
/>
And consider how the .number modifier works. It runs the input through parseFloat, but if parseFloat doesn't work, it leaves it as is.
So with that understanding, I would expect the following:
When you type in "10.2" and then hit backspace, "10." would be emitted via the input event, parseFloat("10.") would transform it to 10, v-on:input="content = $event.target.value" would assign it to content, and v-bind:value="content" would cause the input to display "10". So then, this is the expected behavior.
When you type in "10.2" and then hit "A", "10.2A" would be emitted via the input event, parseFloat("10.2A") would transform it to 10.2, v-on:input="content = $event.target.value" would assign it to content, and v-bind:value="content" would cause the input to display "10.2". It looks like it's failing at that very last step of causing the input to display "10.2", because the state of content is correctly being set to 10.2. If you use <input type="text" v-model.number="content" /> instead of <v-text-field v-model.number="content" />, once you blur, the text field successfully gets updated to "10.2". So it seems that the reason why <v-text-field> doesn't is due to how Vuetify is handling the v-bind:value="content" part.
When you type in "10.2" and then enter "B", in the beginning, "B10.2" would be emitted via the input event, parseFloat("B10.2") would return NaN, and thus the .number modifier would leave it as is, v-on:input="content = $event.target.value" would assign "B10.2" to content, and v-bind:value="content" would cause the input to display "B10.2". I agree that it doesn't seem right for parseFloat("10.2A") to return 10.2 but parseFloat("B10.2") to return "B10.2".
Lastly, how can I most simply provide a text box that's going to evaluate to a number for putting into my data model but not have the annoying auto delete of decimal places?
Given that the default behavior is weird, I think you're going to have to write your own custom logic for transforming the user's input. Eg. so that "10.2A" and "B10.2" both get transformed to 10.2 (or are left as is), and so that decimals are handled like you want. Something like this (CodePen):
<template>
<div id="app">
<input
v-bind:value="content"
v-on:input="handleInputEvent($event)"
/>
<p>{{ content }}</p>
</div>
</template>
<script>
export default {
data() {
return {
content: 0,
};
},
methods: {
handleInputEvent(e) {
this.content = this.transform(e.target.value);
setTimeout(() => this.$forceUpdate(), 500);
},
transform(val) {
val = this.trimLeadingChars(val);
val = this.trimTrailingChars(val);
// continue your custom logic here
return val;
},
trimLeadingChars(val) {
if (!val) {
return "";
}
for (let i = 0; i < val.length; i++) {
if (!isNaN(val[i])) {
return val.slice(i);
}
}
return val;
},
trimTrailingChars(val) {
if (!val) {
return "";
}
for (let i = val.length - 1; i >= 0; i--) {
if (!isNaN(Number(val[i]))) {
return val.slice(0,i+1);
}
}
return val;
},
},
};
</script>
The $forceUpdate seems to be necessary if you want the input field to actually change. However, it only seems to work on <input>, not <v-text-field>. Which is consistent with what we saw in the second bullet point. You can customize your <input> to make it appear and behave like <v-text-field> though.
I put it inside of a setTimeout so the user sees "I tried to type this but it got deleted" rather than "I'm typing characters but they're not appearing" because the former does a better job of indicating "What you tried to type is invalid".
Alternatively, you may want to do the transform on the blur event rather than as they type.

Find duplicate value Vuejs

I have an array of names, and if a user try to update one of them and is duplicate, I want to do something (error message) The problem is that is always duplicate. Pname will be changed on every keypress. I am not sure how to store the initial array and to compare with it.
<input
v-model="Pname"
type="text"
class="form-control"
/>
for(let element of this.customer_names){
if(this.Pname == element.name){
duplicateValue = +1;
}
}
You can do something as simple as:
if(this.customer_names.indexOf(this.Pname) != -1) {
// there is a duplicate somewhere
}
Put that code in your change/key-up event listener
You can use #blur like this:
<input
v-model="Pname"
type="text"
class="form-control"
#blur="findDuplicate"
>
function findDuplicate () {
if(this.customer_names.indexOf(this.Pname) != -1) {
// There is a duplicate
}
}
So, by this when you click outside, after you are done with typing, it will run that findDuplicate function.

Form number input does not work with keys

<template v-for="(paint, index) in paints">
<input type="number" v-bind:min="1" v-model.number="paint.qty">
</template>
-
var paintListApp = new Vue({
delimiters: ['${', '}'],
el: '#paintListApp',
data: {
paints: paints
},
methods: {
addToSet: function(sku, name, image) {
// method triggered when item is clicked - sends data to event bus
this.$eventHub.$emit('addToSelectedPaints', sku, name, image)
}
}
});
var paintWidget = new Vue({
el: '#paintWidget',
delimiters: ['${', '}'],
data: {
paints: []
},
created() {
// data picked up - processed by 'addToSelectedPaints'
this.$eventHub.$on('addToSelectedPaints', this.addToSelectedPaints);
},
methods: {
addToSelectedPaints: function (sku, name, image) {
var skuIndex = _.findIndex(this.paints, function (o) { return o.sku === sku; });
if (skuIndex !== -1) {
this.paints[skuIndex].qty = this.paints[skuIndex].qty + 1;
} else {
this.paints.push({
sku: sku,
name: name,
image: image,
qty: 1
});
}
}
}
});
Trying to get min values to work on number inputs. The min is respected by the browser number plus / minus controls - however, when using the keyboard, the min attribute appears to be ignored. I've tried all sorts of things from adding a method triggered by keyup etc and testing the value, through to watchers.
Keyup gets messy as when deleting, it automatically added a 1... making it difficult to type numbers above 19... (eg, you backspace to enter 2, but - it inserts a 1).
I just need to get native browser input min attribute working with keyboard input.
** Edit **
<input type="number" v-model="paint.qty" #change="paint.qty = paint.qty < 1 ? 1 : paint.qty">
Sort of solves the issue, albeit at the expense of the min attribute. Hooking into the #change event. If input is less than 1, switch it for 1. It also doesn't update until the input has lost focus - not locking the ui up. So not exactly the way I wanted it to work - but the result is the same.
** edit **
I've adapted Richard Matsens answer (the accepted one) to use an input and timeout... this behaves a bit more like the Chrome and Firefox native implementation.
<input type="number" min="1" v-model.number="paint.qty" #input="handleUpdate($event, index)">
and in the handleUpdate method:
...handleUpdate(event, index) {
var updater;
clearTimeout(updater);
this.currentIndex = index;
var paints = this.paints;
var max = this.max;
updater = setTimeout(function() {
if(event.target.value < event.target.min) {
paints[index].qty = parseInt(event.target.min);
}
if(event.target.value > max){
console.log(max);
paints[index].qty = parseInt(max);
}
}, 1000);
}...
clearing the timeout to prevent the updater bit firing too many times -
bouncing / mashing etc...
From this Validate input type number with range min/max
Most browsers “ignore” (it’s their default behavior) min and max, so that the user can freely edit the input field and type a number that’s not in the range 1-5.
From this How to detect changes in nested data, can use an #input on the control and a method() to handle the check.
Works for min="0", but say min="1" may be problematic if the user wants to type in "11".
Changed to blur event to handle above caveat.
methods: {
handleUpdate(event, index) {
if(event.target.value < event.target.min) {
this.paints[index].qty = event.target.min;
}
}
},
also add #blur() to the input
<div >
<input v-for="(paint, index) in paints"
#blur="handleUpdate($event, index)"
type="number" min="2" v-model.number="paint.qty">
</div>
For completeness, you may also want to add a validation message so that the user knows why the input value is being changed.

Error handling with Angular2 async pipe

I am using the Angular2 async pipe to stream values into the DOM. Here's a real simple example:
const stream = Observable.interval(1000)
.take(5)
.map(n => { if (n === 3) throw "ERROR"; return n; });
<div *ngFor="for num of stream | async">
{{num}}
</div>
<div id="error"></div>
What I would like to do is to have the sequence of 1-5 displayed, but on the error item (3), somehow populate the #error div with the error message.
This seems to require two things: first is the ability of the Angular async pipe to do something intelligent with errors, which I see no sign of. Looking at the source code, apparently it throws a JS exception, which doesn't seem too friendly.
Second is the ability to restart or continue the sequence after the error. I have read about catch and onErrorResumeNext and so on, but they all involve another sequence which will be switched to on an error. This greatly complicates the logic of generating the stream, on which I would just like to put a series of numbers (in this simple example). I have the sinking feeling that once an error occurs the game is over and the observable is completed and can only be "restarted" with a different observable. I'm still learning observables; is this in fact the case?
So my question is twofold:
Can Angular2's async pipe do something intelligent with errors?
Do observables have some simple way to continue after an error?
Yes you're right regarding the catch operator and the ability to do something after errors occur...
I would leverage the catch operator to catch the error and do something:
const stream = Observable.interval(1000)
.take(5)
.map(n => {
if (n === 3) {
throw Observable.throw(n);
}
return n;
})
.catch(err => {
this.error = error;
(...)
});
and in the template:
<div>{{error}}</div>
To be able to go on the initial observable, you need to create a new one starting at the point where the error occurs:
createObservable(i) {
return Observable.interval(1000)
.range(i + 1, 5 - i)
.take(5 - i)
});
}
and use it in the catch callback:
.catch(err => {
this.error = error;
return this.createObservable(err);
});
These two questions could help you:
How to resumeOnError (or similar) in RxJS5
RxJS Continue Listening After Ajax Error (last answer)
1) no, The async pipe subscribes and unsubscribes and returns the events it receives. You would need to handle the errors before they receive the async pipe.
2) You can use the catch operator and when it returns an observable then its value(s) is emitted by the .catch(err => Observable.of(-1)) instead of the error.
You could use this to emit a special "error" value and then use something like *ngIf="num === -1 to show the error value in some special way.
You can find more information on this https://blog.thoughtram.io/angular/2017/02/27/three-things-you-didnt-know-about-the-async-pipe.html
#Thierry Templier answer was correct but is now a bit outdated. Here's how to do it with the latest RXJS.
this.myObservable$ = this.myService.myFunc().pipe(
catchError(() => of([])) // this will emit [] if the request fails - u could handle this [] emit on error in the service itself
)
then HTML as normal:
<div *ngFor="let xxx of (myObservable$ | async)">
</div>
Note $ at end of Observable name is Angular recommended way to denote an Observable.
I was facing a similar issue and came up with another approach. I do not know if it's a good way of doing it, but it works.
template where you want to show the result of your observable:
<div *ngIf="tableData$ | async as tableData; else loader" class="mt-4">
<!-- do something with tableData -->
</div>
<ng-template #loader>
<loading [target]="tableData$"></loading>
</ng-template>
The loading component:
export class LoadingComponent implements OnInit {
private _errorMessageSubject : Subject<string> = new Subject<string>();
private _errorMessage$ : Observable<string> = this._errorMessageSubject.asObservable();
public get errorMessage$() : Observable<string> { return this._errorMessage$; }
private _target : Observable<any> | null = null;
public get target() : Observable<any> | null { return this._target }
// this input does nothing except catch the error and feed the
// message into the errorMessage subject.
#Input() public set target(o: Observable<any> | null) {
if(o == null) { return; }
this._target = o.pipe(
catchError((error, _) => {
this._errorMessageSubject.next(error);
return of(null);
}),
);
};
constructor() { }
ngOnInit(): void {
}
}
loader template:
<div *ngIf="target && target | async;">
</div>
<div *ngIf="errorMessage$ | async as error; else loading">
<p class="text-danger">{{ error }}</p>
</div>
<ng-template #loading> <!-- simply a spinner icon -->
<div class="d-flex justify-content-center">
<fa-icon [icon]="['fas', 'spinner']" size="6x" [spin]="true"></fa-icon>
</div>
</ng-template>
I am not perfectly sure if its a good approach to subscribe to the observable twice, as subscribing is done in the original component that needs the data and in the loader, but otherwise this seems to work properly.

Can I make ValidationMessageFor display hint on valid data?

I have the following scenario. A form has a few inputs and under some of them there are hints like "you don't have to fill this field" etc. Now I want the regular validation messages to replace those hints if a validation error appears. When the field is valid again the hint doesn't have to show up again (I wouldn't mind if it showed up though).
Is it possible to achieve that using the standard ValidationMessageFor helper?
I guess I could patch something up using JS, since I'm already monitoring the element which contains validation message for class changes (using http://meetselva.github.io/attrchange/), so I can change the color of a whole control group on validation error.
In this case I would just need to show\hide the hint depending on whether the validation error is visible or not.
In the end the solution I used looks like this. I encapsulated each input paired with validation message inside a 'control-group' div. Then I used attrchange to monitor validation span changes.
In View:
<div class="control-group">
#Html.TextBoxFor(model => model.Something)
<p class="help-block">This is a hint.</p>
#Html.ValidationMessageFor(model => model.Something)
</div>
In css:
.control-group.error .help-block
{
display:none;
}
In JS:
function UpdateControlGroupErrorState(valmsg) {
valmsg = $(valmsg);
var controlGroup = valmsg.closest('.control-group');
if (controlGroup.find('.field-validation-error').length > 0) {
if (!controlGroup.hasClass("error")) {
controlGroup.addClass("error");
}
}
else {
if (controlGroup.hasClass("error")) {
controlGroup.removeClass("error");
}
}
}
function SetupGroupValidate(validators) {
validators.each(function () {
UpdateControlGroupErrorState(this);
$(this).attrchange({
trackValues: true,
callback: function (e) {
if (e.attributeName == "class") {
UpdateControlGroupErrorState(this);
}
}
});
});
}
$(function () {
var validators = $('.control-group span[data-valmsg-for]');
SetupGroupValidate(validators);
});