Polymer document.querySelector not working as expected - polymer

Either I am doing something horribly wrong or Polymer just doesn't like me. See following:
<polymer-element name="menu-paper-ui" noscript>
<template>
<paper-dialog heading="Dialog" transition="paper-dialog-transition-bottom">
[ .. ]
</paper-dialog>
<paper-button label="Dialog Bottom" on-tap="{{toggleDialog}}"></paper-button>
</template>
<script>
Polymer('menu-paper-ui', {
toggleDialog : function() {
var dialog = document.querySelector('paper-dialog');
console.log(dialog); //returns null
dialog.toggle();
}
})
</script>
</polymer-element>
Now, I have my reasons to use querySelector. So, if someone can tell me whats going wrong that will be great!

This question is nearly identical to Using querySelector to find nested elements inside a Polymer template returns null.
The short answer is that elements in a polymer-element's template are put into the ShadowDOM of that element, are not not visible to the anything outside of that element. This is so that you can control styling more easily, and element IDs are scoped.
You can either give the dialog an id and use Polymer's automatic node finding, or use this.shadowRoot.querySelector('paper-dialog').

The Problem is that you can not access the shadow DOM inside a custom element with document.querySelector. See my answer to a similar question.

Related

How to access Polymer functions from JS

Sorry if this comes out a bit garbled, I'm not sure how to ask this question.
What I am trying to do is keep the DOM synced with a localStorage value, and am updating the localStorage value with an interact.js mouse event.
Currently, I am able to properly set the localStorage value, but am having problems updating the DOM.
My current build is within the Polymer framework, so I am having trouble selecting shadow DOM content.
The DOM tree looks like
PARENT-ELEMENT
# SHADOW ROOT
EL
EL
DIV
CUSTOM ELEMENT
EL
EL
Here are some ways I have failed to solve the problem. The Custom Element is in pure JS, since I am not sure how to properly wrap interact.js function in Polymer:
I tried directly accessing the PARENT-ELEMENT's shadow DOM from the Custom Element in pure JS.
var shadowDOMNode = document.querySelector('PARENT-ELEMENT');
var dom_object_1 = shadowDOMNode.querySelector('#dom_object_1');
dom_object_1.innerHTML = localStorage.dom_object_1;
I tried selecting a helper updateDOM() function from the PARENT Polymer element and running it from the Custom Element's setter directly.
if (event.dy > 0) {
this.$$('PARENT-ELEMENT').updateDOM();
}
Maybe I am taking the wrong approach entirely, but I haven't been able to find analogues for interact.js in using native Polymer functions.
I hope this question was clear enough...
If we ignore the interact.js part of the problem and focus on Polymer, you could probably solve this without coupling the two.
To bind to a localStorage value with Polymer, use the <iron-localstorage> element. In the following example, the localStorage value named flavor_1_amount is loaded and stored into a property named _flavor1Amount. If the value doesn't exist in localStorage or is empty, the <iron-localstorage> element fires an event (iron-localstorage-load-empty), which allows you to bind to a callback (e.g., to initialize it).
<iron-localstorage name="flavor_1_amount"
value="{{_flavor1Amount}}"
use-raw
on-iron-localstorage-load-empty="_initFlavor1Amount">
</iron-localstorage>
In the same element, you could provide an input for the user to update the localStorage value.
<paper-input label="Flavor Amount (mL)" value="{{_flavor1Amount}}"></paper-input>
And you can use <iron-localstorage>.reload() to keep your data binding in sync, assuming it could be changed externally.
See this codepen for a full demo. Check your localStorage from Chrome DevTools:
Generally speaking you should use this.set() or any of the array mutation methods if it's an array in order for the ShadowDOM to be notified properly.
Since you want to perform this update from outside the element itself, imperatively, I'd suggest this:
Expose a couple of methods from your element that you can use to add/remove/change property values from outside your element.
These methods would internally use the proper channels to make the changes.
An example (you can call addItem() to add items from outside your element):
<base href="https://polygit.org/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
<dom-module id="x-example">
<template>
<template is="dom-repeat" items="[[data]]">
<div>{{item.name}}</div>
</template>
</template>
<script>
HTMLImports.whenReady(function() {
"use strict";
Polymer({
is: "x-example",
properties: {
data: {
type: Array,
value: [
{name: "One"},
{name: "Two"},
{name: "Three"}
]
}
},
// Exposed publicly, grab the element and use this method
// to add your item
addItem: function(item) {
this.push("data", item);
}
});
});
</script>
</dom-module>
<x-example id="x-example-elem"></x-example>
<script>
setTimeout(function() {
// simply 'grab' the element and use the
// `addItem()` method you exposed publicly
// to add items to it.
document.querySelector("#x-example-elem").addItem({name: "Four"});
}, 2500);
</script>
Important: That being said, this is not the "Polymeric" way of doing stuff as this programming-style is imperative, in constrast with Polymer's style which is more declarative. The most Polymeric solution is to wrap your interact.js functionality in an element itself and use data-binding between your 2 elements to perform the changes.

Call Method of Child Web Component

