Vue js input mask - vue.js

I want to have an input mask on a specific input where it parses the user input into a human readable US phone number.
For example
User enter: 1231231234
User sees: (123)-123-1234
What I have done so far is, i made a watch method like the follow
switch (this.form.values.primary_phone.length) {
case 3:
return this.form.values.primary_phone = this.form.values.primary_phone.replace(/^([0-9]{3})$/, '($1)');
case 6:
return this.form.values.primary_phone = this.form.values.primary_phone.replace(/^([0-9]{3})([0-9]{3})$/, '($1)-$2');
case 10:
return this.form.values.primary_phone = this.form.values.primary_phone.replace(/^([0-9]{3})([0-9]{3})([0-9]{4})$/, '($1)-$2-$3');
}
Now this updates the value only when I focus out of from input. Is there a way I can do it to update while the input is still at focus?

I would recommend you to do a directive wrapping the jquery plugin input mask.
There is an example:
JavaScript
Vue.directive('input-mask', {
params: ['mask'],
bind: function() {
$(this.el).inputmask({
mask: this.params.mask
});
},
});
var vm = new Vue({
el: 'body',
data: {
value: '',
}
});
HTML
<!-- Import Styles/Scripts from the plugin and do not forget the jQuery library -->
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jasny-bootstrap/3.1.3/css/jasny-bootstrap.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/jasny-bootstrap/3.1.3/js/jasny-bootstrap.min.js"></script>
<p>Selected: {{value}}</p>
<input v-model="value" v-input-mask mask="(999) 999-9999">
If you need there is a JSBin to show you how it works.

For that you must define a custom filter on your input. Something like this
Vue.filter('mask', function (value) {
if (value.length <= 3){
var re = new RegExp("^([0-9]{"+value.length+"})$");
return value = value.replace(re, '($1)');
}
if (value.length > 3 && value.length <= 6){
var re = new RegExp("^([0-9]{3})([0-9]{"+ (value.length - 3)+"})$");
return value = value.replace(re, '($1)-$2');
}
if (value.length > 6 && value.length <= 10){
var re = new RegExp("^([0-9]{3})([0-9]{3})([0-9]{"+(value.length-6)+"})$");
return value = value.replace(re, '($1)-$2-$3');
}
return value;
})
Then apply that to your template. I made a jsfiddle (based on the markdown example from Vue page)
Notice that you still have to handle values of length greater than 10, and I also changed your function to use if statements instead of switch-case

