Polymer data binding to text from Firebase snapshot - javascript

I am working on a project that makes use of Firebase Authentication and <firebase-query> from the polymerfire elements. I use data binding in many places throughout my application and never had this problem.
I bind the user object, which was created when a user authenticated, in many places to receive the name of my users. The following code shows a custom element. In there, I am trying to bind besides the user object the Firebase snapshot to a property that is of type Object.
When I console.log() the vidobj property, it displays the whole object. However, I am unable to bind it to my text. Although, the same works for the user object property.
I believe this has something to do with the lifecycle in Polymer. Should the property not update automatically even though the value might be created later?
The following screenshot displays the two console.log's
with the content of the vidobj:
<link rel="import" href="../bower_components/polymer/polymer.html">
<dom-module id="my-singlevideo">
<template>
<style>
:host {
display: block;
}
</style>
<firebase-auth user="{{user}}"></firebase-auth>
<iron-localstorage
id="localstorage"
name="my-app-storage"
value="{{localUserDetails}}">
</iron-localstorage>
<h1>Name: [[user.displayName]]</h1>
<h1>Video Title: [[vidobj.title]]</h1>
</template>
<script>
Polymer({
is: 'my-singlevideo',
properties: {
user: {
type: Object,
},
localUserDetails: {
type: Object,
},
vidobj: {
type: Object,
},
},
ready: function() {
this.$.localstorage.reload();
var videoId = this.localUserDetails.lastClickedVid;
firebase.database().ref('/videos/' + videoId).once('value', function(snapshot) {
this.vidobj = snapshot.val();
console.log(this.vidobj);
console.log(this.vidobj.title);
});
},
});
</script>
</dom-module>

Your Firebase callback's context is not bound to your Polymer object, so you're actually setting vidobj on the outer context (usually the Window object).
To fix this, use Function#bind like this:
ready: function() {
// ...
firebase.database().ref('/videos/' + videoId).once('value',
function(snapshot) {
this.vidobj = snapshot.val();
console.log(this.vidobj);
console.log(this.vidobj.title);
}.bind(this)
);
}

Related

Paper-input value not accessible Polymer

I am pretty new with Polymer but think it is a great concept, although I have some troubles achieving pretty basic stuff that I normally have no problem with.
I have a template where I have a paper-input element. If I fill this element, I want to be able to click a button and transfer the value from that input field to somewhere else. Simple right?
Nope, no matter what I do, I can't seem to access the value of that input field. It's like the ID doesn't exist. I think it is because of the shadow dom, but I have no clue at all why!
I have tried with this.$.messaged.value, document.querySelector('#messaged').value and more without success. What else is there to do? Thanks in advance!!
<link rel="import" href="../../../bower_components/paper-input/paper-input.html">
<dom-module id="dd-bully">
<template>
<paper-input id="messaged" value="{{messaged}}"></paper-input>
<paper-button raised on-tap="sendmsg"></paper-button>
</template>
<script src="bully.js"></script>
</dom-module>
And the script content below:
class Bully extends Polymer.Element {
static get is() {
return 'dd-bully';
}
static get properties() {
return {
messaged: {
type: String,
value: "test message"
}
};
}
sendmsg() {
this.messaged = window.msg /* latest test */
console.log(messaged)
window.socket.emit('sendmsg', window.msg, err => {
if (err) {
console.error(err);
return;
}
});
}
}
customElements.define(Bully.is, Bully);
The <paper-input> is already updating messaged through the two-way data binding (i.e., value="{{messaged}}"). In sendmsg(), you could read the value of messaged via this.messaged (not this.messaged.value).
sendmsg() {
console.log('messaged', this.messaged);
this.messaged += ' SENT!';
}
demo

Store returned values from the iron-ajax request in variables (Polymer)

