Polymer: detecting an object property change from the parent component - firebase

I have a component that loads an object from Firebase using firebase-document. Then that object is passed to a child component. When I change the object in the child component, the change is not detected in the parent, so the object is not updated in Firebase.
Here is the main component:
<dom-module id="some-component">
<template>
<firebase-document path="/projects/[[project_id]]" data="{{project}}"></firebase-document>
<some-child project="{{project}}"></some-child>
</template>
<script>
Polymer({
is: 'some-component',
properties: {
project: {type: Object, notify: true, observer: "projectChanged"}
},
projectChanged: function() {
console.log("we've detected some changes!");
}
});
</script>
</dom-module>
And here is the child component:
<dom-module id="some-child">
<template>
<a on-tap="changeProject">Let's change some property on our project!</a>
</template>
<script>
Polymer({
is: 'some-child',
properties: {
project: {type: Object, notify: true}
},
changeProject: function() {
this.project.name = "A new name"; // this never propagates back to the parent component
}
});
</script>
</dom-module>
The expected behavior is that when I click on the link, the object's property would change, it would be detected by the parent, and there would be a console.log. However it doesn't seem to happen.
Update with the solution
Using this.set() in the child component does the trick:
this.set("project.name", "A new name")

I think your issue here, is from the observer.
A simple observer like this only watch the reference of the object itself.
You can use a deep observer instead like this
properties: {
project: {type: Object, notify: true}
},
observers:[
'projectChanged(project.name)'
],
or for a more properties generic version
properties: {
project: {type: Object, notify: true}
},
observers:[
'projectChanged(project.*)'
],
and it should work like this.
Here is the link to the full documentation on the subject.
https://www.polymer-project.org/1.0/docs/devguide/observers#observing-path-changes
To be perfect you can also change the classic way of setting the value by the polymer way, it ensure better detection by the framework, remplacing
this.project.name = "A new name";
by
this.set("project.name", "A new name")
It will help on complexe binding case, or heavy objects it seems.

Related

Is there a way to use a prop as the site-key for vue-recaptcha?

