Dynamically injecting a template in Polymer 1.0 - polymer

I'm trying to inject and display a <template> the right way into a Polymer Webapp, but I have a few difficulties with it. (… or maybe I misunderstood the 1.0 Documentation?)
The documentation about manipulation the DOM says:
Polymer provides a custom API for manipulating DOM such that local DOM and light DOM trees are properly maintained. These methods and properties have the same signatures as their standard DOM equivalents, except that properties and methods that return a list of nodes return an Array, not a NodeList.
Note: All DOM manipulation must use this API, as opposed to DOM API directly on nodes.
So I guess I have to use Polymer.dom API everywhere to manipulate the DOM, which makes sense to me, because this way Polymer can stay in sync with the generated shady DOM. No DOM.appendChild(), instead Polymer.dom().appendChild(). And manipulating the shady DOM directly wouldn't be a great idea … or would it?
Imagine a simple page structure:
<body>
<template is="dom-bind" id="app">
<main>
<template is="dom-bind" id="content">
<p>Lorem ipsum dolor sit amet.</p>
</template>
</main>
</template>
</body>
And a second small snippet which I can import into the page.
<template id="snippet">
<p>Consectetur adipisici elit.</p>
</template>
This template should be replaced/referenced with the #content. So, let's start.
Importing the snippet is easy. I can fetch it and get the DOM Element of it.
Polymer.Base.importHref('/snippet', function(e) {
// on success: imported content is in e.target.import
var template = Polymer.dom(e.target.import).querySelector('#snippet');
// until here it works, `template` is the template from my snippet
...
Now I guess I have to append this to my template#app and change the ref of template#content to content… if changing the ref is still supported? And how am I supposed to do that? I get stuck every time, no matter how I approach this.
var app = Polymer.dom(this).querySelector('#app'); // Works, is template#app
var app = document.querySelector('#app'); // Same result
Polymer.dom(app).appendChild(template); // will append it, but outside of the document fragment
Polymer.dom(app.root).appendChild(template); // won't do anything
Polymer.dom(app).querySelector('template'); // undefined
Polymer.dom(app.root).querySelector('template'); // undefined
app.querySelector('template'); // undefined
I looked hours and days into this, trying to find a solution. It works with the standard DOM API, but I don't think that's the right way to do this. If somebody could solve my confusion, it would be really great.
EDIT: Or will Polymer.dom(this) do it's thing and I don't need to call Polymer.dom(app)? But again, I tried it and it won't work. Aaargh, it's just so confusing.

If I understood you correctly and you want to insert the template to local dom (inserting it somewhere else doesn't really make sense) then it's Polymer.dom(this.root).appendChild.
From https://www.polymer-project.org/1.0/docs/devguide/local-dom.html#dom-api: In order to insert/append into the local dom of a custom element, use this.root as the parent.

Related

JS method not defined

I want to use a java-script method in a polymer Template. I am using Vaadin with Polymer Elements. In my Project I have a Vaadin-Grid of Objects that can be of different type. I want to render these types with different Templates.
This problem can be solved with a dom-if template, as described by ollitietavainen in this answer
This works perfectly, but there is a problem. When using more than two different Types of Objects in the Grid, one would need to use the same amount of booleans to set that up. Suppose we have a fictional shop that displays PC-Parts, and each type of PC-Part needs to be rendered with its own template, then we would need something like the fallowing. This is quite cumbersome.
private boolean isMemory(AbstractPcPart pcPart) {
return pcPart.getClass().equals(Memory.class);
}
private boolean isGraphicsCard(AbstractPcPart pcPart) {
return pcPart.getClass().equals(GraphicsCard.class);
}
private boolean isCPU(AbstractPcPart pcPart) {
return pcPart.getClass().equals(CPU.class);
}
// … is-checker for all other types of pcParts.
private void initColumn() {
addColumn(Objects.requireNonNull(CardFactory.getTemplate())
.withProperty("partCard", CardFactory::create)
.withProperty("isMemory", this::isMemory)
.withProperty("isGraphicsCard", this::isGraphicsCard)
.withProperty("isCPU", this::isCPU)
// add all other properties
);
}
The corresponding Templates would look something like this.
<template is='dom-if' if='[[item.isMemory]]'>"
<memory-card part-card='[[item.partCard]]'>
</memory-card>"
</template>
<template is='dom-if' if='[[item.isGraphicsCard]]'>"
<graphics-card part-card='[[item.partCard]]'>
</graphics-card-card>"
</template>
<template is='dom-if' if='[[item.isCPU]]'>"
<cpu-card part-card='[[item.partCard]]'>
</cpu-card>"
</template>
<!-- one additional template for every type of part -->
The question now is, if there is any other way, that would not be needing all these Properties.
Luckily there is, as Kuba Šimonovský explained in an answer to another question.
Using this method we could rewrite the code from above to something like the fallowing.
private String type(AbstractPcPart pcPart) {
return pcPart.getClass().getSimpleName();
}
private void initColumn() {
addColumn(Objects.requireNonNull(CardFactory.getTemplate())
.withProperty("partCard", CardFactory::create)
.withProperty("type", this::type));
}
This time we use a java-script method to conditionally select the corresponding template.
<template is='dom-if' if='[[_isEqualTo(item.type, "Memory")]]'>"
<memory-card part-card='[[item.partCard]]'>
</memory-card>"
</template>
<template is='dom-if' if='[[_isEqualTo(item.type, "GraphicsCard")]]'>"
<graphics-card part-card='[[item.partCard]]'>
</graphics-card-card>"
</template>
<template is='dom-if' if='[[_isEqualTo(item.type, "CPU")]]'>"
<cpu-card part-card='[[item.partCard]]'>
</cpu-card>"
</template>
<!-- one additional template for every type of part -->
The Polymer Template is a bit more complicated now, but on the java side, the code is much shorter, and possibly easier to maintain. There is probably still some overhead, as every template gets added to the dom. But in addition to that only the content from the templates that we want to see gets added to the dom.
I don’t think there is a better way to do this though.
So using this method, we need a java-script method called _isEqualTo. This method is not a standard method so we need to implement it ourselves. The implementation for this method is straightforward.
function _isEqualTo(one, other) {
return one == other;
}
But the answer from Kuba does not specify where to implement this method. I have tried to put the method in different places with no luck. The js console in my browser always complains that it can not find the method.
Digging a little bit deeper I found this Link. So maybe what i want to have is a global variable.
window._isEqualTo = function(one, other) {
return one == other;
}
But even with this change the same warning persists. What’s weird is that the function is visible in the interactive console in the developer tools. Setting a breakpoint in the java-script file that i have added the function; and calling the function in the console reveals that it is really the correct function that get’s called, leading me to beleave that the function gets initialized too late in the lifecycle of the application. Although I am not sure at all.
And because the function is not found, the grid in the view will be empty. It still shows the rows, but they don’t show content.
I really hope someone can help me out.
Here is a Git-Repository to reproduce my problem. The concerning views are the PartsDomIfView and the PartsDomIfElegantView.
Instead of using the deprecated TemplateRenderer, you could create a LitRenderer (v22+) and create a custom lit component that can be used there as your column's content. In there you could create complex logic based templates as a separate component, that can be better maintained.

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.

google map wrong size in polymer element

I am using the google-map element and I read about the .resize() trick to fix the size of a google-map element.
However I could not get it to work. Moreless, it doesn't make sense to me.
In my application:
only the first page which displays maps renders properly. Another page which render a map will not render properly.
if the first page rendered doesn't contain a map, if I go to another page with a map, the map renders properly.
That is why I do not understand how this can be related to the core-animated pages.
Live demo:
http://nicolasrannou.github.io/webapp-core/components/webapp-core/demo.html#welcome/home
All the "Contact" pages contain maps.
The maps are created after pulling data from a google doc, using a template:
<template repeat="{{row, rowIndex in rows}}">
<!-- location -->
<template if="{{ row.gsx$type.$t === 'location'}}">
<google-map latitude="{{+ row.gsx$latitude.$t}}" longitude="{{+ row.gsx$longitude.$t}}" showCenterMarker zoom="15">
</google-map>
</template>
</template>
Those "google-map" element are pretty far in the shadow dom and encapsulated in templates.
I tried to access then in the core-animated-prepare event without success:
transitionPrepare : function(){
window.console.log(document.querySelectorAll('google-map'));
}
Is there a good way to access an element inside a template, inside a shadow dom?
Thanks
On accessing inside a shadow dom, here is one citation from the docs:
...If the element is in another shadow tree deep within another element, you can't traverse into it easily. You can use .shadowRoot if you really need to poke around:
element.shadowRoot.querySelector('x-other-element')
.shadowRoot.querySelector('#something');
On maps that do not resize properly, I'll first look at the timing: .resize() should be done AFTER the animation is completed.

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.

How to tell when Polymer is done with all the data-binding?

Let's say I have a Polymer element x-foo which uses templates for data-binding.
<template>
<!--shadow DOM-->
<template repeat='{{item in items}}'>
<div class='content'>{{item}}</div>
</template>
</template>
items is a property of x-foo which decides what is present in the view.
Now, on the fly in one of the methods of x-foo I do:
this.items = getNewItemList();
and then try to access shadow DOM content,
this.shadowRoot.querySelectorAll('.content') // was supposed to return 5 elements
I find that Polymer still hasn't iterated through the template loop and generated my shadow DOM content. Is there a way to know when it has finished it?
By design, Polymer waits until your JavaScript has finished processing before it does expensive things like messing with DOM. That way you can do several operations at once and not worry about thrashing DOM and slowing down your application.
The short answer to your question is to do something like this:
this.items = getNewItemList();
this.async(
// `async` lets the main loop resume and perform tasks, like DOM updates,
// then it calls your callback
function() {
this.contents = this.shadowRoot.querySelectorAll('.content');
}
);
A better answer is to avoid needing to query for the elements. Instead, let the elements communicate with the container via events or even using the 'item' objects (the data model). If you can drive your UI from your data-model, and not the reverse, you will have a better time.