I am working the first time with Polymers iron-ajax element and came across a problem which I believe is fairly simple to solve but I am unable to find a solution online.
I retrieve user details with the iron-ajax element and loop through it with the dom-repeat. The first and last name of a user are getting placed in a custom card element that I created to display users. Everything works fine except that I am unable to store the returned id value in a variable.
I am sure this is possible with the usage of data binding but I couldn't get it to work. And even when I try to get the value with getAttribute in my JavaScript it just returns null.
In the code below my iron-ajax request & response can be found including my awkward attempt to get the id value {{item.id}}.To do so I created a on-tap function when a user clicks on one of the cards which then should get the value of the stdId attribute.
<dom-module id="my-students-view">
<template>
<style>
:host {
display: block;
}
</style>
<!-- Iron-Ajax created connection and gets data -->
<iron-ajax
auto
id="requestRepos"
url="http://api.dev/user/"
handle-as="json"
loading="{{isloading}}"
last-response="{{response}}"></iron-ajax>
<!-- dom-repeat iterates the response -->
<template is="dom-repeat" items="[[response.data]]" sort="sortData">
<student-card
id="studentCard"
to="http://localhost:8080/profile-view/{{item.id}}"
picsrc="../../images/anonymous.jpg"
name="{{item.first_name}} {{item.last_name}}"
stdId="{{item.id}}"
on-tap="sendId"></student-card>
</template>
</template>
<script>
Polymer({
is: 'my-students-view',
properties: {
stdId: {
notify: true,
}
},
//Sort the array/data alphabetically
sortData: function(a, b) {
if(a.first_name < b.first_name) return -1;
if(a.first_name > b.first_name) return 1;
return 0;
},
//Try to get the id value of a the student card
sendId: function() {
var idPropertie = this.$$("#studentCard");
var idValue = idPropertie.getAttribute('stdId');
console.log('the id is:' + idValue);
},
});
</script>
</dom-module>
Rewrite the sendId function:
sendId: function(event) {
var idValue = event.model.item.id;
console.log('the id is:' + idValue);
},

Polymer binding tap to an object method

I'm trying to bind a method to an on-tap attribute of a paper-button. After much testing, I've found that I can only bind a (for lack of a better word) top-level function, and not a method of an object in the template.
For example, I have a template, to which I have bound a number of objects, one of which is a user object. Object user has a bunch of methods and variables, like 'isNew' or 'reputation'. The user object also has a method 'addReputation'
I can use the object variables like this :
<template if = '{{user.new}}'><h1>{{user.name}}</h1></template>
And I can bind button taps like this:
<paper-button on-tap='{{addReputation}}'>Add Rep</paper-button>
But not like this:
<paper-button on-tap='{{user.addReputation}}'>Add Rep</paper-button>
Does anyone know why this may be?
if you set the method to a handler on your element's prototype it works. That way you can still keep things dynamic:
<script src="http://www.polymer-project.org/webcomponents.min.js"></script>
<script src="http://www.polymer-project.org/polymer.js"></script>
<polymer-element name="my-element" on-tap="{{tapHandler}}">
<template>
<style>
:host {
display: block;
}
</style>
click me
<content></content>
</template>
<script>
Polymer({
created: function() {
this.user = {
method: function() {
alert('hi');
}
};
this.tapHandler = this.user.method;
}
});
</script>
</polymer-element>
<my-element></my-element>
i'm sharing my plunk to resolve above problem. plunk link
In the template
<button on-tap="{{fncall}}" data-fnname="b">b call</button>
In the script
x.fncall = function(e) {
var target = e.target;
var fnName = target.getAttribute("data-fnname");
return x.datamodel[fnName]();
}
Polymer(x);

DOM element to corresponding vue.js component