I have a Form component in Vue where I import the vue-recaptcha like this:
<template>
... Contains the form and button that triggers onSubmit function
</template>
<script>
import { VueReCaptcha } from 'vue-recaptcha-v3';
Vue.use(VueReCaptcha, {
siteKey: "hard-coded site-key here",
loaderOptions: {
useRecaptchaNet: true,
},
});
export default {
methods: {
async onSubmit(e) {
// Uses the recapatcha and handles errors/success etc.
},
},
};
...
This works since the value for site-key is hard-coded.
However, I wish to be able to pass the site-key as a prop to the Form component and then use this as the site-key.
I tried something as bold as simply creating a prop in the Form component and passing it in as the site-key when setting the vue-recaptcha options, like this:
<script>
import { VueReCaptcha } from 'vue-recaptcha-v3';
Vue.use(VueReCaptcha, {
siteKey: this.siteKey,
loaderOptions: {
useRecaptchaNet: true,
},
});
export default {
props: {
siteKey: String,
},
...
</script>
This does not work because this.siteKey is undefined, as expected. However, is there a way to set the site-key value as the prop siteKey? Maybe there is a way to set the vue-recaptcha plugin options inside the component where this.siteKey isn't undefined, for example in mounted()?
Have you tried to provide the Key to the Form over prop?
<my-form siteKey="<My Site Key>" ...
or
<my-form site-key="<My Site Key>" ...
Pay attention:
You should be careful with components and prop's naming & using, since there are DOM
Template Parsing Caveats

Vue draggable change parent data when using KeepAlive

I have a vue3 app, and one of the child component uses vue-draggable.
In the parent component I have an object (let's call it myJson) which propagates to child component with props.
So far it works as expected.
However, when adding 'KeepAlive' to the parent component, every time I drag the items, myJson is set to the drag event instead of the origin data it had.
It still occures even if I pass to the child component a copy of myJson (with JSON parse-JSON stringify). See details below
parent component:
<template>
<KeepAlive>
<component :is="activeComponent" :my-json="myJson" />
</KeepAlive >
</template>
data: () => ({
myJson: { ...someData }
})
mid component:
<template>
<list-items :items="items" />
</template>
<script>
export default {
components: { ListItems },
computed: {
items() {
return JSON.parse(JSON.stringify(this.myJson.value.items))
}
},
}
</script>
child component (ListItems):
<template>
<draggable
v-model="items"
animation="100"
handle=".dnd-handle"
item-key="product"
class="items-list"
#start="drag=true"
#end="drag=false"
>
<template #item="{ element, index }">
{{element}}
</template>
</draggable>
</template>
<script>
import draggable from 'vuedraggable'
export default {
components: { draggable },
props: ['items'],
}
</script>
The items are displayed correctly in the component.
Before dragging, myJson is an object with my data.
After dragging myJson is an event.
Any idea?
vuedraggable version is 4.1.0
--UPDATE--
In parent component there is a function "update", which gets value and updates myJson.
methods: {
update (value) {
myJson = value
}
}
I found out that every time I drag, there is a call to this function with the dragging event as value, even when I try to catch the draggable events. Thats why myJson gets wrong value.
My problem was solved when I changed the function's name. But anyone knows why this happens?

Syncing a paper-input with firebase using polymer

How is this for a solution for syning the data from a paper input to a firebase database.
properties: {
teamid: {
type: String,
value: null
},
formid: {
type: String,
value: null
},
metaName: {
type: String,
value: null,
observer: '_updateMetaName'
}
},
_updateMetaName: function(metaName) {
var path = 'formModel/' + this.teamid + '/' + this.formid + '/meta/name';
firebase.database().ref(path).set(metaName);
},
The data metaName comes from a a paper-input element
<paper-input value="{{metaName}}"></paper-input>
I'm using an observer over the on-change attribute because I hate the idea that a user must move out of an input for it to persist.
I've also chosen not to use PolymerFire because i dosen't have some features I need and its not production ready.
I also don't like the idea that the observer runs multiple times before any data has been changed. And that should, i thought, break it but its working to my surprise.
What other options do I have?
Are their any disadvantages to my current solution?
One disadvantage is that every keystroke fires off a request to Firebase, which could be inefficient (a waste of CPU and bandwidth).
To address this, you could debounce the callback with this.debounce(jobName, callback, wait), as shown in the following demo.
HTMLImports.whenReady(_ => {
"use strict";
Polymer({
is: 'x-foo',
properties : {
metaName: {
type: String,
value: 'Hello world!',
observer: '_metaNameChanged'
}
},
_setFirebaseMetaName: function(metaName) {
var path = 'formModel/' + this.teamid + '/' + this.formid + '/meta/name';
//firebase.database().ref(path).set(metaName);
console.log('metaName', metaName);
},
_metaNameChanged: function(metaName) {
this.debounce('keyDebouncer',
_ => this._setFirebaseMetaName(metaName),
500);
}
});
});
<head>
<base href="https://polygit.org/polymer+1.5.0/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="paper-input/paper-input.html">
</head>
<body>
<x-foo></x-foo>
<dom-module id="x-foo">
<template>
<paper-input label="Meta Name" value="{{metaName}}"></paper-input>
</template>
</dom-module>
</body>
codepen
I've decided to go with on-keyup="_updateViewDesc" to stop a error occurring when multiple clients have the same page open. Using observers, when some data updates, it triggers the observer on all the connected clients. Causing characters to go missing.

What is the best way to declare global variables in Vue.js?

I need access to my hostname variable in every component.
Is it a good idea to put it inside data?
Am I right in understanding that if I do so, I will able to call it everywhere with this.hostname?
As you need access to your hostname variable in every component, and to change it to localhost while in development mode, or to production hostname when in production mode, you can define this variable in the prototype.
Like this:
Vue.prototype.$hostname = 'http://localhost:3000'
And $hostname will be available in all Vue instances:
new Vue({
beforeCreate: function () {
console.log(this.$hostname)
}
})
In my case, to automatically change from development to production, I've defined the $hostname prototype according to a Vue production tip variable in the file where I instantiated the Vue.
Like this:
Vue.config.productionTip = false
Vue.prototype.$hostname = (Vue.config.productionTip) ? 'https://hostname' : 'http://localhost:3000'
An example can be found in the docs:
Documentation on Adding Instance Properties
More about production tip config can be found here:
Vue documentation for production tip
a vue3 replacement of this answer:
// Vue3
const app = Vue.createApp({})
app.config.globalProperties.$hostname = 'http://localhost:3000'
app.component('a-child-component', {
mounted() {
console.log(this.$hostname) // 'http://localhost:3000'
}
})
Warning: The following answer is using Vue 1.x. The twoWay data mutation is removed from Vue 2.x (fortunately!).
In case of "global" variables—that are attached to the global object, which is the window object in web browsers—the most reliable way to declare the variable is to set it on the global object explicitly:
window.hostname = 'foo';
However form Vue's hierarchy perspective (the root view Model and nested components) the data can be passed downwards (and can be mutated upwards if twoWay binding is specified).
For instance if the root viewModel has a hostname data, the value can be bound to a nested component with v-bind directive as v-bind:hostname="hostname" or in short :hostname="hostname".
And within the component the bound value can be accessed through component's props property.
Eventually the data will be proxied to this.hostname and can be used inside the current Vue instance if needed.
var theGrandChild = Vue.extend({
template: '<h3>The nested component has also a "{{foo}}" and a "{{bar}}"</h3>',
props: ['foo', 'bar']
});
var theChild = Vue.extend({
template: '<h2>My awesome component has a "{{foo}}"</h2> \
<the-grandchild :foo="foo" :bar="bar"></the-grandchild>',
props: ['foo'],
data: function() {
return {
bar: 'bar'
};
},
components: {
'the-grandchild': theGrandChild
}
});
// the root view model
new Vue({
el: 'body',
data: {
foo: 'foo'
},
components: {
'the-child': theChild
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.16/vue.js"></script>
<h1>The root view model has a "{{foo}}"</h1>
<the-child :foo="foo"></the-child>
In cases that we need to mutate the parent's data upwards, we can add a .sync modifier to our binding declaration like :foo.sync="foo" and specify that the given 'props' is supposed to be a twoWay bound data.
Hence by mutating the data in a component, the parent's data would be changed respectively.
For instance:
var theGrandChild = Vue.extend({
template: '<h3>The nested component has also a "{{foo}}" and a "{{bar}}"</h3> \
<input v-model="foo" type="text">',
props: {
'foo': {
twoWay: true
},
'bar': {}
}
});
var theChild = Vue.extend({
template: '<h2>My awesome component has a "{{foo}}"</h2> \
<the-grandchild :foo.sync="foo" :bar="bar"></the-grandchild>',
props: {
'foo': {
twoWay: true
}
},
data: function() {
return { bar: 'bar' };
},
components: {
'the-grandchild': theGrandChild
}
});
// the root view model
new Vue({
el: 'body',
data: {
foo: 'foo'
},
components: {
'the-child': theChild
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.16/vue.js"></script>
<h1>The root view model has a "{{foo}}"</h1>
<the-child :foo.sync="foo"></the-child>
I strongly recommend taking a look at Vuex, it is made for globally accessible data in Vue.
If you only need a few base variables that will never be modified, I would use ES6 imports:
// config.js
export default {
hostname: 'myhostname'
}
// .vue file
import config from 'config.js'
console.log(config.hostname)
You could also import a json file in the same way, which can be edited by people without code knowledge or imported into SASS.
For any Single File Component users, here is how I set up global variable(s)
Assuming you are using Vue-Cli's webpack template
Declare your variable(s) in somewhere variable.js
const shallWeUseVuex = false;
Export it in variable.js
module.exports = { shallWeUseVuex : shallWeUseVuex };
Require and assign it in your vue file
export default {
data() {
return {
shallWeUseVuex: require('../../variable.js')
};
}
}
Ref: https://v2.vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
In vue cli-3 You can define the variable in main.js like
window.basurl="http://localhost:8000/";
And you can also access this variable in any component by using
the the
window.basurl
A possibility is to declare the variable at the index.html because it is really global. It can be done adding a javascript method to return the value of the variable, and it will be READ ONLY.
An example of this solution can be found at this answer:
https://stackoverflow.com/a/62485644/1178478

Polymer communication between elements

I want to achieve communication between child parent with Polymer element.
Here my index.html
<proto-receiver data="message">
<proto-element data="message"></proto-element>
</proto-receiver>
Both element have their respective "data" property
properties: {
data: {
value: 'my-data',
notify: true,
}
},
In proto-receiver, which is the parent I update "data" by handling simple click
<template>
<span on-tap="onClick">proto receiver: {{data}}</span>
<content></content>
</template>
onClick: function () {
this.data = 'new-message';
},
I want the change to be propagate to the child element as well, as it mentioned here.
I achieve this by passing a setter in my child element and called it like this. Which is, I guess, not the way it should be done.
Polymer.Base.$$('body').querySelector('proto-element').setData(this.data);
What I'm doing wrong
Thanks
UPDATE:
For those coming here. The proper way of doing this is by using Events.
Polymer 1.x
this.fire('kick', {kicked: true});
Polymer 2.x (simple javascript)
this.dispatchEvent(new CustomEvent('kick', {detail: {kicked: true}}));
In both case the receiver should implement the regular addEventListener
document.querySelector('x-custom').addEventListener('kick', function (e) {
console.log(e.detail.kicked); // true
})
To provide a concrete example to Scott Miles' comments, if you can wrap your parent and child elements in a Polymer template (such as dom-bind or as children to yet another Polymer element), then you can handle this declaratively. Check out the mediator pattern.
parent element:
<dom-module id="parent-el">
<template>
<button on-tap="onTap">set message from parent-el</button>
<p>parent-el.message: {{message}}</p>
<content></content>
</template>
<script>
Polymer({
is: 'parent-el',
properties: {
message: {
type: String,
notify: true
}
},
onTap: function() {
this.message = 'this was set from parent-el';
}
});
</script>
</dom-module>
child element:
<dom-module id="child-el">
<template>
<p>child-el.message: {{message}}</p>
</template>
<script>
Polymer({
is: 'child-el',
properties: {
message: {
type: String,
notify: true
}
}
});
</script>
</dom-module>
index.html:
<template is="dom-bind" id="app">
<parent-el message="{{message}}">
<child-el message="{{message}}"></child-el>
</parent-el>
</template>
<script>
(function(document) {
var app = document.querySelector('#app');
app.message = 'this was set from index.html script';
}) (document);
</script>
JS Bin
I was facing same issue and got solution for it and fixed it as below
this.fire('iron-signal', {name: 'hello', data: null});
You can refer this iron-signals you will get the solution which you are looking for its basically event fire from any element to another
Hope this will help you
Polymer iron signals

Resources