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
Related
I want to change the background of my component when the window is scrolled down, and I tried implementing it this way but it doesnt work. Any guidance would be helpful!
export class NewsContainer{
#Listen('scroll', { target: 'window' })
handleScroll(ev) {
var newsContainer= document.querySelector(".newsContainer");
newsContainer.classList.remove("transparentBackground");
newsContainer.classList.add("darkBackground");
}
render(){
return [
<div class="newsContainer transparentBackground">Content</div>
]
}
}
I get the following error:
news-container.entry.js:9 Uncaught TypeError: Cannot read property 'classList' of null
at NewsContainer.handleScroll
The problem I faced from time to time is that some hooks or listener are executed before the elements are rendered and available in the DOM tree (the render function is called). That can lead to a termination of the JavaScript in order to stop the application. I bet this is exactly why you get this error. So a simple if condition should be sufficient like:
#Listen('scroll', { target: 'window' })
handleScroll(ev) {
var newsContainer= document.querySelector(".newsContainer");
if(newsContainer && newsContainer.classList){
newsContainer.classList.remove("transparentBackground");
newsContainer.classList.add("darkBackground");
}
}
I would go a step further and use references since I think it looks a bit clearer and has the charme that you are independent of classNames. and you can access the element from every method and validate a bit easier:
export class NewsContainer{
private element;
#Listen('scroll', { target: 'window' })
handleScroll(ev) {
if(!this.element) return;
this.element.classList.remove("transparentBackground");
this.element.classList.add("darkBackground");
}
render(){
return [
<div ref={(el) => {this.element = el}} class="newsContainer transparentBackground">Content</div>
]
}
}
You can use this.element which refers to your div element.
I have created a function in global.function.js file as
function getData(flag) {
if (flag === 1) {
return "one";
}
else {
return "not one";
}
}
which then is imported using custom-js-import.html element:
<script src="global.function.js"></script>
When I tried to access the above function in custom-element.html, I am able to access it in the script part but not in the template part.
Is there any way I can access the function inside the HTML element?
<!-- custom-element.html -->
<link rel="import" href="https://polygit.org/components/polymer/polymer-element.html">
<link rel="import" href="custom-js-import.html">
<dom-module id="custom-element">
<template>
<div>
Hello
</div>
<div id="data"></div>
<div>{{getData(1)}}</div><!-- Unable to access this from here -->
<div>{{getLocalData()}}</div>
</template>
<script>
// Define the class for a new element called custom-element
class CustomElement extends Polymer.Element {
static get is() { return "custom-element"; }
constructor() {
super();
}
ready(){
super.ready();
this.$.data.textContent = "I'm a custom-element.";
console.log(getData(1));//can be easily accessed from here
}
getLocalData(){
return "local";
}
}
// Register the new element with the browser
customElements.define(CustomElement.is, CustomElement);
</script>
</dom-module>
Sample Code
Is there any way I can access the function inside the HTML element?
Not really. In order to use data in a template you need to bind it to a property (Polymer calls this data binding).
Polymer's data binding system is designed for binding values to a template. Those values are typically just literals (e.g. strings and numbers) or plain ole javascript objects e.g. {a: 'someval', b: 5}. Polymer's data binding system is not designed to bind functions to a template and you can't just use javascript inside of a template. (If you're really into that idea, check out React as a replacement to polymer).
The polymer way to do what you're trying to do is to use a computed property. Instead of calling a function inside the template, create a computed property that reacts to changes of other variables. When the state of a property changes, the computed property will change too. This state can be thought of as the argument of your function.
I think it's better just to see the code working yeah (tested in chrome)?
<link rel="import" href="https://polygit.org/components/polymer/polymer-element.html">
<link rel="import" href="custom-js-import.html">
<dom-module id="custom-element">
<template>
<div>
Hello
</div>
<label>
<input type="number" value="{{flag::input}}">
</label>
<h1>from flag: [[flag]]</h1>
<div id="data"></div>
<div>{{boundComputedData}}</div><!-- Unable to access this from here -->
<div>{{getLocalData()}}</div>
</template>
<script>
// Define the class for a new element called custom-element
class CustomElement extends Polymer.Element {
static get is() {
return "custom-element";
}
constructor() {
super();
}
getData(flag) {
const flagAsNumber = parseInt(flag);
if (flagAsNumber === 1) {
return "one";
} else {
return "not one";
}
}
ready() {
super.ready();
this.$.data.textContent = "I'm a custom-element.";
console.log(this.getData(1)); //can be easily accessed from here
}
getLocalData() {
return "local";
}
static get properties() {
return {
flag: {
type: Number,
value: 0
},
boundComputedData: {
type: String,
computed: 'getData(flag)'
}
};
}
}
// Register the new element with the browser
customElements.define(CustomElement.is, CustomElement);
</script>
</dom-module>
<custom-element></custom-element>
So What I'm doing here is:
creating a computed property boundComputedData and setting the computed property to 'getData(flag)' which will make it use the class function getData.
Now whenever the state the property flag changes, the computed property will update.
Hope it helps!
Thanks to Rico Kahler for suggesting me to use a mixin. Using mixin solved my problem. You can view the full working sample here.
All the global functions can be defined in the mixin.
<!--custom-mixin.html-->
<script>
const CustomMixin = superclass => class extends superclass {
static get properties() {
return {};
}
connectedCallback() {
super.connectedCallback();
}
getData(flag) {
if (flag === 1) {
return "one(From Mixin)";
} else {
return "not one(From Mixin)";
}
}
};
</script>
And remember to import the mixin file and add that mixin to your element.
<!-- custom-element.html -->
<link rel="import" href="https://polygit.org/components/polymer/polymer-element.html">
<link rel="import" href="custom-mixin.html">
<dom-module id="custom-element">
<template>
<div>
Hello
</div>
<div id="data"></div>
<div>{{getData(1)}}</div>
<!-- Unable to access this from here -->
<div>{{getLocalData()}}</div>
</template>
<script>
// Define the class for a new element called custom-element
class CustomElement extends CustomMixin(Polymer.Element) {
static get is() {
return "custom-element";
}
constructor() {
super();
}
ready() {
super.ready();
this.$.data.textContent = "I'm a custom-element.";
console.log(getData(1)); //can be easily accessed from here
}
getLocalData() {
return "local";
}
}
// Register the new element with the browser
customElements.define(CustomElement.is, CustomElement);
</script>
</dom-module>
So basically what I'm struggling with is how to pass the reference of the parent element to its child element i.e the custom remove element?
can anyone please help me out!
*******************this is the el-insert element********************
// this element recieves data from another element(username and comment)
<link rel="import" href="./remove.html">
<dom-module id="el-insert">
<template >
<div id="userComment"><span>{{username}}</span>: {{saveComment}}</div>
<input id="edit" type="text" value={{saveComment::input}}>
<hr>
<reply-comment></reply-comment>
<button on-click="edit">Edit</button>
<remove-comment ></remove-comment>
</template>
<script>
class elInsert extends Polymer.Element {
static get is() { return 'el-insert'; }
static get properties(){
return {
saveComment:{
type:String,
notify:true
},
username:{
type:String,
notify:true
}
}//return ends
}
edit (){
$(this.$.userComment).toggleClass('hide');
var display = $(this.$.edit).css('display');
if(display == 'none'){
$(this.$.edit).css('display','block');
}else{
$(this.$.edit).css('display','none');
}
}
}
window.customElements.define(elInsert.is, elInsert);
</script>
</dom-module>
***************the remove button element******************
<link rel="import" href="../bower_components/polymer/polymer.html">
<dom-module id="remove-comment">
<template>
<button on-click="remove">el-Remove</button>
</template>
<script>
class removeComment extends Polymer.Element {
static get is() { return 'remove-comment'; }
static get properties(){
return{
}//return ends
}
remove(){
var element = this.parentNode.host;
$(element).remove();
}
}
window.customElements.define(removeComment.is,removeComment);
</script>
</dom-module>
This code works perfectly for me.
But how will I do the same thing using fire or dispatch as mentioned in the answer by Nicolas.
How to pass event details from parent element to child element?
Also, I want to make this element reusable so that I can simply drop it into another place like in a reply to delete that also.
(Also, I'm new very new to polymer so if there is anything else that I can improve upon in this code then please let me know)
I hope now you guys can help me out.
You do not want to do that - in general you should just pass properties to the child element and catch events from the child in the parent elements.
properties go down and events go up -
In you case you do not have to pass anything to the child element. The child element should just fire an event and the parent should respond to it when it catches it.
Something like that:
<dom-module id="child-element">
<template>
<div on-tap="_deleteComment"></div>
</template>
<script>
class ChildElement extends Polymer.Element {
...
_deleteComment() : {
// fire event to be caught by parent
const deleteEvent = new CustomEvent('deleteComment', {detail: {
whatever: {
you: 'want',
},
}});
this.dispatchEvent(deleteEvent);
}
}
...
</script>
</dom-module>
<dom-module id="parent-element">
<template>
<child-element on-delete-comment="_deleteComment"></child-element>
</template>
<script>
class ParentElement extends Polymer.Element {
...
_deleteComment(evt) : {
// handle evt
}
}
...
</script>
</dom-module>
#daKmoR is right - more context and some code would help -
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)
);
}
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);