Hey I'm beginning with Webcomponents and I built a little HTML Import Example, where I import a calander from another Website with VanillaJs and it works perfekt.
As the 2. Step I wanted to HTML import into a Polymer element, so that I can use the element over and over again. Here is my code:
<link rel="import" href="http://www.testsite.com">
<link rel="import" href="bower_components/polymer/polymer.html">
<polymer-element name="event-calendar-element">
<template>
<div id="container" style = "width:220px;"></div>
</template>
<script>
var owner = document._currentScript.ownerDocument;
var link = owner.querySelector('link[rel="import"]');
var content = link.import;
var container = document.getElementById('container');
var el = content.querySelector('.slider-teaser-column');
container.appendChild(el.cloneNode(true));
</script>
</polymer-element>
In the other HTML document I use the custom element and I can see, that the import worked(the resources from testsite.com are loaded), but my Polymer element has no shadowDOM - the imported and selected element is not appended to my <event-calendar-element> :/
the container <div> is null and therefore the following error occurs: Cannot read property 'appendChild' of null
Any help appreciated ;)
The <div> is inside the <template> and since you're not using Polymer() you'd need to use template.content.querySelector() get at and modified it's content. Instead, you can do this in Polymer:
<link rel="import" href="http://www.testsite.com" id="fromsite">
<link rel="import" href="bower_components/polymer/polymer.html">
<polymer-element name="event-calendar-element">
<template>
<div id="container" style="width:220px;"></div>
</template>
<script>
(function()
var owner = document._currentScript.ownerDocument;
Polymer({
ready: function() {
var content = owner.querySelector('link#fromsite').import;
var el = content.querySelector('.slider-teaser-column');
this.$.container.appendChild(el.cloneNode(true));
}
});
})();
</script>
</polymer-element>
Related
I don't know how to make a disqus comments code to work inside of my custom elements.
Structure of my site:
| index.html
--------\ my-app.html (custom element)
----------------\ my-testView1.html (custom element)
----------------\ my-testView2.html (custom element)
I need to put disqus comments inside my-testView1.html and my-testView2.html
Structure of index.html:
<body>
<my-app>
<div class="disqusClass1" id="disqus_thread"></div>
<div class="disqusClass2" id="disqus_thread"></div>
<my-app>
</body>
Structure of my-app.html:
<iron-pages>
<my-testView1 name="testView"><content select=".disqusClass1"></content></my-testView1>
<my-testView2 name="testView2"><content select=".disqusClass2"></content></div></my-testView2>
</iron-pages>
Structure of my-testView1.html :
<template>
<content select=".disqusClass1"></content>
</template>
Structure of my-testView2.html :
<template>
<content select=".disqusClass2"></content>
</template>
The disqus div
I put the div of the disqus comments inside <my-app> on the index.html so that chrome could find it. It can't find it if I put it inside <my-testView1> like that:
page my-app.html
<iron-pages>
<my-testView1 name="testView"><div id="disqus_thread"></div></my-testView1>
<my-testView2 name="testView2"><div id="disqus_thread"></div></my-testView2>
</iron-pages>
because the my-app.html is a custom element itself and it won't let chrome to find it. So I had to put it outside of the shadow dom (the index.html page)
Javacript code on the pages my-testView1.html and my-testView2.htmllook like this:
<dom-module id="my-testView1">
<template>
...
<content></content>
...
</template>
<script>
Polymer({
is: 'my-testView1',
ready: function ()
{
// DEFAULT DISQUS CODE (I changed real URLs though):
var disqus_config = function () {
this.page.url = 'https://www.example.com/testView1'; // Replace PAGE_URL with your page's canonical URL variable
this.page.identifier = '/testView1';
// this.page.title = 'Test View';
};
(function() {
var d = document, s = d.createElement('script');
s.src = '//myChannelName.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
}
});
</script>
</dom-module>
Result
Comments appears only on one of these my-testView1.html my-testView2.html at the time. I need 1 disqus thread on my-testView1.html and another disqus thread on my-testView2.html
Maybe it's because of routing. Disqus console message says that I need to use ajax method https://help.disqus.com/customer/portal/articles/472107-using-disqus-on-ajax-sites Unfortunately I could not make it work when I replaced the javascript code above with the code from the example:
<script>
Polymer({
is: 'my-testView1',
ready: function ()
{
var disqus_shortname = 'myDisqusChannelId';
var disqus_identifier = '/testView1';
var disqus_url = 'http://example.com/testView1';
var disqus_config = function () {
this.language = "en";
};
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
var reset = function (newIdentifier, newUrl) {
DISQUS.reset({
reload: true,
config: function () {
this.page.identifier = newIdentifier;
this.page.url = newUrl;
}
});
};
}
});
</script>
</dom-module>
inside both custom elements (changing disqus_identifier and disqus_url correspondingly for each of them)
The error is due to the fact that the disqus library can't find the <div id="disqus_thread"> element.
It's because this element is inside a Shadow DOM (and that's why it works fine in Firefox which doesn't implement real Shadow DOM).
3 possible solutions:
Don't use Shadow DOM with your Polymer elements.
Don't put the #disqus_thread element in a Polymer element (insert it in the normal DOM).
Use <content> in your <template>, and the #disqus_thread element inside the polymer tag to make it availabe to the library:
In the custom elements:
<template>
//HTML code here
<content></content>
</template>
In the HTML page where you insert the custom element:
<my-app>
<my-testView>
<div id="disqus_thread"></div>
</my-testView>
</my-app>
The <div> will be revealed at the place where the (nested) <content> tags are placed.
I wanted to give another possible solution to using Disqus comments in Polymer. The main problem is the inability to use Disqus with elements in the shadow dom because they are hidden. So how can we make a shadow dom element visible? We can pass it down the component hierarchy from the index.html of the application.
To expose an element structure your html like this:
index.html
<polymer-app>
<div id="disqus_thread">
</polymer-app>
In Polymer App:
<dom-module id="polymer-app">
<template>
<slot></slot>
<template>
</dom-module>
Slot is where the #disqus_thread will show. And you can pass it further down to other components, inside polymer-app.
<dom-module id="polymer-app">
<template>
<my-page>
<slot></slot>
</my-page>
<template>
</dom-module>
Note: This is code is just an example. Don't try to copy and paste, it won't run.
Using the polymer-cli tool, and the shopping cart boilerplate as a starting point, I made a simple mock-up to illustrate the use case.
Assume your index.html file includes "test-app.html" and the matching tag
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>My App</title>
<link rel="shortcut icon" sizes="32x32" href="/images/app-icon-32.png">
<meta name="theme-color" content="#fff">
<link rel="manifest" href="/manifest.json">
<script>
// setup Polymer options
window.Polymer = {lazyRegister: true, dom: 'shadow'};
// load webcomponents polyfills
(function() {
if ('registerElement' in document
&& 'import' in document.createElement('link')
&& 'content' in document.createElement('template')) {
// browser has web components
} else {
// polyfill web components
var e = document.createElement('script');
e.src = '/bower_components/webcomponentsjs/webcomponents-lite.min.js';
document.head.appendChild(e);
}
})();
// load pre-caching service worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js');
});
}
</script>
<!-- <link rel="import" href="/src/bewi-app.html"> -->
<link rel="import" href="/src/test-app.html">
<style>
body {
margin: 0;
font-family: 'Roboto', 'Noto', sans-serif;
line-height: 1.5;
min-height: 100vh;
background-color: #eee;
}
</style>
</head>
<body>
<span id="browser-sync-binding"></span>
<test-app id="test"></test-app>
</body>
</html>
Now, assume test-app.html containing the following (again a mere simplified copy of my-app.html):
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/app-route/app-location.html">
<link rel="import" href="../bower_components/app-route/app-route.html">
<link rel="import" href="test-element.html">
<dom-module id="test-app">
<template>
<app-location route="{{route}}"></app-location>
<app-route
route="{{route}}"
pattern="/:page"
data="{{routeData}}"
tail="{{subroute}}"></app-route>
test-element is loaded bellow
<test-element></test-element>
<div>
</div>
</template>
<script>
Polymer({
is: 'test-app',
properties: {
page: {
type: String,
reflectToAttribute: true,
observer: '_pageChanged'
},
baseUrl: {
type: String,
value: '/'
},
siteUrl: {
type: String,
value: 'http://fqdn.local'
}
},
observers: [
'_routePageChanged(routeData.page)'
],
_routePageChanged: function(page) {
this.page = page || 'view1';
},
_pageChanged: function(page) {
// load page import on demand.
this.importHref(
this.resolveUrl('my-' + page + '.html'), null, null, true);
}
});
</script>
</dom-module>
Now, the test-element.html
<link rel="import" href="../../bower_components/polymer/polymer.html">
<dom-module id="test-element">
<template>
<div> I am a test </div>
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'test-element',
ready: function() {
console.log('READY');
console.log('find #test using document.querySelector', document.querySelector('#test')); // OK
console.log('find #test .siteUrl using document.querySelector', document.querySelector('#test').siteUrl); // undefined
console.log('find #test .siteUrl using Polymer.dom', Polymer.dom(document.querySelector('#test')).siteUrl); // undefined
console.log('find #test .siteUrl using Polymer.dom().node', Polymer.dom(document.querySelector('#test')).node.siteUrl); // undefined
console.log('find #test .siteUrl using Polymer.dom().properties', Polymer.dom(document.querySelector('#test')).node.properties); // {object} but, I'm guessing not the computed values of the properties
// So, how may I access the #test app's "siteUrl" property from within a custom element?
}
});
})();
</script>
</dom-module>
So, the real question is, how may test-element access the property "siteUrl" in the main app?
I'm planning to make this variable readOnly, and access it from other custom elements.
I'ld prefer this approach VS passing the siteUrl as an attribute to the test-element element..
What do you think?
The right way to pass information through elements is using the Data Binding system, i.e. "passing the siteUrl as an attribute to the test-elemet element"
You'll accomplish the Read Only requirement surrounding the variable with square brackets, like this [[siteUrl]] as described in Property change notification and two-way binding.
You can set a variable in a global environment as you said like
<script>
var myGlobalVar = 'is accessible in any element'
Polymer({
is: 'test-app',
// ...
});
</script>
and you can access it in every element.
BUT, global variables are not recommended as you may know. References about why in the links below.
Global Variables Are Bad
Why are global variables considered bad practice?
I've Heard Global Variables Are Bad, What Alternative Solution Should I Use?
Is there a callback available in the Polymer({}) object which fires everytime the element is shown ?
ready is not suitable because it's called when the element is created on initial page load.
I need an event or callback every time the route changes and my element is displayed.
Why do I need this ? I have an element which is behaving differently if a certain request parameter is set. So I need to check on each load whether the parameter is set or not.
Edit:
I worked around my requirement by doing the stuff I need to be done on element display in my routing functions:
page("/app/list", function() {
document.querySelector("my-list").$.loadList.generateRequest();
app.route = "list";
});
In the meantime I came also accross app-route which as well has functionality to call methods on route or view changes.
You can read about it here:
https://www.polymer-project.org/1.0/toolbox/routing#take-action-on-route-changes
Here is a working example:
<!DOCTYPE html>
<html>
<head>
<script src="bower_components/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="bower_components/polymer/polymer.html" />
<link rel="import" href="bower_components/app-route/app-location.html" />
<link rel="import" href="bower_components/app-route/app-route.html" />
<link rel="import" href="bower_components/iron-pages/iron-pages.html" />
</head>
<body>
<container-element></container-element>
</body>
</html>
<dom-module id="container-element">
<template>
<app-location route="{{route}}" use-hash-as-path></app-location>
<app-route route="{{route}}" pattern=":view" data="{{routeData}}"></app-route>
Page 1 | Page 2
<iron-pages selected="[[routeData.view]]" attr-for-selected="name">
<div name="page1">This is Page 1.</div>
<x-element1 name="element1"></x-element1>
</iron-pages>
</template>
<script type="text/javascript">
Polymer({
is : "container-element",
observers : [ "_viewChanged(routeData.view)" ],
_viewChanged : function(view) {
if (view) {
if (view === "element1") {
document.querySelector("x-element1").test();
}
}
}
});
</script>
</dom-module>
<dom-module id="x-element1">
<template><p>This is Element 1.</p></template>
<script type="text/javascript">
Polymer({
is : "x-element1",
test : function() {
console.log("Callback of Element 1 called.");
}
});
</script>
</dom-module>
Maybe the attached callback is what you're looking for.
This lifecycle callback is called when an element is attached to the DOM and should therefore be the right choice. It is always called after the ready callback.
From the polymer docs:
https://www.polymer-project.org/1.0/docs/devguide/registering-elements.html#initialization-order
attached: function() {
this.async(function() {
// access sibling or parent elements here
});
}
index.html
<!DOCTYPE html>
<html>
<head>
<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script>
<script>
traceur.options.experimental = true;
</script>
<link rel="import" href="x-item.html">
</head>
<body>
<x-item></x-item>
</body>
</html>
and my web component:
x-item.html
<template id="itemtemplate">
<span>test</span>
</template>
<script type="module">
class Item extends HTMLElement {
constructor() {
let owner = document.currentScript.ownerDocument;
let template = owner.querySelector("#itemtemplate");
let clone = template.content.cloneNode(true);
let root = this.createShadowRoot();
root.appendChild(clone);
}
}
Item.prototype.createdCallback = Item.prototype.constructor;
Item = document.registerElement('x-item', Item);
</script>
and I get no error nor what I expect to be displayed, any idea if this should actually work?
Is this how one would extend an HTMLElement in ECMA6 syntax?
E: putting it altogether in one page solves the problem at least now I know its the right way to create a custom component, but the problem is having it in a separate file I think it has to do with how traceur handles <link rel="import" href="x-item.html"> I tried adding the type attribute to the import with no luck.
Traceur's inline processor does not appear to have support for finding <script> tags inside <link import>. All of traceur's code seems to access document directly, which results in traceur only looking at index.html and never seeing any <scripts> inside x-item.html. Here's a work around that works on Chrome. Change x-item.html to be:
<template id="itemtemplate">
<span>test</span>
</template>
<script type="module">
(function() {
let owner = document.currentScript.ownerDocument;
class Item extends HTMLElement {
constructor() {
// At the point where the constructor is executed, the code
// is not inside a <script> tag, which results in currentScript
// being undefined. Define owner above at compile time.
//let owner = document.currentScript.ownerDocument;
let template = owner.querySelector("#itemtemplate");
let clone = template.content.cloneNode(true);
let root = this.createShadowRoot();
root.appendChild(clone);
}
}
Item.prototype.createdCallback = Item.prototype.constructor;
Item = document.registerElement('x-item', Item);
})();
</script>
<script>
// Boilerplate to get traceur to compile the ECMA6 scripts in this include.
// May be a better way to do this. Code based on:
// new traceur.WebPageTranscoder().selectAndProcessScripts
// We can't use that method as it accesses 'document' which gives the parent
// document, not this include document.
(function processInclude() {
var doc = document.currentScript.ownerDocument,
transcoder = new traceur.WebPageTranscoder(doc.URL),
selector = 'script[type="module"],script[type="text/traceur"]',
scripts = doc.querySelectorAll(selector);
if (scripts.length) {
transcoder.addFilesFromScriptElements(scripts, function() {
console.log("done processing");
});
}
})();
</script>
Another possible solution would be to pre-compile the ECMA6 into ECMA5 and include the ECMA5 only. This would avoid the problem of traceur not finding the <script> tags in the import and would remove the need for the boilerplate.
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="my-thing">
<script>
Polymer('my-thing', {
athing: function () { return 'hello' }
});
</script>
</polymer-element>
I want the use the element defined above in the element below and have access to the athing property.
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="./mything.html">
<polymer-element name="my-hello"">
<template>
<my-thing id="mything"></my-thing>
</template>
<script>
Polymer('my-hello', {
ready: function () {
this.$.mything.athing() // returns undefined
}
});
</script>
</polymer-element>
Nodes with id's are reflected in the $ hash, not in this, so
this.$.mything.athing(); // returns 'hello'
[Poster changed his example to include the missing .$]
After fixing the syntax, I cannot recreate your problem.
http://jsbin.com/tegefura/1/edit