Below is an example for a custom web component my-input
. I would like to bind the value attribute of my custom input component to the email attribute of a vue instance. (The example might require Chrome to support custom web components.)
=>How do I have to adapt my web component example to get the binding working?
If I replace my-input
with a plain input
tag, the binding works. Therefore, my syntax for the vue.js part seems to be just fine.
https://jsfiddle.net/j5f9edjt/
new Vue({
el: '#app',
template: '#app-template',
data: {
//email data is blank initially
email: ''
}
})
<script type="text/javascript" src="https://unpkg.com/[email protected]"></script>
<script>
class MyInput extends HTMLElement {
static get observedAttributes() {
return ['value'];
}
constructor(){
super();
this.wrappedInput=undefined;
}
connectedCallback(){
var self=this;
if(!self.wrappedInput){
var wrappedInput = document.createElement('input');
wrappedInput.type='text';
wrappedInput.onchange = ()=>this.wrappedInputChanged();
self.appendChild(wrappedInput);
self.wrappedInput = wrappedInput;
}
}
attributeChangedCallback(attr, oldValue, newValue) {
if(attr==='value'){
console.log('attribute changed ' + newValue);
if(this.wrappedInput){
this.wrappedInput.value= newValue;
}
}
}
wrappedInputChanged(){
console.log('wrapepd input changed')
var newValue = this.wrappedInput.value;
this.value = newValue;
}
get value() {
console.log('get value')
return this.getAttribute('value');
}
set value(newValue) {
this.setAttribute('value',newValue);
console.log('set value ' + newValue);
}
}
window.customElements.define('my-input', MyInput);
</script>
<div id="app"></div>
<template id="app-template">
<div>
<my-input v-model="email"></my-input>
<h1>
You entered {{email}}
</h1>
</div>
</template>
I tried to dispatch an extra input event but that did not help:
var myInput = new CustomEvent("input",
{
detail: {
message: "Hello World!",
type: 'text',
},
bubbles: true,
cancelable: true
}
);
this.dispatchEvent(myInput);
Where can I find the source code for the v-model directive to understand what it does?
Related question:
How to target custom element (native web component) in vue.js?
To make v-model
work you need to make a wrapper component for your webcomponent. The wrapper will conform to the requirements for using v-model
with a component.
Alternatively, you can decompose the v-model
into its two parts: set the value
prop and handle input
events. Vue doesn't seem to recognize a webcomponent as a native element as far as v-model
goes.
class MyInput extends HTMLElement {
static get observedAttributes() {
return ['value'];
}
constructor() {
super();
this.wrappedInput = undefined;
}
connectedCallback() {
var self = this;
if (!self.wrappedInput) {
var wrappedInput = document.createElement('input');
wrappedInput.type = 'text';
wrappedInput.onchange = () => this.wrappedInputChanged();
self.appendChild(wrappedInput);
self.wrappedInput = wrappedInput;
}
}
attributeChangedCallback(attr, oldValue, newValue) {
if (attr === 'value') {
console.log('attribute changed ' + newValue);
if (this.wrappedInput) {
this.wrappedInput.value = newValue;
}
}
}
wrappedInputChanged() {
var newValue = this.wrappedInput.value;
this.value = newValue;
}
get value() {
console.log('get value')
return this.getAttribute('value');
}
set value(newValue) {
this.setAttribute('value', newValue);
console.log('set value ' + newValue);
}
}
window.customElements.define('my-input', MyInput);
new Vue({
el: '#app',
template: '#app-template',
data: {
//email data is blank initially
email: ''
},
methods: {
handleInput(event) {
this.email = event.target.value;
}
},
components: {
wrappedMyInput: {
template: '#wmi-template',
props: ['value'],
methods: {
emitInput(event) {
this.$emit('input', event.target.value);
}
}
}
}
})
<script type="text/javascript" src="https://unpkg.com/[email protected]"></script>
<div id="app"></div>
<template id="app-template">
<div>
<my-input :value="email" @input="handleInput"></my-input>
<h1>
You entered {{email}}
</h1>
<wrapped-my-input v-model="email"></wrapped-my-input>
<h1>
You entered {{email}}
</h1>
</div>
</template>
<template id="wmi-template">
<my-input :value="value" @input="emitInput"></my-input>
</template>