How can I find the vue.js component corresponding to a DOM element?
If I have
element = document.getElementById(id);
Is there a vue method equivalent to the jQuery
$(element)
Just by this (in your method in "methods"):
element = this.$el;
:)
The proper way to do with would be to use the v-el directive to give it a reference. Then you can do this.$$[reference].
Update for vue 2
In Vue 2 refs are used for both elements and components: http://vuejs.org/guide/migration.html#v-el-and-v-ref-replaced
In Vue.js 2 Inside a Vue Instance or Component:
Use this.$el to get the HTMLElement the instance/component was mounted to
From an HTMLElement:
Use .__vue__ from the HTMLElement
E.g. var vueInstance = document.getElementById('app').__vue__;
Having a VNode in a variable called vnode you can:
use vnode.elm to get the element that VNode was rendered to
use vnode.context to get the VueComponent instance that VNode's component was declared (this usually returns the parent component, but may surprise you when using slots.
use vnode.componentInstance to get the Actual VueComponent instance that VNode is about
Source, literally: vue/flow/vnode.js.
Runnable Demo:
Vue.config.productionTip = false; // disable developer version warning
console.log('-------------------')
Vue.component('my-component', {
template: `<input>`,
mounted: function() {
console.log('[my-component] is mounted at element:', this.$el);
}
});
Vue.directive('customdirective', {
bind: function (el, binding, vnode) {
console.log('[DIRECTIVE] My Element is:', vnode.elm);
console.log('[DIRECTIVE] My componentInstance is:', vnode.componentInstance);
console.log('[DIRECTIVE] My context is:', vnode.context);
// some properties, such as $el, may take an extra tick to be set, thus you need to...
Vue.nextTick(() => console.log('[DIRECTIVE][AFTER TICK] My context is:', vnode.context.$el))
}
})
new Vue({
el: '#app',
mounted: function() {
console.log('[ROOT] This Vue instance is mounted at element:', this.$el);
console.log('[ROOT] From the element to the Vue instance:', document.getElementById('app').__vue__);
console.log('[ROOT] Vue component instance of my-component:', document.querySelector('input').__vue__);
}
})
<script src="https://unpkg.com/vue#2.5.15/dist/vue.min.js"></script>
<h1>Open the browser's console</h1>
<div id="app">
<my-component v-customdirective=""></my-component>
</div>
If you're starting with a DOM element, check for a __vue__ property on that element. Any Vue View Models (components, VMs created by v-repeat usage) will have this property.
You can use the "Inspect Element" feature in your browsers developer console (at least in Firefox and Chrome) to view the DOM properties.
Hope that helps!
this.$el - points to the root element of the component
this.$refs.<ref name> + <div ref="<ref name>" ... - points to nested element
💡 use $el/$refs only after mounted() step of vue lifecycle
<template>
<div>
root element
<div ref="childElement">child element</div>
</div>
</template>
<script>
export default {
mounted() {
let rootElement = this.$el;
let childElement = this.$refs.childElement;
console.log(rootElement);
console.log(childElement);
}
}
</script>
<style scoped>
</style>
So I figured $0.__vue__ doesn't work very well with HOCs (high order components).
// ListItem.vue
<template>
<vm-product-item/>
<template>
From the template above, if you have ListItem component, that has ProductItem as it's root, and you try $0.__vue__ in console the result unexpectedly would be the ListItem instance.
Here I got a solution to select the lowest level component (ProductItem in this case).
Plugin
// DomNodeToComponent.js
export default {
install: (Vue, options) => {
Vue.mixin({
mounted () {
this.$el.__vueComponent__ = this
},
})
},
}
Install
import DomNodeToComponent from'./plugins/DomNodeToComponent/DomNodeToComponent'
Vue.use(DomNodeToComponent)
Use
In browser console click on dom element.
Type $0.__vueComponent__.
Do whatever you want with component. Access data. Do changes. Run exposed methods from e2e.
Bonus feature
If you want more, you can just use $0.__vue__.$parent. Meaning if 3 components share the same dom node, you'll have to write $0.__vue__.$parent.$parent to get the main component. This approach is less laconic, but gives better control.
Since v-ref is no longer a directive, but a special attribute, it can also be dynamically defined. This is especially useful in combination with v-for.
For example:
<ul>
<li v-for="(item, key) in items" v-on:click="play(item,$event)">
<a v-bind:ref="'key' + item.id" v-bind:href="item.url">
<!-- content -->
</a>
</li>
</ul>
and in Vue component you can use
var recordingModel = new Vue({
el:'#rec-container',
data:{
items:[]
},
methods:{
play:function(item,e){
// it contains the bound reference
console.log(this.$refs['key'+item.id]);
}
}
});
I found this snippet here. The idea is to go up the DOM node hierarchy until a __vue__ property is found.
function getVueFromElement(el) {
while (el) {
if (el.__vue__) {
return el.__vue__
} else {
el = el.parentNode
}
}
}
In Chrome:
Solution for Vue 3
I needed to create a navbar and collapse the menu item when clicked outside. I created a click listener on windows in mounted life cycle hook as follows
mounted() {
window.addEventListener('click', (e)=>{
if(e.target !== this.$el)
this.showChild = false;
})
}
You can also check if the element is child of this.$el. However, in my case the children were all links and this didn't matter much.
If you want listen an event (i.e OnClick) on an input with "demo" id, you can use:
new Vue({
el: '#demo',
data: {
n: 0
},
methods: {
onClick: function (e) {
console.log(e.target.tagName) // "A"
console.log(e.targetVM === this) // true
}
}
})
Exactly what Kamil said,
element = this.$el
But make sure you don't have fragment instances.
Since in Vue 2.0, no solution seems available, a clean solution that I found is to create a vue-id attribute, and also set it on the template. Then on created and beforeDestroy lifecycle these instances are updated on the global object.
Basically:
created: function() {
this._id = generateUid();
globalRepo[this._id] = this;
},
beforeDestroy: function() {
delete globalRepo[this._id]
},
data: function() {
return {
vueId: this._id
}
}