I use the text-mask plugin which give the same great functionality as the JQuery one without the Jquery bloat.
Github here: https://github.com/text-mask/text-mask
Vue instructions here: https://github.com/text-mask/text-mask/tree/master/vue
const MaskedInput = window.vueTextMask.default
new Vue({
el: '#app',
components: { MaskedInput },
data: {
phone: '',
mask: ['(', /\d/, /\d/,/\d/, ')', '-', /\d/, /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-text-mask#6.1.2/dist/vueTextMask.min.js"></script>
<div id="app">
phone:
<masked-input v-model="phone" :mask="mask" placeholder="(___)-___-____"></masked-input>
</div>

Related

Can't return empty value to input box on vue

I want made a validation for input to be number only, whenever someone input a string, the input box will be cleared.
First, I made a method function on $event like this (ps. I use props)
<BaseInput
:value="nama"
#update="nama = ruled($event)"
label="Nama"
type="type"
/>
and this is the method, I use RegExp to check if the $event value is number. When it's false then I return $event value to empty string.
ruled(event) {
console.log(event)
var intRegex = new RegExp(/[0-9]/);
var data = intRegex.test(event)
if(!data) {
alert("Value Must Number")
event = ""
console.log('masuk if' + data)
}
return event
}
but it didn't clear the input box, anyone know why it happened ?
As Creative Learner said, I must clearing input box in child component only, so I did this on my component child
<template>
<input
:value="value"
:placeholder="label"
#input="$emit('update', ruled($event))"
/>
</template>
And this is the methods:
methods: {
ruled(event) {
//console.log(event)
var val = event.target.value
if(event.target.type == "number"){
var intRegex = new RegExp(/[0-9]/);
var intdata = intRegex.test(val)
if(intdata == false) {
error = "Value must contain number"
//alert("Value must contain number")
return event.target.value = ""
}
}
return val
}
}
great thanks to Creative Learner who made me understand
Suggestions :
Instead of #update you have to use #keypress or #change.
You can use v-model for two-way data binding.
Working Demo :
new Vue({
el: '#app',
data: {
nama: ''
},
methods: {
ruled(event) {
var intRegex = new RegExp(/^\d+$/);
var data = intRegex.test(this.nama);
if (!data) {
alert("Value must contain number");
this.nama = "";
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input
v-model="nama"
#change="ruled($event)"
label="Nama"
type="text"
/>
</div>

Vue-Select: Pushing a 2 dimensional array to :options

The plugin Vue-Select.
What I was trying to do is, make a search-select-dropdown input based on database.
So here's my SQL first named Ms_Location.
id_Loc | name_Loc
LOC0001 | Indonesia
LOC0002 | China
LOC0003 | America
My index.php
<!DOCTYPE html>
<html>
<head>
</head
<body>
<div class="form-group">
<label for="lokasi_id" class="control-label required"><strong>Lokasi</strong></label>
<v-select :options="lokasi_list" placeholder='Type location..'></v-select>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script src="https://unpkg.com/vue-select#latest"></script>
Vue.component('v-select', VueSelect.VueSelect);
var app = new Vue ({
el: '#app',
data: {
lokasi_select: '',
lokasi_list: [],
},
// End of data
computed: {
get_lokasi() {
var list_loc = new Array();
list_loc = <?php include('receive_lokasi.php') ?>;
for(var i=0; i<list_loc.length; i++) {
var pushLoc = {
label: list_loc[i][1], value: list_loc[i][0]
}
this.lokasi_list.push(pushLoc);
}
return list_loc[0][1];
}
}
})
});
</script>
</body>
</html>
And this is my receive_lokasi.php
<?php
include ('koneksi.php');
$condition = "1";
if(isset($_GET['userid'])){
$condition = " id=".$_GET['userid'];
}
$sqltran = mysqli_query($con, "SELECT id_Loc, name_Loc FROM ms_location")or die(mysqli_error($con));
$response = array();
while ($rowList = mysqli_fetch_array($sqltran,MYSQLI_NUM)) {
$response[] = $rowList;
}
echo json_encode($response);
mysqli_close($con);
?>
However, I can't seem to get the option shown. This only happens after I make the get_lokasi(). So the mistake is probably there? Or perhaps I was missing something.
I've tried to print the lokasi_list somewhere, and yes, the value is there, but not shown in the dropdown bar.
Also, I'm new to Vue, so any help would be good. Thanks!
Nevermind..
My mistake, I didn't notice my receive_lokasi.php code
Instead of using MYSQLI_NUM
while ($rowList = mysqli_fetch_array($sqltran,MYSQLI_NUM)) {
$response[] = $rowList;
}
I should be using MYSQLI_ASSOC, as documented in here.
while ($rowList = mysqli_fetch_array($sqltran,**MYSQLI_ASSOC**)) {
$response[] = $rowList;
}
After that change this
<v-select :options="lokasi_list" placeholder='Type location..'></v-select>
To this
<v-select label='nama_Location' :options="lokasi_list" placeholder='Type location..'></v-select>
After that, everything loads fine.
Vue's computed properties aren't normally used to populate vue data attributes, they normally take one or more data attributes and combine them into something different for the template to use.
In your code you've tried to populate the vue data attribute 'lokasi_list' in the computed property 'get_lokasi', but you never call 'get_lokasi' anywhere in the template so lokasi_list remains empty.
Another approach to this sort of situation is to use a vue method to fetch data from the php backend via an ajax call with something like axios, and you'd normally use that method in the vue app's created life cycle event to get the data asap.
e.g.
<script>
Vue.component('v-select', VueSelect.VueSelect);
var app = new Vue({
el: '#app',
data: {
lokasi_select: '',
lokasi_list: [],
},
created: function() {
this.fetchLocations();
},
methods: {
fetchLocations: function() {
axios.get('/api/locations-end-point')
.then((response) => {
this.lokasi_list = response.data //might need to change this to match how your php is returning the json
})
.catch((error) => {
//handle the error
})
}
}
});
</script>
Sorry to mention this, but in your php you've got:
if(isset($_GET['userid'])){
$condition = " id=".$_GET['userid'];
}
That looks like you were planning to use it as part of your sql, but it would have been vulnerable to SQL injection attacks, sorry If I'm pointing out something you already knew.

If user types open parenthesis in textarea, how to add closing paren automatically?

I have a textarea element in my template. I want Vue to add closing paren ) when user types open paren (, just like any programming text editor does.
How can I achieve this with Vue?
First thing I tried was v-on:keydown event. I checked what information is available in KeyboardEvent in callback method. Unfortunately I can't even see a position of a typed character so I can't modify a model variable bound to textarea. I do not know the position of user's cursor when he presses the character key.
Eddited:
What do you think about this? https://jsfiddle.net/eywraw8t/242422/
HTML:
<div id="app">
<textarea v-model="inputText" #keypress="changeInput"></textarea>
</div>
SCRIPT:
changeInput (event) {
if (event.key === '(') {
// Remember coursor position
var cursorStart = event.target.selectionStart
var cursorEnd = event.target.selectionEnd
event.preventDefault()
// Prevent adding additional (
this.$nextTick(() => {
this.inputText = this.inputText.substr(0, cursorStart) + '()' + this.inputText.substr(cursorStart)
})
setTimeout(() => {
// Maybe you can find better solution for this
// Without this part coursor will go to the end of text area
event.target.setSelectionRange(cursorStart + 2, cursorEnd + 2)
}, 4)
}
}
Below is my solution to the problem. It will add paired char if required and will keep the cursor between characters
const SINGLE_QUOTE = "'";
const LPAREN = "(";
const RPAREN = ")";
const LBRACK = "[";
const RBRACK = "]";
const CHARS_REQUIRE_PAIR = {
[SINGLE_QUOTE]: SINGLE_QUOTE,
[LPAREN]: RPAREN,
[LBRACK]: RBRACK
};
new Vue({
el: '#app',
data: {
title: 'The VueJS instance'
},
methods: {
smartTyper(evt) {
let keyTyped = evt.key;
let inputElement = evt.target;
if (!Object.keys(CHARS_REQUIRE_PAIR).includes(keyTyped)) {
return
}
let caretPosition = inputElement.selectionStart;
let closingChar = CHARS_REQUIRE_PAIR[keyTyped];
inputElement.value =
inputElement.value.substr(0, caretPosition) +
closingChar +
inputElement.value.substr(caretPosition);
inputElement.setSelectionRange(caretPosition, caretPosition);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<textarea v-on:keydown="smartTyper"></textarea>
</div>

Vue 2 custom select2: why is #change not working while #input is working

I created a custom select2 input element for Vue 2.
My question is: why is
<select2 v-model="vacancy.staff_member_id" #input="update(vacancy)"></select2>
working, but
<select2 v-model="vacancy.staff_member_id" #change="update(vacancy)"></select2>
not?
Since normal <input> elements in Vue have a #change handler, it would be nice if my custom select2 input has the same.
Some information on my custom element:
The purpose of this element is to not render all <option> elements but only those needed, because we have many select2 inputs on one page and many options inside a select2 input, causing page load to become slow.
This solution makes it much faster.
Vue.component('select2', {
props: ['options', 'value', 'placeholder', 'config', 'disabled'],
template: '<select><slot></slot></select>',
data: function() {
return {
newValue: null
}
},
mounted: function () {
var vm = this;
$.fn.select2.amd.require([
'select2/data/array',
'select2/utils'
], function (ArrayData, Utils) {
function CustomData ($element, options) {
CustomData.__super__.constructor.call(this, $element, options);
}
Utils.Extend(CustomData, ArrayData);
CustomData.prototype.query = function (params, callback) {
if (params.term && params.term !== '') {
// search for term
var results;
var termLC = params.term.toLowerCase();
var length = termLC.length;
if (length < 3) {
// if only one or two characters, search for words in string that start with it
// the string starts with the term, or the term is used directly after a space
results = _.filter(vm.options, function(option){
return option.text.substr(0,length).toLowerCase() === termLC ||
_.includes(option.text.toLowerCase(), ' '+termLC.substr(0,2));
});
}
if (length > 2 || results.length < 2) {
// if more than two characters, or the previous search give less then 2 results
// look anywhere in the texts
results = _.filter(vm.options, function(option){
return _.includes(option.text.toLowerCase(), termLC);
});
}
callback({results: results});
} else {
callback({results: vm.options}); // no search input -> return all options to scroll through
}
};
var config = {
// dataAdapter for displaying all options when opening the input
// and for filtering when the user starts typing
dataAdapter: CustomData,
// only the selected value, needed for un-opened display
// we are not using all options because that might become slow if we have many select2 inputs
data:_.filter(vm.options, function(option){return option.id === parseInt(vm.value);}),
placeholder:vm.placeholder
};
for (var attr in vm.config) {
config[attr] = vm.config[attr];
}
if (vm.disabled) {
config.disabled = vm.disabled;
}
if (vm.placeholder && vm.placeholder !== '') {
$(vm.$el).append('<option></option>');
}
$(vm.$el)
// init select2
.select2(config)
.val(vm.value)
.trigger('change')
// prevent dropdown to open when clicking the unselect-cross
.on("select2:unselecting", function (e) {
$(this).val('').trigger('change');
e.preventDefault();
})
// emit event on change.
.on('change', function () {
var newValue = $(this).val();
if (newValue !== null) {
Vue.nextTick(function(){
vm.$emit('input', newValue);
});
}
})
});
},
watch: {
value: function (value, value2) {
if (value === null) return;
var isChanged = false;
if (_.isArray(value)) {
if (value.length !== value2.length) {
isChanged = true;
} else {
for (var i=0; i<value.length; i++) {
if (value[i] !== value2[i]) {
isChanged = true;
}
}
}
} else {
if (value !== value2) {
isChanged = true;
}
}
if (isChanged) {
var selectOptions = $(this.$el).find('option');
var selectOptionsIds = _.map(selectOptions, 'value');
if (! _.includes(selectOptionsIds, value)) {
var missingOption = _.find(this.options, {id: value});
var missingText = _.find(this.options, function(opt){
return opt.id === parseInt(value);
}).text;
$(this.$el).append('<option value='+value+'>'+missingText+'</option>');
}
// update value only if there is a real change
// (without checking isSame, we enter a loop)
$(this.$el).val(value).trigger('change');
}
}
},
destroyed: function () {
$(this.$el).off().select2('destroy')
}
The reason is because you are listening to events on a component <select2> and not an actual DOM node. Events on components will refer to the custom events emitted from within, unless you use the .native modifier.
Custom events are different from native DOM events: they do not bubble up the DOM tree, and cannot be captured unless you use the .native modifier. From the docs:
Note that Vue’s event system is separate from the browser’s EventTarget API. Though they work similarly, $on and $emit are not aliases for addEventListener and dispatchEvent.
If you look into the code you posted, you will see this at the end of it:
Vue.nextTick(function(){
vm.$emit('input', newValue);
});
This code emits a custom event input in the VueJS event namespace, and is not a native DOM event. This event will be captured by v-on:input or #input on your <select2> VueJS component. Conversely, since no change event is emitted using vm.$emit, the binding v-on:change will never be fired and hence the non-action you have observed.
Terry pointed out the reason, but actually you can simply pass your update event to the child component as a prop. Check demo below.
Vue.component('select2', {
template: '<select #change="change"><option value="value1">Value 1</option><option value="value2">Value 2</option></select>',
props: [ 'change' ]
})
new Vue({
el: '#app',
methods: {
onChange() {
console.log('on change');
}
}
});
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app">
<div>
<p>custom select</p>
<select2 :change="onChange"></select2>
</div>
<div>
<p>default select</p>
<select #change="onChange">
<option value="value1">Value 1</option>
<option value="value2">Value 2</option>
</select>
</div>
</div>
fiddle

How to separate in vue.js entered value and displayed value in input[type=text]?

Example:
<span class='prefix'>+{{ prefix }}</span>
<input type='tel' v-model='phone'>
What should be displayed
When phone === '790012345678', it is actually
prefix = '7'
phone = '90012345678'
And displayed accordingly
<span class='prefix'>+7</span>
<input type='tel' value='90012345678'>
When user removes value from input, prefix is removed too.
Problem (jsfiddle http://jsfiddle.net/4qqza69k/48/)
I use watcher for `phone`.
When user changes something inside `input` watcher must update value for `phone`, but this way it is triggered again and it receives updated (incorrect) value.
Scenarios:
Phone equals 7-100-200-30-40
prefix = +7, phone = 1002003040
Phone equals 7
prefix = +7, phone = ''
Phone equals 7123
prefix = +7, phone = 123
Phone is empty
prefix = '', phone = ''
Problem: how to exclude prefix from input without triggering updates?
I think you need to rewrite v-model into more explicit v-on + v-bind pair and listen to input for a phone number, while calculating prefix and the rest part separately:
new Vue({
el: '#app',
data: {
prefix: '',
phone: '', // full phone number
},
methods: {
handleInput: function(e) {
if (e.target.value === '') this.prefix = '';
if (this.prefix !== '') {
this.phone = this.prefix + e.target.value;
} else {
const v = e.target.value;
this.phone = v;
this.prefix = v.slice(0,1);
}
}
},
computed: {
withoutPrefix: function() {
return (this.prefix !== '') ? this.phone.slice(1) : ''
}
}
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<span class='prefix'>+{{ prefix }}</span>
<input type='tel' #input="handleInput" :value="withoutPrefix">
<p>Phone: {{ phone }}</p>
</div>
It does not exactly work cause I'm being a bit confused by your example, but I think one way is to use computed getter/setter instead.
data() {
return {
hiddenInputValue: ''
}
},
computed: {
inputValue: {
get() {
return this.hiddenInputValue;
},
set(val) {
// do something with the input value...
this.hiddenInputValue = // assign a modified version of the input value...
}
}
}
You should be able to do something with that, but check my comment. It's probably a better solution.