Is it possible to pass data from Polymer component to Vue component? - vue.js

The below code is what I'd like to do but it currently doesn't work. I'm trying to start building Vue components inside my Polymer app as a way to slowly migrate off Polymer.
I've been able to get a Vue component working inside my Polymer app, but I'm stuck on how to pass data from the Polymer component to the Vue component. Ideally, what I'd like to do is pass a Polymer property into the Vue component like I'm doing with testValue below (although the code below doesn't work)
Any pointers are greatly appreciated, thank you!
<dom-module id="part-input-view">
<template>
<style include="part-input-view-styles"></style>
<div id="vueApp">
<vue-comp id="test" test$="[[testValue]]"></vue-comp>
</div>
</template>
<script>
class PartInputView extends Polymer.Element {
static get is() { return 'part-input-view'; }
constructor() {
super();
}
static get properties() {
return {
testValue: 'This is working!'
};
}
ready() {
super.ready();
Vue.component('vue-comp', {
props: ['test'],
template: '<div class="vue-comp">{{test}}</div>'
})
const el = this.shadowRoot.querySelector('#vueApp')
let vueApp = new Vue({
el
});
}
}
</script>
</dom-module>

Yes, it's possible. Your code would've worked had it not been for your [incorrect] property declaration. You should see this error in the console:
element-mixin.html:122 Uncaught TypeError: Cannot use 'in' operator to search for 'value' in This is working!
at propertyDefaults (element-mixin.html:122)
at HTMLElement._initializeProperties (element-mixin.html:565)
at new PropertiesChanged (properties-changed.html:175)
at new PropertyAccessors (property-accessors.html:120)
at new TemplateStamp (template-stamp.html:126)
at new PropertyEffects (property-effects.html:1199)
at new PropertiesMixin (properties-mixin.html:120)
at new PolymerElement (element-mixin.html:517)
at new PartInputView (part-input-view.html:17)
at HTMLElement._stampTemplate (template-stamp.html:473)
In Polymer, string properties with a default value can only be declared like this:
static get properties() {
return {
NAME: {
type: String,
value: 'My default value'
}
}
}
There is no shorthand for this. You might've confused the shorthand for the uninitialized property, which would be:
static get properties() {
return {
NAME: String
}
}
If you fix that bug, you'll notice your code works...
class PartInputView extends Polymer.Element {
static get is() { return 'part-input-view'; }
static get properties() {
return {
testValue: {
type: String,
value: 'This is working!'
}
};
}
ready() {
super.ready();
Vue.component('vue-comp', {
props: ['test'],
template: '<div class="vue-comp">{{test}}</div>'
})
const el = this.shadowRoot.querySelector('#vueApp')
let vueApp = new Vue({
el
});
}
}
customElements.define(PartInputView.is, PartInputView)
<head>
<script src="https://unpkg.com/vue#2.6.10"></script>
<base href="https://cdn.rawgit.com/download/polymer-cdn/2.6.0.2/lib/">
<script src="webcomponentsjs/webcomponents-loader.js"></script>
<link rel="import" href="polymer/polymer.html">
</head>
<body>
<part-input-view></part-input-view>
<dom-module id="part-input-view">
<template>
<style include="part-input-view-styles"></style>
<div id="vueApp">
<vue-comp id="test" test$="[[testValue]]"></vue-comp>
</div>
</template>
</dom-module>
</body>

Related

Vue3 custom element into Vue2 app using external framework

I have an application written in Vue2 which is not really ready to be upgraded to Vue3. But, I would like to start writing a component library in Vue3 and import the components back in Vue2 to eventually make the upgrade once it's ready.
Vue 3.2+ introduced defineCustomElement which works nicely but once I use a framework in the Vue3 environment (for example Quasar) that attaches to the Vue instance, it starts throwing errors in the Vue2 app, possibly because the result of defineCustomElement(SomeComponent) tries to use something from the framework that should be attached to the app.
I've thought about extending the HTMLElement and mounting the app on connectedCallback but then I lose the reactivity and have to manually handle all props/emits/.. like so:
class TestQuasarComponentCE extends HTMLElement {
// get init props
const prop1 = this.getAttribute('prop1')
// handle changes
// Mutation observer here probably...
const app = createApp(TestQuasarComponent, { prop1 }).use(Quasar)
app.mount(this)
}
customElements.define('test-quasar-component-ce', TestQuasarComponentCE);
So finally the question is - is it possible to somehow combine the defineCustomElement with a framework that attaches to the app?
So, after a bit of digging, I came up with the following.
First, let's create a component that uses our external library (Quasar in my case)
// SomeComponent.vue (Vue3 project)
<template>
<div class="container">
// This is the quasar component, it should get included in the build automatically if you use Vite/Vue-cli
<q-input
:model-value="message"
filled
rounded
#update:model-value="$emit('update:message', $event)"
/>
</div>
</template>
<script setup lang="ts>
defineProps({
message: { type: String }
})
defineEmits<{
(e: 'update:message', payload: string | number | null): void
}>()
</script>
Then we prepare the component to be built (this is where the magic happens)
// build.ts
import SomeComponent from 'path/to/SomeComponent.vue'
import { reactive } from 'vue'
import { Quasar } from 'quasar' // or any other external lib
const createCustomEvent = (name: string, args: any = []) => {
return new CustomEvent(name, {
bubbles: false,
composed: true,
cancelable: false,
detail: !args.length
? self
: args.length === 1
? args[0]
: args
});
};
class VueCustomComponent extends HTMLElement {
private _def: any;
private _props = reactive<Record<string, any>>({});
private _numberProps: string[];
constructor() {
super()
this._numberProps = [];
this._def = SomeComponent;
}
// Helper function to set the props based on the element's attributes (for primitive values) or properties (for arrays & objects)
private setAttr(attrName: string) {
// #ts-ignore
let val: string | number | null = this[attrName] || this.getAttribute(attrName);
if (val !== undefined && this._numberProps.includes(attrName)) {
val = Number(val);
}
this._props[attrName] = val;
}
// Mutation observer to handle attribute changes, basically two-way binding
private connectObserver() {
return new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === "attributes") {
const attrName = mutation.attributeName as string;
this.setAttr(attrName);
}
});
});
}
// Make emits available at the parent element
private createEventProxies() {
const eventNames = this._def.emits as string[];
if (eventNames) {
eventNames.forEach(evName => {
const handlerName = `on${evName[0].toUpperCase()}${evName.substring(1)}`;
this._props[handlerName] = (...args: any[]) => {
this.dispatchEvent(createCustomEvent(evName, args));
};
});
}
}
// Create the application instance and render the component
private createApp() {
const self = this;
const app = createApp({
render() {
return h(self._def, self._props);
}
})
.use(Quasar);
// USE ANYTHING YOU NEED HERE
app.mount(this);
}
// Handle element being inserted into DOM
connectedCallback() {
const componentProps = Object.entries(SomeComponent.props);
componentProps.forEach(([propName, propDetail]) => {
// #ts-ignore
if (propDetail.type === Number) {
this._numberProps.push(propName);
}
this.setAttr(propName);
});
this.createEventProxies();
this.createApp();
this.connectObserver().observe(this, { attributes: true });
}
}
// Register as custom element
customElements.define('some-component-ce', VueCustomElement);
Now, we need to build it as library (I use Vite, but should work for vue-cli as well)
// vite.config.ts
export default defineConfig({
...your config here...,
build: {
lib: {
entry: 'path/to/build.ts',
name: 'ComponentsLib',
fileName: format => `components-lib.${format}.js`
}
}
})
Now we need to import the built library in a context that has Vue3, in my case index.html works fine.
// index.html (Vue2 project)
<!DOCTYPE html>
<html lang="">
<head>
// Vue3
<script src="https://cdn.jsdelivr.net/npm/vue#3/dist/vue.global.prod.js"></script>
// Quasar styles
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet" type="text/css">
<link href="https://cdn.jsdelivr.net/npm/quasar#2.4.3/dist/quasar.prod.css" rel="stylesheet" type="text/css">
// Our built component
<script src="path/to/components-lib.umd.js"></script>
</head>
...rest of your html...
</html>
Now we are ready to use our component within our Vue2 (or any other) codebase same way we are used to with some minor changes, check comments below.
// App.vue (Vue2 project)
<template>
<some-component-ce
:message="message" // For primitive values
:obj.prop="obj" // Notice the .prop there -> for arrays & objects
#update:message="message = $event.detail" // Notice the .detail here
/>
</template>
<script>
export default {
data() {
return {
message: 'Some message here',
obj: { x: 1, y: 2 },
}
}
}
</script>
Now, you can use Vue3 components in Vue2 :)