How to bind an AngularJS variable as a Polymer global?

I have an object variable in an Angular controller with some properties. I want to create a series of polymer elements that takes that variable an the name of the property and shows them in a specific format (depending of type and other attributes). Something like the next example:
<polymer-element name="x-property" attributes="data property">
<template>
{{data.labels[property]}}: {{data[property]}}
</template>
<script>
Polymer('x-property', {
data: {}
});
</script>
</polymer-element>
And then I use it as this:
<x-property data="{{person}}" property="firstName"></x-property>
That works just fine. But now I want to avoid to specify the attribute data in all the elements. Reading Polymer documentation I see that it is possible to have global variables. I followed the example created the app-globals element, as shown in the api guide but when I try to access the property, instead of having the object "person" I got the text "{{person}}"
<polymer-element name="app-globals" attributes="values">
<script>
(function () {
var values = {};
Polymer('app-globals', {
ready: function () {
this.values = values;
for (var i = 0; i < this.attributes.length; ++i) {
var attr = this.attributes[i];
values[attr.nodeName] = attr.value;
}
}
});
})();
</script>
</polymer-element>
<polymer-element name="x-property" attributes="property">
<template>
<app-globals id="globals" values="{{globals}}"></app-globals>
{{globals.data.labels[property]}}: {{globals.data[property]}}
{{globals.data}}
</template>
<script>
Polymer('x-property', {
});
</script>
</polymer-element>
So in my html I have:
<app-globals data="{{person}}"></app-globals>
<x-property property="firstName"></x-property>
And the result I get is just this:
:
{{person}}
Is there anyway I can make this work as it works in the first example?
Because attributes are only strings, this code values[attr.nodeName] = attr.value; cannot capture your object-valued data. Instead, JavaScript converts your object to a string, which is why you see [Object object].
Capturing objects with mustaches ({{ }}) is a special Polymer feature that you enable by publishing the property as an attribute (or listing it in the publish map in the prototype).
If, instead of making app-globals generic, we instead publish values and data directly, then we can make it work like so:
<polymer-element name="app-globals" attributes="values data">
<script>
(function () {
var values = {};
Polymer('app-globals', {
created: function() {
this.values = values;
},
dataChanged: function() {
this.values.data = this.data;
}
});
})();
</script>
</polymer-element>
<polymer-element name="x-property" attributes="property">
<template>
<app-globals values="{{globals}}"></app-globals>
{{globals.data.labels[property]}}: {{globals.data[property]}}
{{globals.data | json}}
</template>
<script>
Polymer('x-property', {
json: function(s) {
return JSON.stringify(s);
}
});
</script>
</polymer-element>
Your original code doesn't have the json filter, but otherwise we are again asking Javascript to put an object in a string context, and it will render [Object object].
http://jsbin.com/vusayo/10/edit

Categories