Vue.js - Is Chaining Form Input Modifiers Allowed? - vue.js

The Vue.js ecosystem comes with some handy modifiers for use with form inputs.
<input v-model.lazy="msg">
<input v-model.trim="msg">
<input v-model.number="msg">
I was wondering if it were possible to chain such modifiers, perhaps something like this:
<input v-model.lazy.trim="msg">
If not, has anyone any experience of making their own modifiers?

Unfortunately it doesn't look like you can make custom modifiers the moment, there's some desire for the feature here. Your best option IMO is to make a custom component and then emit the modified value based on what you need.
I made a quick app (included below) to test chaining modifiers and it does work.
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
},
computed: {
len: function() {
return this.message.length;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" v-model.lazy.trim="message" />
<span style="background:yellow">{{ message }}</span>
<span>{{len}}</span>
</div>

Related

Asking for confirmation before changing the radio option

How do I ask the user for a confirmation before changing the option in a radio button using Vue.js?
Something like Are you sure? would be fine.
Assuming you have the following DOM structure:
<div id="app">
<input type="radio"/>
</div>
you can bind a #change directive to the radio button with a method implementing the expected "Are you sure?" confirmation popup. So you can enrich the above mentioned DOM structure like this:
<div id="app">
<input type="radio" #change="showConfirm"/>
</div>
And in the Vue instance you can define the expected confirmation method, for example:
new Vue({
el: '#app',
methods: {
showConfirm: function(event) {
event.preventDefault();
let checkedRadio = window.confirm("Are you sure?");
event.target.checked = checkedRadio;
}
}
})
Here you find the working example.

How to avoid vue component redraw?

I have prepared tag input control in Vue with tag grouping. Templates includes:
<script type="text/x-template" id="tem_vtags">
<div class="v-tags">
<ul>
<li v-for="(item, index) in model.items" :key="index" :data-group="getGroupName(item)"><div :data-value="item"><span v-if="typeof model.tagRenderer != 'function'">{{ item }}</span><span v-if="typeof model.tagRenderer == 'function'" v-html="tagRender(item)"></span></div><div data-remove="" #click="remove(index)">x</div></li>
</ul>
<textarea v-model="input" placeholder="type value and hit enter" #keydown="inputKeydown($event,input)"></textarea>
<button v-on:click="add(input)">Apply</button>
</div>
</script>
I have defined component method called .getGroupName() which relays on other function called .qualifier() that can be set over props.
My problem: once I add any tags to collection (.items) when i type anything into textarea for each keydown .getGroupName() seems to be called. It looks like entering anything to textarea results all component rerender?
Do you know how to avoid this behavior? I expect .getGroupName to be called only when new tag is added.
Heres the full code:
https://codepen.io/anon/pen/bKOJjo?editors=1011 (i have placed debugger; to catch when runtime enters .qualifier().
Any help appriciated.
It Man
TL;DR;
You can't, what you can do is optimize to reduce function calls.
the redraws are dynamic, triggered by data change. because you have functions (v-model and #keydown) you will update the data. The issue is that when you call a function: :data-group="getGroupName(item)" it will execute every time, because it makes no assumptions on what data may have changed.
One way of dealing with is is setting groupName as a computed key-val object that you can look up without calling the function. Then you can use :data-group="getGroupName[item]" without calling the function on redraw. The same should be done for v-html="tagRender(item)"
Instead of trying to fight how the framework handles data input events and rendering, instead use it to your advantage:
new Vue({
el: '#app',
template: '#example',
data() {
return {
someInput: '',
someInputStore: []
};
},
methods: {
add() {
if (this.someInputStore.indexOf(this.someInput) === -1) {
this.someInputStore.push(this.someInput);
this.someInput = '';
}
},
}
});
<html>
<body>
<div id="app"></div>
<template id="example">
<div>
<textarea v-model="someInput" #keyup.enter.exact="add" #keyup.shift.enter=""></textarea>
<button #click="add">click to add new input</button>
<div>
{{ someInputStore }}
</div>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
</body>
</html>
Event modifiers modifying
In the example, you can see that I am using 4 different event modifiers in order to achieve the desired outcome, but I will focus on the combination of them here:
#keyup.enter.exact - allows control of the exact combination of system modifiers needed to trigger an event. In this case, we are looking for the enter button.
#keyup.shift.enter - this is the interesting bit. Instead of trying to hackily prevent the framework from firing on both events, we can use this (and a blank value) to prevent the event we added into #keyup.enter.exact from firing. I must note that ="" is critical to the whole setup working properly. Without it, you aren't giving vue an alternative to firing the add method, as shown by my example.
I hope this helps!

Is it possible to render two adjacent VueJS components?

I have created two custom VueJS components and I would like to place them adjacent to one another like so:
<div id="app">
<x-component />
<y-component />
</div>
...
new Vue({
el: '#app',
components: {
'x-component': { template: '<div>component x</div>' },
'y-component': { template: '<div>component y</div>' }
}
});
When I do this, only the first component is rendered. Is this a bug in VueJS or am I doing something wrong? It seems like this should be possible.
When I change it as follows, it works:
<div id="app">
<div>
<x-component />
</div>
<div>
<y-component />
</div>
</div>
Reproductions below:
Not working:
https://jsfiddle.net/mquqonuq/1/
Working:
https://jsfiddle.net/mquqonuq/2/
I can't remember right now if it's an html spec issue but custom web elements need to be a two tag closed system, not a self closed single element.
Try:
<div id="app">
<x-component></x-component>
<y-component></y-component>
</div>
Which works.
EDIT:
if you look at google's web components primer it lists
3. Custom elements cannot be self-closing because HTML only allows a few elements to be self-closing. Always write a closing tag (<app-drawer></app-drawer>).

Keeping things DRY in Vue.js

I've done a couple of projects with React in the last year and I've switched to Vue for my current one, attracted by its greater simplicity, less verbose nature and the fact that you don't have to transpile your code to work, so it's easier to get going with and more flexible (well, to be accurate you don't have to transpile with React either, there's no need to use JSX, but it loses one of its great benefits if you don't).
Anyway, one of the things I'm missing from React (and I'm sure it's just ignorance of the Vue way which is my problem) is a way of reusing a fragment of code to avoid repeating myself in templates. The specific situation which prompted this question was a template where I have a custom input element like this:
<input ref="input" :id='name' :name='name' :type='fieldType' class='form-control' :value="value" :readonly="readonly" :disabled="disabled" #input="handleInput"/>
In certain situations I'd want to wrap it in a div, otherwise I'd want to use it as is. With React, I'd simply store it in a variable, something like this:
var inp=( <input ref="input" :id='name' :name='name' :type='fieldType' class='form-control' :value="value" :readonly="readonly" :disabled="disabled"
#input="handleInput"/>);
Then I could do something like the following:
var myInput;
if(divSituation){
myInput=(<div>{inp}</div>);
} else {
myInput=inp;
}
Then I could use the myInput var. The Vue logic doesn't seem to allow this, though. Unless, of course, using JSX within Vue would allow me to do the very same thing? I currently have for this in Vue something like the following, which offends me:
<template v-if="divSituation">
<div><input ref="input" :id='name' :name='name' :type='fieldType' class='form-control' :value="value" :readonly="readonly" :disabled="disabled" #input="handleInput"/></div>
</template>
<template v-else>
<input ref="input" :id='name' :name='name' :type='fieldType' class='form-control' :value="value" :readonly="readonly" :disabled="disabled" #input="handleInput"/
</template>
You can create vue components for re-usable components, which can be used as par requirement.
You can find an example of re-usable input component in vue docs:
<currency-input v-model="price"></currency-input>
and you can write that as re-usable component like following:
Vue.component('currency-input', {
template: '\
<span>\
$\
<input\
ref="input"\
v-bind:value="value"\
v-on:input="updateValue($event.target.value)"\
>\
</span>\
',
props: ['value'],
methods: {
// Instead of updating the value directly, this
// method is used to format and place constraints
// on the input's value
updateValue: function (value) {
var formattedValue = value
// Remove whitespace on either side
.trim()
// Shorten to 2 decimal places
.slice(0, value.indexOf('.') + 3)
// If the value was not already normalized,
// manually override it to conform
if (formattedValue !== value) {
this.$refs.input.value = formattedValue
}
// Emit the number value through the input event
this.$emit('input', Number(formattedValue))
}
}
})
You can add more props for readonly, disabled, etc.
You can also have a look at custom input elements of element-ui and it's code.
Given the example you have given, You can use v-html more efficiently. with v-html, you can pass a HTML string which will be rendered as HTML. However Note: the contents are inserted as plain HTML - they will not be compiled as Vue templates.
You can have a computed property, which will return HTML string as par your variable: divSituation, like following:
var data = {
templateInput: '<input ref="input" :id="name" :name="name" :type="fieldType" class="form-control" :value="value" :readonly="readonly" :disabled="disabled" #input="handleInput"/>',
divSituation: true,
myInput: ''
}
var demo = new Vue({
el: '#demo',
data: function(){
return data
},
computed: {
getMyInput: function(){
if(this.divSituation){
return this.templateInput
}
else{
return '<div>' + this.templateInput + '</div>'
}
}
}
})
Now you can just render myInput in HTML using v-html like this:
<div id="demo">
<div v-html="getMyInput">
</div>
</div>
check out working fiddle.

Vue.js v-show function with data parameter

I am trying to learn vue.js and javascript, so this is probably easy for those who already have walked the path... so please show me the way...
I want to have a universal function that passes parameters so...
in HTML
I have a call to function with div id parameter
<button #click="showdiv('user_likes')" type="button" class="btn btn-default">+</button>
In vue all the div elements are by default false.
This is my function in Vue.js methods
changediv: function(data){
data==false ? data=true : data=false;
},
All hints are appreciated
Button without a method
To complement the existing answer, since this case is a simple toggle, you can reduce the amount of code by getting rid of the method showDiv entirely. This should not be used if you have more complex logic since it could compromise readability: https://jsfiddle.net/8pLfc61s/
HTML:
<div id="demo">
<button #click="showParagraph = !showParagraph">Toggle paragraph with button</button>
<p v-if="showParagraph">Hello!</p>
</div>
JS
var demo = new Vue({
el: '#demo',
data: {
showParagraph: false
}
})
Checkbox with v-model
Another cool trick is to use a checkbox with v-model, although in this case the amount of markup is increased, so probably not a worthy tradeoff: https://jsfiddle.net/v09tyj36/
HTML:
<div id="demo">
<label class="button">
Toggle Paragraph with checkbox
<input type="checkbox" v-model="showParagraph">
</label>
<p v-if="showParagraph">Hello!</p>
</div>
JS
var demo = new Vue({
el: '#demo',
data: {
showParagraph: false
}
})
Here's a fiddle that replicates what you're trying to to: https://jsfiddle.net/9wmcz2my/
HTML:
<div id="demo">
<button #click="showDiv('showParagraph')">Click me</button>
<p v-if="showParagraph">Hello!</p>
</div>
JS
var demo = new Vue({
el: '#demo',
data: {
showParagraph: false
},
methods : {
showDiv: function(param){
this[param] = !this[param]
},
}
})