Polymer 1.x: Accessing behavior properties - 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 =

Related

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);

How can I use passed data using props in child component v-bind:style?

I'm trying to get data from parent component and use them in child component v-bind:style.
Here's my code.
<body>
<div id="parentComponent">
<child-component v-bind:propsdata="parentBackground" v-bind:propsdata2="parentFontstyle"></child-component>
</div>
<script>
Vue.component('child-component', {
props: ['propsdata', 'propsdata2'],
data: function() {
return {
childBackground: this.propsdata,
childFontStyle: this.propsdata2
},
template: '<p v-bind:style="childBackgroundColor, childFontStyle">Child componnent Area</p>'
});
new Vue ({
el: '#parentComponent',
data: function() {
return {
parentBackground: 'background-color:yellow;',
parentFontStyle: 'font-style: italic;'
},
})
</body>
When I run this code, only second style(childFontStyle) is applied to Child componnent template.
I also tried v-bind:style="[childBackgroundColor, childFontStyle]" and doesn't work.
Is there any way to apply both style?
You need to pass the props property as an object and can able to bind the props directly to the child. You have some typo mistakes that need to be fixed as well. Here is the working snippet.
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="parentComponent">
<child-component v-bind:propsdata="parentBackground" v-bind:propsdata2="parentFontStyle"></child-component>
</div>
<script>
Vue.component("child-component", {
template: `<p v-bind:style="[propsdata, propsdata2]">Child componnent Area</p>`,
props: ["propsdata", "propsdata2"]
});
new Vue({
el: "#parentComponent",
data: function() {
return {
parentBackground: {
"background-color": "yellow"
},
parentFontStyle: {
"font-style": "italic"
}
};
}
});
</script>
</body>
</html>

Is it possible to pass data from Polymer component to Vue component?

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>

Vuejs Reactive propery interpolation is not working

I've run into an issue where i'm creating components that are then being applied to the root App - after creating a dynamic child w/ vanilla JS. When i look at the Vue object in the console, message is not present, which i expect it to be - Can anyone tell me why?
Create the App
Dynamically add new DOM element w/ createElement with a {{ message }} property (ex: <div id="test">{{message}}</div>)
Create a custom component using Vue.Component (ex: <custom-component><custom-component> w/ pre-populated {{ messsage }} value test message
Render the Vue w/ the component w/ update props values for {{ message }}
Below is the actual code tested:
import Vue from 'vue/dist/vue.js';
export default {
name: 'app',
components:
{
HelloWorld
},
data()
{
return this;
},
mounted()
{
// #2 Create an Html Target that contains the component's name 'custom-element'
var v = document.createElement('div');
v.setAttribute('id', 'test');
v.innerHTML = '<custom-element></custom-element>';
var $element = this.$el.prepend(v);
// #1 Create a component
var MyComponent = Vue.component(
'custom-element',
{
template: '<div v-bind:id="UID">{{message}}</div>',
prop: ['UUID', 'message'],
data() {
return {
UID: '',
message: 'test message',
}
},
}
);
// #3 Append the component to the Html Target
window.vm = new Vue({
el: '#test',
components: {
'custom-component': MyComponent,
},
beforeCreate() {
return {
UID: 'x7x7x',
message: 'test message update...'
}
},
})
window.console.log(MyComponent);
window.console.log(this);
}
}`
Here's the main index.html:
`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>hello-world</title>
</head>
<body>
<noscript>
<strong>We're sorry but hello-world doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>'
Here's the main.js
'use strict'
import Vue from 'vue'
import App from './App.vue'
import Reaktr from './js/reaktr.js'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
data: {
Reaktr: new Reaktr(),
},
mounted() {
}
}).$mount('#app')
Here's the Helloworld.vue
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>

How to load external html file in a template in VueJs

I'm new to vue js.
I'm just creating a simple project where I just include vuejs through CDN. not using node/npm or cli.
I keep all my html markup in single html which looks messy as it grows. I tried to split html to views and want to include it by something analogous to ng-include of angularJs
I have worked in angular previously where there is ng-include to load external html files. I'm looking for something similar to that in vue. the whole point is to split my html files into more maintainable separate files.
have come across <template src="./myfile.html"/> but it doesn't work
Can somebody help me out
It's actually remarkably easy, but you need to keep something in mind. Behind the scenes, Vue converts your html template markup to code. That is, each element you see defined as HTML, gets converted to a javascript directive to create an element. The template is a convenience, so the single-file-component (vue file) is not something you'll be able to do without compiling with something like webpack. Instead, you'll need to use some other way of templating. Luckily there are other ways of defining templates that don't require pre-compiling and are useable in this scenario.
1 - string/template literals
example: template: '<div>{{myvar}}</div>'
2 - render function 🤢
example: render(create){create('div')}
Vue has several other ways of creating templates, but they just don't match the criteria.
here is the example for both:
AddItem.js - using render 😠 functions
'use strict';
Vue.component('add-item', {
methods: {
add() {
this.$emit('add', this.value);
this.value = ''
}
},
data () {
return {
value: ''
}
},
render(createElement) {
var self = this
return createElement('div', [
createElement('input', {
attrs: {
type: 'text',
placeholder: 'new Item'
},
// v-model functionality has to be implemented manually
domProps: {
value: self.value
},
on: {
input: function (event) {
self.value = event.target.value
// self.$emit('input', event.target.value)
}
}
}),
createElement('input', {
attrs: {
type: 'submit',
value: 'add'
},
on: {
click: this.add
}
}),
])
}
});
ListItem.js - using template literals (back-ticks)
'use strict';
Vue.component('list-item', {
template: `<div class="checkbox-wrapper" #click="check">
<h1>{{checked ? '☑' : '☐'}} {{ title }}</h1>
</div>`,
props: [
'title',
'checked'
],
methods: {
check() {
this.$emit('change', !this.checked);
}
}
});
and the html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.0/vue.js"></script>
<script src="ListItem.js"></script>
<script src="AddItem.js"></script>
</head>
<body>
<div id="app">
<add-item #add='list.push({title:arguments[0], checked: false})'></add-item>
<list-item v-for="(l, i) in list" :key="i" :title="l.title" :checked="l.checked" #change="l.checked=arguments[0]"></list-item>
</div>
<script type="text/javascript">
new Vue({
el: '#app',
data: {
newTitle: '',
list: [
{ title: 'A', checked: true },
{ title: 'B', checked: true },
{ title: 'C', checked: true }
]
}
});
</script>
</body>
</html>
TL; DR;
See it in action at : https://repl.it/OEMt/9
You cant. You must use async components - read guide here
Actually you can. This is kinda easy. Depends on your needs and situation. However, this code is NOT technically correct, however it will explain to you how it might work, gives you massive freedom and makes your original vue instance smaller.
To make this work, you will need vue router (cdn is ok) and in this case axios or fetch (if you dont care about supporting older browsers).
The only downfall in my opinion is that in content files you will need to add additional call parameter $parent . This will force vue to work.
index
<div id="app">
<router-link v-for="route in this.$router.options.routes" :to="route.path" :key="route.path">{{ route.name }}</router-link>
<section style="margin-top:50px;">
<component :is="magician && { template: magician }" />
</section>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
const viewer = axios.create({ baseURL: location.origin });
const routes = [
{"name":"Hello","slug":"hello","path":"/lol/index.html"},
{"name":"Page One","slug":"page_one","path":"/lol/page-one.html"},
{"name":"Page Two","slug":"page_two","path":"/lol/page-two.html"}
];
const app = new Vue({
router,
el: '#app',
data: {
magician: null,
},
watch: {
$route (to) {
this.loader(to.path);
}
},
mounted() {
this.loader(this.$router.currentRoute.path);
},
methods: {
viewer(opt) {
return viewer.get(opt);
},
loader(to) {
to == '/lol/index.html' ? to = '/lol/hello.html' : to = to;
this.viewer(to).then((response) => {
this.magician = response.data;
}).catch(error => {
alert(error.response.data.message);
})
},
huehue(i) {
alert(i);
}
}
});
</script>
hello.html content
<button v-on:click="$parent.huehue('this is great')">Button</button>
page-one.html content
<select>
<option v-for="num in 20">{{ num }}</option>
</select>
page-two.html content
// what ever you like
router explanation
To make this work perfectly, you will need to find a correct way to configure your htaccess to render everything if current page after first view is not index. Everything else should work fine.
As you can see, if it is index, it will load hello content file.
I faced the same issue and this is how I solved it , I also made a video about this question https://www.youtube.com/watch?v=J037aiMGGAw
create a js file ,for your component (logic) let's call it "aaaa.vue.js"
create an HTML file for your template that will be injected in your "aaaa.vue.js" and let's call it "aaaa.html"
Component file (Logic file javascript)
const aaaa = {
name:"aaaa",
template: ``,
data() {
return {
foo:"aaaa"
};
},
methods: {
async test() {
alert(this.foo)
},
},
};
Template file (HTML)
<!--template file-->
<div>
<button #click="test" > click me plz </button>
</div>
index.html
<html>
<head>
<title>my app</title>
</head>
<body>
<div id="app" class="main-content col-12">
<aaaa></aaaa>
</div>
</body>
</html>
<script src="axios.min.js"></script>
<script src="vue.js"></script>
<!-- load js file (logic) -->
<script src="aaaa.vue.js"></script>
<script>
document.addEventListener("DOMContentLoaded", async function () {
//register components
let html = await axios.get("aaaa.html"); // <---- Load HTML file
aaaa.template = html.data;
Vue.component("aaaa", aaaa);
new Vue({
el: "#app",
name: "main",
//... etc
});
});
</script>
Update :
I also created an example on github to see it in action
https://github.com/nsssim/Vue-CDN-load-component
Sure you can, this is the way we are doing it in all our components of our app.
<template src="../templates/the_template.html"></template>
<script>
export default {
name: 'ComponentName',
props: {},
computed: {},
methods: {},
};
</script>
<style lang="scss">
#import '../styles/myscss_file';
</style>
Will need to add
runtimeCompiler: true
to your vue.config.js file. That's it.