I'm really having difficulty trying to figure out how to call a function of a nested Polymer web component.
Here's the markup:
<rise-playlist>
<rise-playlist-item duration="5">
<rise-distribution distribution='[{"id":"VGZUDDWYAZHY"}]'></rise-distribution>
</rise-playlist-item>
</rise-playlist>
The rise-distribution component has a canPlay function that I would like to call from inside of rise-playlist.
The dom-module definition of rise-playlist looks like this:
<dom-module id="rise-playlist">
<template>
<content id="items" select="rise-playlist-item"></content>
</template>
</dom-module>
I can successfully access the rise-distribution element like this:
var distribution = Polymer.dom(this.$.items[0]).querySelector("rise-distribution");
However, when I try to call distribution.canPlay(), it says that distribution.canPlay is not a function.
I've defined the dom-module of rise-playlist-item like this:
<dom-module id="rise-playlist-item">
<content id="dist" select="rise-distribution"></content>
</dom-module>
Not sure if I need that <content> tag, although neither works.
Any ideas?
Thx.
I know that there have been a while but I am sure this problems still occurs as it is being viewed number of times.
Probably there is a problem with your component definition. Let me explain.
This is the way you put your child component inside DOM:
<your-child-component></your-child-component>
And and this should be the definition of your component:
Polymer({
is: 'your-child-component',
apiMethod: function() {
//some stuff
}
});
If you by mistake or due copy-paste error mistype the is: 'your-child-component' part, so it will not reflect the <your-child-component> you will get confused becouse your:
this.$$('your-child-component').apiMethod();
will tell you that there is no method you are willing to call.
Polymer correctly identified and selected from DOM <your-child-component> but if you have different is property (like for example is: your_child_component>) it will not attach its API to dom element you selected.
I hope that it will help if anyone ever will encounter this problem.

Polymer attributes and variable mapping

I have a polymer element looking like this:
<polymer-element name="foo" attributes="bar">
<template>
<core-ajax
url="url.php"
params="{{ {last: bar} }}"></core-ajax>
{{bar}}
</template>
</polymer-element>
Though, when I create element, the attribute shows up on the page, but the ajax request is like url.php?last so it seems that variable is undefined.
Also, this.bar returns undefined.
How can I solve that?
Keep in mind core-ajax has been deprecated in polymer 1.0 to iron-ajax.
Anyway, is params bind being resolved properly?
Based on documentation for new iron-ajax element the sintax should be:
params='{"alt":"json", "q":"chrome"}'

How to reference "this" in polymer expression

I want to pass the current polymer element as an attribute to another element. Using {{this}} returns null. Is there a supported way to access the value of "this" other than creating an attribute that returns the value
Example
<polymer-element name='my-el'>
<template>
<sub-element target={{this}}>
I think no. When a new instance of a polymer element is created, scope for template is the element instance and expressions are evaluated using this scope. So, {{this}} is evaluated using the element instance and expected to be a property on model.
"{{}}" appears to be the solution
I can't find documentation on it, but it works in v0.5 and is used in existing polymer components (core-a11y-keys)
According to the expression scopes section of the Polymer 0.5 documentation, you can use an empty binding expression: "{{}}". Note that this hasn't been documented in Polymer 1.0, so no guarantees, but it appears to still be working.
<polymer-element name="my-el">
<template>
<sub-element target="{{}}"></sub-element>
</template>
</polymer-element>
Alternatively, you can use the parentElement attribute which all polymer elements have. Here's an example from the documentation for using core-a11y-keys for keyboard navigation. Using `parentElement' would look like this:
<polymer-element name="my-el">
<template>
<sub-element target="{{parentElement}}"></sub-element>
</template>
</polymer-element>
Be careful with parentElement though. If you move the element, its parent could change.

Is it possible to inject HTML into a polymer component via an attribute?

I'm using one of the core polymer components that basically has:
<polymer-element attributes="label">
<div>{{label}}</div>
as part of the source. I'd like to inject some HTML into this so that it ultimately renders as:
<div>Item <small>Description</small></div>
Is there any way to do this without copying the entire component (which is basically impossible considering the dependency chain)?
Polymer doesn't allow setting HTML inside {{}} expressions because it's a known XSS outlet. However, there are ways around it (1, 2).
I'm not sure there's a great way around this issue but I found something that works. You want to extend the element but also need to modify its shadow dom because of the .innerHTML limitation. Taking paper-button as an example, it has an internal {{label}}. You could extend the element, drill into its shadow dom, and set .innerHTML of the container where {{label}} is set. React to label changing (labelChanged) and call this.super():
<polymer-element name="x-el" extends="paper-button">
<template>
<shadow></shadow>
</template>
<script>
Polymer('x-el', {
labelChanged: function() {
// When label changes, find where it's set in paper-button
// and set the container's .innerHTML.
this.$.content.querySelector('span').innerHTML = this.label;
// call paper-button's labelChanged().
this.super();
}
});
</script>
</polymer-element>
Demo: http://jsbin.com/ripufoqu/1/edit
Problem is that it's brittle and requires you to know the internals of the element you're extending.