cant set property of undefined vuejs 3

Within my Something.vue:
<template>
<p>{{ codes }}</p>
</template>
export default {
data() {
return {
codes: 'test'
}
},
name: 'Functions',
props: {
msg: String
},
setup() {
this.codes = 'does it work?';
}
</script>
<style>
</style>
Why would I face this issue?
Uncaught TypeError: Cannot set property 'codes' of undefined
In vue3, you can defined variable like this:
import { ref } from "vue";
export default {
name: 'Functions',
props: {
msg: String
},
setup() {
let codes = ref('does it work?');
return { codes }
}
</script>
And you can use codes in your Vue component.
Because, in setup methods, you can not use this keywords. Its execution is earlier than other life cycle function
If you want to change codes, you can defined a function in setup or methods, like this:
setup() {
let codes = ref("does it work?");
function onChangeCodes() {
codes.value = "changed!";
}
return { codes, onChangeCodes };
}
Then, you can use this function in your template:
<template>
<div>{{ codes }}</div>
<button #click="onChangeCodes">change</button>
</template>
When setup is executed, the component instance has not been created yet,
In other words, You can't access 'codes' when setup() execute.
you can check the document here

How to fix Vue 3 template compilation error : v-model value must be a valid JavaScript member expression?

I am working on a vue project and the vue version is 3.0
And recently I can see these many warnings for some reason.
Template compilation error: v-model value must be a valid JavaScript member expression
I guess it is because I am using long v-model variable name like this.
<textarea v-model="firstVariable.subVariable.subVariableKey" readonly></textarea>
Please let me know if any idea.
Thanks in advance
This is the component and template code.
var myTemplate = Vue.defineComponent({
template: '#myTemplate',
data() {
return {
firstVariable: {}
}
},
mounted() {
loadData();
},
methods:{
loadData() {
axios.get(MY_ROUTES).then(res => {
// let's suppose res.data is going to be {subVariable: {subVariableKey: "val"}}
this.firstVariable = res.data;
})
}
}
});
// template.html
<script type="text/template" id="myTemplate">
<div class="container">
<textarea v-model="firstVariable.subVariable?.subVariableKey"></textarea>
</div>
</script>
In order that your property go reactive you've to define its full schema :
data() {
return {
firstVariable: {
subVariable: {
subVariableKey: ''
}
}
}
},
and use it directly without optional chaining
v-model="firstVariable.subVariable.subVariableKey"
because v-model="firstVariable.subVariable?.subVariableKey" malformed expression like v-model="a+b" like this test
Example
var comp1 = Vue.defineComponent({
name: 'comp1',
template: '#myTemplate',
data() {
return {
firstVariable: {
subVariable: {
subVariableKey: ''
}
}
}
},
mounted() {
this.loadData();
},
methods: {
loadData() {
}
}
});
const {
createApp
} = Vue;
const App = {
components: {
comp1
},
data() {
return {
}
},
mounted() {
}
}
const app = createApp(App)
app.mount('#app')
<script src="https://unpkg.com/vue#3.0.0-rc.11/dist/vue.global.prod.js"></script>
<div id="app" >
vue 3 app
<comp1 />
</div>
<script type="text/template" id="myTemplate">
<div class="container">
<textarea v-model="firstVariable.subVariable.subVariableKey"></textarea>
<div>
{{firstVariable.subVariable.subVariableKey}}
</div>
</div>
</script>
You are adding a new property to an object which is not reactive.
Vue cannot detect property addition or deletion. Since Vue performs
the getter/setter conversion process during instance initialization, a
property must be present in the data object in order for Vue to
convert it and make it reactive. For example:
Source
Instead of
this.firstVariable = res.data;
Use
this.$set(this.firstVariable, 'subVariable', res.data.subVariable);

Is that the right way to set an id in Vue.js component?

I am trying to integrate Phaser 3 with Vue.js 2.
My goal is to create a Vue.js component associated with a game canvas.
My initial solution was:
<template>
<div :id="id">
</div>
</template>
<script>
import Phaser from 'phaser'
export default {
data () {
return {
id: null,
game: null
}
},
mounted () {
this.id = 'game' + this._uid
var config = {
parent: this.id,
type: Phaser.CANVAS
}
this.game = new Phaser.Game(config)
....
}
}
</script>
This code attach the game canvas to my template. But to my surprise it only worked 'sometimes'.
I figured out, after hours of debugging, that my div element in the DOM wasn't updated with the id when I was instantiating my new Game.
So I came up with the solution of instantiating the id in the beforeMount () method as follow:
<template>
<div :id="id">
</div>
</template>
<script>
import Phaser from 'phaser'
export default {
data () {
return {
id: null,
game: null
}
},
beforeMount () {
this.id = 'game' + this._uid
},
mounted () {
var config = {
parent: this.id,
type: Phaser.CANVAS
}
this.game = new Phaser.Game(config)
....
}
}
</script>
It is working, but I would like to know if there is a more simple and elegant solution ?
One better solution for integrating Phaser.Game into the application is directly passing the config the HTML element, a configuration supported by Phaser.Game.
To get a reference to a HTML element in vue, you can use refs, these are basically id's, but local to the component itself, so there is not risk in creating conflicts.
<template>
<div ref="myDiv">
</div>
</template>
<script>
import Phaser from 'phaser'
export default {
data () {
return {
game: null
}
},
mounted () {
var config = {
parent: this.$refs.myDiv,
type: Phaser.CANVAS
}
this.game = new Phaser.Game(config)
....
}
}
</script>
Vue3 sample:
<script setup>
import { ref,onMounted } from 'vue';
import Phaser from 'phaser'
const myDiv = ref(null)
let canvasWidth = 750;
let canvasHeight = 1450;
onMounted(() => {
const config = {
type: Phaser.AUTO,
parent: popWrap.value,
width: canvasWidth,
height: canvasHeight,
scene: {
preload: preload,
create: create,
update: update
}
};
const game = new Phaser.Game(config);
})
</script>
<template>
<div ref="myDiv">
</div>
</template>

Polymer 1.x: Accessing behavior properties

How do we access the properties of a behavior from inside an element using that behavior?
From inside my-element.html, using this.randomData to access the randomData property of the imported behavior seems like it should work; but it does not.
my-element.html
<link rel="import" href="random-data-behavior.html">
<script>
(function() {
Polymer({
is: 'my-element',
behaviors: [
MyBehaviors.MyRandomBehavior,
],
show: function() {
// unsuccessfully trying to access the behavior property: `randomData`
// using `this.randomData` seems like it should work; but it doesn't
console.log('randomData', this.randomData); // undefined
},
...
</script>
random-data-behavior.html
<script>
var MyBehaviors = MyBehaviors || {};
MyBehaviors.MyRandomBehaviorImpl = {
properties: {
randomData: {
type: String,
value: function() {
return 'My random data';
},
},
},
};
MyBehaviors.MyRandomBehavior = [
MyBehaviors.MyRandomBehaviorImpl,
];
</script>
Prior Research
My digging turned up this issue on the topic. → Here is the jsBin.
https://jsbin.com/lihakafuki/1/edit?html,console
<!DOCTYPE html>
<html>
<head>
<title>Polymer behaviors scope issue</title>
<meta name="description" content="Example of issue with behaviors scope on Polymer">
<meta charset="utf-8">
<base href="https://polygit.org/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
</head>
<body>
<!-------------------- working-element -------------------->
<dom-module id="working-element">
<template>
<span>Working Element (Check the console)</span>
</template>
<script>
Polymer({
is: "working-element",
properties: {
property: {
type: String,
value: "Default value"
}
},
getProperties() {
// Returns an object with the properties values
return Object
.keys(this.properties)
.reduce((o,p)=>Object.assign(o,{[p]:this[p]}),{});
}
});
</script>
</dom-module>
<working-element></working-element>
<script>
console.log(document.querySelector("working-element").getProperties());
</script>
<!-------------------- /working-element -------------------->
<!-------------------- broken-element -------------------->
<script>
// Behavior to mimic working-element
window.SomeNamespace = window.SomeNamespace || {};
SomeNamespace.SomeBehavior = {
properties: {
property: {
type: String,
value: "Default value"
}
},
getProperties() {
// Returns an object with the properties values
return Object
.keys(this.properties)
.reduce((o,p)=>Object.assign(o,{[p]:this[p]}),{});
}
};
</script>
<dom-module id="broken-element">
<template>
<span>Broken Element (Check the console)</span>
</template>
<script>
Polymer({
is: "broken-element",
behaviors: [SomeNamespace.SomeBehavior]
});
</script>
</dom-module>
<broken-element></broken-element>
<script>
// This should have the same output as working-element
// but instead it outputs an empty object
console.log(document.querySelector("broken-element").getProperties());
</script>
<!-------------------- /broken-element -------------------->
</body>
</html>
The last comment reads:
https://github.com/Polymer/polymer/issues/3681
Still, I think for now I'll solve it with
this.behaviors.map(behavior=>behavior.properties)
or something like that.
But what did he mean? How would that work?
Try defining your behavior as follows:
<script>
(function (root) {
'use strict';
root.MyBehaviors = root.MyBehaviors || {};
root.MyBehaviors.MyRandomBehavior = root.MyBehaviors.MyRandomBehavior || {};
root.MyBehaviors.MyRandomBehavior.Foo = {
bar: () {
console.log("Hello!");
}
};
}(window));
</script>
I think it's because you used the var declaration for your behaviours, this limit the scope of the property.
form
var MyBehaviors =
to
MyBehaviors =