Framework agnostic custom element with v-model doesn't work - vue.js

I am facing an issue.
I am currently working on a framework agnostic element library with the new browser API customElements.define() and created a custom input and would like him to work with v-model binding.
But, it does not work. I am able to bind a value with :value="prop" but v-model simply does not work.
class LmTag extends HTMLElement {
constructor() {
super();
this.template = document.querySelector("#lmTag");
this.shadow = this.createShadowRoot();
this.clone = document.importNode(this.template.content, true);
this.shadow.appendChild(this.clone);
this.input = this.shadow.querySelector('input');
}
connectedCallback() {
this.value = this.getAttribute("value");
}
get value() {
return this.input.value;
}
set value(value) {
this.input.value = value;
}
attributeChangedCallback(attr, oldValue, newValue) {
console.log(attr, oldValue, newValue);
}
}
customElements.define("lm-tag", LmTag);
Vue.config.productionTip = false
Vue.config.devtools = false;
new Vue({
el: '#app',
data: {
lights: true,
input: "model"
},
methods: {
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template id="lmTag">
<style>
.lm-tag {
display: inline-block;
padding: 4px;
border-radius: 4px;
background-color: #e74c3c;
color: #ffffff;
font-size: 12px;
}
.lm-tag input {
border: none;
}
</style>
<span class="lm-tag">
<input type="text"/>
<content></content>
</span>
</template>
<div id="app" class="text-center body">
<p>
Binding :value
<lm-tag :value="input">{{input}}</lm-tag>
</p>
<p>
Binding v-model
<lm-tag v-model="input">{{input}}</lm-tag>
</p>
<p>
Raw input with :value
<input type="text" :value="input">
</p>
<p>
Raw input with v-model
<input type="text" v-model="input">
</p>
</div>
Could you help a fellow dev having trouble with his code?
Thank you!

Related

VueJS complex v-if

I want to show a button ONLY if two conditions are met. First I try v-if with only one condition at a time:
v-if="editMode"
v-if="$can('customersdelete')"
using only one condition at a time, the button is display, so i think both conditions are true. If i use:
v-if="editMode && $can('customersdelete')"
The button isn't display. $can is a mixin, to validate user has permission to do something.
<script>
export default {
methods: {
$can(permissionName) {
return Permissions.indexOf(permissionName) !== -1;
},
},
};
</script>
I don't know why this is not working...
Apparently, any subsequent v-if is ignored and only the first one is taken into account, as demonstrated by the following example:
Vue.config.devtools = false;
Vue.config.productionTip = false;
const Permissions = ['customersdelete'];
new Vue({
el: '#app',
data() {
return { editMode: true };
},
methods: {
$can(permissionName) {
return Permissions.indexOf(permissionName) !== -1;
},
},
})
code {
background-color: #f5f5f5;
border: 1px solid #eee;
padding: 2px 5px;
color: red;
border-radius: 3px;
display: inline-block;
}
div {
margin-top: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<label><input type="checkbox" v-model="editMode">editMode</label><br><br>
<div>$can('customersdelete') => <code v-text="$can('customersdelete')"></code></div>
<div>editMode => <code v-text="editMode"></code></div>
<hr />
<div v-if="$can('customersdelete')">
<code>v-if="$can('customersdelete')"</code>
</div>
<div v-if="editMode">
<code v-if="editMode">v-if="editMode"</code>
</div>
<div v-if="editMode && $can('customersdelete')">
<code>v-if="editMode && $can('customersdelete')"</code>
</div>
<div v-if="editMode"
v-if="$can('customersdelete')">
<code>v-if="editMode" v-if="$can('customersdelete')"</code>
</div>
<div v-if="$can('customersdelete')"
v-if="editMode">
<code>v-if="$can('customersdelete')" v-if="editMode"</code>
</div>
</div>
I would suggest use function and then use in v-if.
example
v-if="isButtonShow()"
and below is the method portion:
methods:{
isButtonShow(){
if(this.editMode){
if(this.$can('customersdelete'){
return true;
}
}
return false;
}
}

Can I execute a method when an input box gets to a certain length?

I have an input box that takes a string. Can I execute a method (in vue.js) when the length of the string gets to a certain number?
something like
<input v-if="inputBox.length == 6 THEN runme()"...>
You can use watch option, you'll be able to react to data changes :
new Vue({
el: '#root',
data: {
message: '',
inputLength : undefined
},
methods : {
doSomething(){
console.log('I did it !')
}
},
watch :{
message : function(val) {
if(val.length>=5){
this.inputLength = val.length
this.doSomething();
}
}
}
})
.container {
padding-top: 2em;
}
.intro {
font-size: 1.5em;
margin-bottom: 1.5em;
}
.input-value {
margin-top: 1em;
font-size: 1.25em;
}
.highlight {
color: #00d1b2;
font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div class="container">
<h1 class="intro">Binding with Vue</h1>
<div id='root' class="box">
<label class="label">Enter text here</label>
<input class="input is-medium" type='text' id='input' v-model='message'>
<p class="input-value">The value of the input is: <span class="highlight">{{ inputLength }}</span></p>
</div>
</div>
In this example, if input length is >= 5 then it will change the inputLenght value in data and execute a method.
For more informations about this, go see :
https://v2.vuejs.org/v2/guide/computed.html#Watchers
You can use a watcher to trigger a method when the string exceeds the length:
new Vue({
data () {
return {
model: ''
}
},
watch: {
model: {
handler: function (value) {
if (value.length >= 6) {
this.trigger()
}
}
}
},
el: '#app',
methods: {
trigger () {
alert('hi there')
}
},
template: `<input v-model="model">`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

Assing uploaded file value to input field multiple times using vue.js

Assign filename to input field on upload and then show success message working fine,but when i try to delete file and upload again it's not working!
new Vue({
el: "#app",
data() {
return {
form: {
message: '',
fileurl: ''
},
loading: false
}
},
methods: {
uploadImage(event) {
this.form.fileurl = 'uploaded!'
},
deleteFile(furl) {
this.form.fileurl = ''
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
span {
color: red;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<h3>
File Upload / remove Demo
</h3>
<hr />
<div class="custom-file attach_file" v-show="!form.fileurl">
<input type="file" id="file" name="file" #change="uploadImage($event)">
<input type="text" v-model="form.fileurl">
</div>
<p v-if="form.fileurl"> {{ form.fileurl }} <span #click="deleteFile(form.fileurl)">Delete</span></p>
</div>
I am not getting any console error as well.
This is what i have tried so far.
Can you guys please have a look at this!
The problem is that #change is not trigger when you choose the same file. The simplest solution is reset value of input when you click delete
this.$refs.fileToUpload.value = '';
new Vue({
el: "#app",
data() {
return {
form: {
message: '',
fileurl: ''
},
loading: false
}
},
methods: {
uploadImage(event) {
this.form.fileurl = 'uploaded!'
},
deleteFile(furl) {
this.form.fileurl = ''
this.$refs.fileToUpload.value = '';
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
span {
color: red;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<h3>
File Upload / remove Demo
</h3>
<hr />
<div class="custom-file attach_file" v-show="!form.fileurl">
<input type="file" id="file" name="file" class="custom-file-input" #change="uploadImage($event)" ref="fileToUpload">
<input type="text" v-model="form.fileurl">
</div>
<p v-if="form.fileurl"> {{ form.fileurl }} <span #click="deleteFile(form.fileurl)">Delete</span></p>
</div>
You need to clear you input field first.
Update your upload method with this.
uploadImage(event) {
this.form.fileurl = 'uploaded!'
this.success = false
this.$nextTick(() => {
this.success = true
})
Have a look at this working demo
https://jsfiddle.net/asimshahzad/8c9pcfys/

Vue.js 2.0 data binding issue - one data field's change will trigger the other data fields' filter

Following is the code, I define a my data in Vue as below:
{
result: {
name: 'user A',
age: 20,
male: true
},
showDetails: true
}
There are several filter/computed/methods in the view will do the result formatting.
The problem is even I just change the value of the showDetailds, the methods and filters defined for the result will be triggered as well.
May I know any issue in my code ? Or it's the natural of Vue.js (I don't believe.) ?
function countTheCall(key) {
let counter = document.getElementById(key)
if (!counter) {
let container = document.getElementById('statList')
var item = document.createElement('li')
item.innerHTML = `${key}: <span id='${key}'>0</span>`
container.appendChild(item)
counter = document.getElementById(key)
}
counter.innerText = _.parseInt(counter.innerText) + 1
}
var vm = new Vue({
el: '#app',
data() {
return {
result: {
name: 'user A',
age: 20,
male: true
},
showDetails: true
}
},
methods: {
countTheCall: function(key) {
let counter = document.getElementById(key)
counter.innerText = _.parseInt(counter.innerText) + 1
},
trimValue: function(value) {
console.log('Invoke method - trimValue.')
countTheCall('methodCalls of trimValue')
return _.trim(value)
},
getValFromResult: function(key) {
console.log('Invoke method - getValFromResult.')
countTheCall('methodCalls of getValFromResult')
return _.get(this.result, key, 'null')
}
},
computed: {
displayName: function() {
console.log('Invoke computed value - computedVal.')
countTheCall('computedCalls of displayName')
return `${this.result.name} (${this.result.age})`
}
},
filters: {
convertString: function(val) {
countTheCall('filterCalls of convertString')
return _.upperFirst(val)
},
getValFromObject: function(obj, key) {
console.log(`[filter] getValue From Object by ${key}`)
countTheCall('filterCalls of getValFromObject')
return _.get(obj, key, 'null')
},
convertDateString: function(datestr, format = 'MMM DD (ddd), YYYY') {
console.log(`[filter] convertDateString ${datestr}`)
countTheCall('filterCalls of convertDateString')
let m = moment(datestr)
let formattedStr = ''
if (m.isValid()) {
formattedStr = m.format(format)
}
return formattedStr
}
}
})
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
label {
font-style: italic;
color: black;
}
span {
font-weight: bold;
color: darkblue;
}
.callStat {
float: right;
width: 400px;
border: solid darkgrey 2px;
padding: 5px;
margin-right: 40px;
}
<body>
<script type="text/javascript" src="//cdn.bootcss.com/vue/2.2.4/vue.min.js"></script>
<script type="text/javascript" src="//cdn.bootcss.com/moment.js/2.17.1/moment-with-locales.min.js"></script>
<script type="text/javascript" src="//cdn.bootcss.com/lodash.js/4.17.4/lodash.min.js"></script>
<div class="callStat">
<h4>The Stat. of Calls</h4>
<ul id="statList"></ul>
</div>
<div id="app">
<h2>User Profile</h2>
<div>
<label>User Name:</label>
<input type="text" v-model="result.name"></input>
</div>
<div>
<label>Show Details:</label>
<input type="checkbox" id="showDetails" v-model="showDetails"></input>
<label for="showDetails">show detail section?</label>
</div>
<hr/>
<div>
<label>User Name:</label>
<ul>
<li>result.name: <span>{{trimValue(result.name)}}</span></li>
<li>result.unknownFiled: <span>{{result.unknownFiled}}</span></li>
<li>getValFromResult('name'): <span>{{ getValFromResult('name')}} </span></li>
<li>getValFromResult('unknownField'): <span>{{ getValFromResult('unknownField')}} </span></li>
<li>computed Display Name : <span>{{ displayName }} </span></li>
<li>convertString by filter : <span>{{ result.name | convertString }} </span></li>
<li>user birthDay: <span>{{ result.birthDay | convertDateString}}</span></li>
</ul>
</div>
</div>
</body>
This is natural to Vue.
Methods & Filters run on every DOM re-render.
This is why it's best to use Computed whenever possible - these re-run only on change of connected properties.

VueJS adding transition on new data view

I want to add some transition effect on newly created element on VueJS. Like in the following code if I create a new list element using the input field, I want it to appear using 'fade' or 'slide-in' effect. I couldn't figure out it from the official documentation. how can I do that?
var vm = new Vue({
el: "#vue-instance",
data: {
newelement: '',
list: []
},
methods: {
addelement: function() {
this.list.push(this.newelement);
this.newelement = '';
}
}
});
<div id="vue-instance">
<input type="text" v-model="newelement" #keyup.enter="addelement">
<ul v-for="element in list">
<li>{{ element }}</li>
</ul>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.16/vue.js"></script>
According to official docs u need to wrap you loop with a transition element. And specify a name and a (optional) tag attributes.
Like so:
<transition-group name="list" tag="p">
<span v-for="item in list" class="list-item"></span>
</transition-group>
And add some css classes with name of transition:
.list-item {
display: inline-block;
margin-right: 10px;
}
.list-enter-active, .list-leave-active {
transition: all 1s;
}
.list-enter, .list-leave-to /* .list-leave-active below version 2.1.8 */ {
opacity: 0;
transform: translateY(30px);
}
More info at the https://v2.vuejs.org/v2/guide/transitions.html#List-Transitions