Selecting an img element on polymer element load - polymer

I have created a polymer element which has an img tag in it. I am using a javascript library(Vibrant.js) which basically picks up the dominant color from the image and gives out the color code. I am trying to select the image element in attached event of polymer. But somehow the image element is not getting selected to properly.
I get the following exception.
Vibrant.min.js:1 Uncaught IndexSizeError: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The source width is 0.
<dom-module id="my-image-block">
<template>
<div class="service-wrapper">
<div><img id="imgobject" src={{imagepath}}></div>
<div class="service-wrapper-inner">
<div>
<p class="group-title">Stencil
<p> <!--TODO : Remove hard coding-->
<div class="title">
<p>Some content</p></div>
<p class="last-modified">Last updated: {{lastmodified}}</p>
</div>
<div class="description">
<p>{{description}}</p>
</div>
</div>
</div>
</template>
<script>
Polymer({
is: "my-image-block",
attached: function () {
var self = this;
var imageobject = self.$.imgobject;
var vibrant = new Vibrant(imageobject, 64, 6);
var swatches = vibrant.swatches();
var backColor = swatches["Vibrant"].getHex()
$(".service-wrapper-inner").css("background-color", backColor);
}
});
</script>

Your error can be resolved by giving width and height to the image tag.
<template>
<style>
#imgobject{
width: 100px;
height: 100px;
}
</style>
<!-- rest of your code -->
But even after that i was not able to make it run now i got a new error
Vibrant.js:651 Uncaught TypeError: Cannot read property 'map' of undefined
On further debugging i found that this error was coming because of an unhandled error in vibrant which was being generated due to the fact that cmap wa coming as false rather than an object as expected. This was happening because when the js disintegrated image and read rgba value of each pixel it got 0 as value, as a result it did not push anything in allpixels array.
I tried with images from vibrant example also but no success.

I was able to do that by calling a function on on-load event of img object.
<!-- rest of the code-->
<div><img id="image-object" src={{imagepath}} on-load="handleImageObjectOnLoad"></div>
<!-- rest of the code-->
And for the polymer code i did something like this.
<script>
Polymer({
is: "marketplace-image-block",
handleImageObjectOnLoad: function () {
var self = this;
var imageobject = self.$['image-object'];
var vibrant = new Vibrant(imageobject, 64, 5);
var swatches = vibrant.swatches();
var backColor = swatches["Vibrant"].getHex();
self.customStyle["--custom-background-color"] = backColor;
self.updateStyles();
}
});
</script>
Where --custom-background-color is a css styling variable.
<style>
:host{
--custom-background-color: grey;
}
.service-wrapper-inner {
background-color: var(--custom-background-color);
}
</style>

Related

How to reuse HTML code across multiple pages? [duplicate]

I have several pages on a website that use the same header for each page. I was wondering if there was some way to simply reference a file with the html for the header sort of like in this pseudo code:
<!-- Main Page -->
<body>
<html_import_element src = "myheadertemplate.html">
<body>
Then in a separate file:
<!-- my header template html -->
<div>
<h1>This is my header</h1>
<div id = "navbar">
<div class = "Tab">Home</div>
<div class = "Tab">Contact</div>
</div>
</div>
This way I could write the header html once and just import it in each of my pages where I need it by writing one simple tag. Is this possible? Can I do this with XML?
You could do it in this fashion below.
<head>
<link rel="import" href="myheadertemplate.html">
</head>
where you could have your myheadertemplate.html
<div>
<h1>This is my header</h1>
<div id = "navbar">
<div class = "Tab">Home</div>
<div class = "Tab">Contact</div>
</div>
</div>
You can then use it with JS below
var content = document.querySelector('link[rel="import"]').import;
So, after a long time I actually found a way to do this using AJAX. HTML Imports are a great solution, but the support across browsers is severely lacking as of 04/2017, so I came up with a better solution. Here's my source code:
function HTMLImporter() {}
HTMLImporter.import = function (url) {
var error, http_request, load, script;
script =
document.currentScript || document.scripts[document.scripts.length - 1];
load = function (event) {
var attribute, index, index1, new_script, old_script, scripts, wrapper;
wrapper = document.createElement("div");
wrapper.innerHTML = this.responseText;
scripts = wrapper.getElementsByTagName("SCRIPT");
for (index = scripts.length - 1; index > -1; --index) {
old_script = scripts[index];
new_script = document.createElement("script");
new_script.innerHTML = old_script.innerHTML;
for (index1 = old_script.attributes.length - 1; index1 > -1; --index1) {
attribute = old_script.attributes[index1];
new_script.setAttribute(attribute.name, attribute.value);
}
old_script.parentNode.replaceChild(new_script, old_script);
}
while (wrapper.firstChild) {
script.parentNode.insertBefore(
wrapper.removeChild(wrapper.firstChild),
script
);
}
script.parentNode.removeChild(script);
this.removeEventListener("error", error);
this.removeEventListener("load", load);
};
error = function (event) {
this.removeEventListener("error", error);
this.removeEventListener("load", load);
alert("there was an error!");
};
http_request = new XMLHttpRequest();
http_request.addEventListener("error", error);
http_request.addEventListener("load", load);
http_request.open("GET", url);
http_request.send();
};
Now when I want to import HTML into another document, all I have to do is add a script tag like this:
<script>HTMLImporter.import("my-template.html");</script>
My function will actually replace the script tag used to call the import with the contents of my-template.html and it will execute any scripts found in the template. No special format is required for the template, just write the HTML you want to appear in your code.
As far as I know it's not possible. You can load the header as a webpage in a iframe element though. In the past webpages were built with frame elements to load seperate parts of a webpage, this is not recommended and support in current browsers is due to legacy.
In most cases this is done with server side languages like php with as example include("header.php");.

How to make a paper-card scrollable

I'm using polymer's paper-card (I can't use paper-dialog because of this issue). How can I make it scrollable?
For example, in this code I would like to be able to scroll the card when it gets too large for the screen. Right now it just makes some of the content unreachable.
<paper-card>
<h2>[[someMessage]]</h2>
<div id="someReallyLongStuff"></div>
<paper-button raised on-click="doSomethingAndCloseCard">OK got it:)</paper-button>
</paper-card>
I've tried limiting the paper-card's size with max-height but that doesn't help.
EDIT
Here are photos of another example to clarify my problem:
In the first photo I have a small paper-card that fits the screen, but when it gets bigger it doesn't become scrollable, but goes out of page boundaries to make some of the content unreachable
window with small paper-card
window with longer paper-card that is too big for the page
I'm looking for something like paper-dialog-scrollable only for paper-card
!!!Update with code example at the bottom!!!
Paper-dialog
However, I used to work around that backdrop problem, related to the paper-dialog, with this answer that I found somewhere. I used this in a Polymer 1 project so I am not certain if it will still work in version 2. You definitely have to adapt the function.
<paper-dialog with-Backdrop on-iron-overlay-opened="patchOverlay"></paper-dialog>
// Fix with-backdrop for paper-doalog (Polymer1)
// https://github.com/PolymerElements/paper-dialog/issues/7
patchOverlay: function (e) {
if (e.target.withBackdrop) {
e.target.parentNode.insertBefore(e.target.backdropElement, e.target);
}
}
Paper-Card
If you are still looking to work with the paper-card Element and find yourself unable to change the width read further.
I have never tested that code but a presume that you will have to use the Polymer mixin for the paper-card. I don't think max-length is valid CSS better would be to use max-width or max-height.
paper-card {
--paper-card: {
max-width: 500px;
};
}
Update
I have added a code example using the basic paper-card provided by Polymer here
Long story short, I gave the text container a fixed height and added overflow auto to it. This way scroll bars will be added as soon as the text doesn't fit in it's container. This can be improved by adding the overflow only to the y-axes with "overflow-y: auto;"
// Load webcomponents.js polyfill if browser doesn't support native Web Components.
var webComponentsSupported = (
'registerElement' in document
&& 'import' in document.createElement('link')
&& 'content' in document.createElement('template')
);
if (webComponentsSupported) {
// For native Imports, manually fire WebComponentsReady so user code
// can use the same code path for native and polyfill'd imports.
if (!window.HTMLImports) {
document.dispatchEvent(
new CustomEvent('WebComponentsReady', {bubbles: true})
);
}
} else {
// Load webcomponents.js polyfill
var script = document.createElement('script');
script.async = true;
script.src = 'https://cdn.rawgit.com/StartPolymer/cdn/1.8.1/components/webcomponentsjs/webcomponents-lite.min.js';
document.head.appendChild(script);
}
paper-card {
--paper-card: {
width: 100px;
box-sizing: border-box;
};
}
.card-content {
width: 200px;
height: 100px;
overflow: auto;
}
<!-- <base href="https://gitcdn.xyz/cdn/StartPolymer/cdn/v1.11.0/components/"> -->
<!-- <base href="https://cdn.rawgit.com/StartPolymer/cdn/v1.11.0/components/"> -->
<base href="https://rawcdn.githack.com/StartPolymer/cdn/v1.11.0/components/">
<link rel="import" href="iron-flex-layout/iron-flex-layout.html">
<link rel="import" href="paper-card/paper-card.html">
<style is="custom-style">
</style>
<paper-card heading="Emmental" image="http://placehold.it/200x100/FFC107/000000" alt="Emmental">
<div class="card-content">
Emmentaler or Emmental is a yellow, medium-hard cheese that originated in the area around Emmental, Switzerland. It is one of the cheeses of Switzerland, and is sometimes known as Swiss cheese.
</div>
<div class="card-actions">
<paper-button>Share</paper-button>
<paper-button>Explore!</paper-button>
</div>
</paper-card>
<script>
window.addEventListener('WebComponentsReady', function() {
});
</script>

Use Reveal.js with Polymer

I have a project with Polymer + Reveal.js
I have a view with polymer that gets all the Slides/Sections.
<template is="dom-repeat" items="[[my.slides]]" as="slide">
<section>
<h1>slide.title</h1>
<h2>slide.content</h2>
</section>
</template>
When I try to start Reveal.js, I have the issue related to:
(index):21136 Uncaught TypeError: Cannot read property
'querySelectorAll' of undefined
I think is because Reveal.js cannot select a Webcomponent generated by Polymer, because Reveal.js needs to have all slides content wrote on the html file by separate.
Then my question is: How to use Polymer Webcomponents with Reveal,js?
Alan: Yes, you are right.
Now I am creating DOM elements directly with JS avoiding Polymer shadowDOM elements.
Then I created a function called createSlides where - based in a JSON response - I appending slides (sections) within slides div.
Fist I create a Polymer template similar to:
<template>
<div class="reveal">
<div id="slides" class="slides">
<section>
This section will be removed
</section>
</div>
</div>
</template>
Next I removed the unused slide and appended some slides. Finally start the Reveal presentation
ready()
{
this.removeInitialSlide();
this.addSomeSlides();
this.startRevealPresentation();
}
clearInitialSlides()
{
var slidesComp = this.$.slides;
while (slidesComp.hasChildNodes()) {
slidesComp.removeChild(slidesComp.lastChild);
}
}
addSomeSlides()
{
var slide1 = document.createElement("section");
var image = document.createElement("img");
image.src = "some/path/to/image.jpg";
slide1.appendChild(image);
this.$.slides.appendChild(slide1);
var slide2 = document.createElement("section");
slide2.innerHTML = "<h1>Test content</h1>"
this.$.slides.appendChild(slide2);
}
Working fine for now.
I think you most likely can't use reveal.js in a web component created with Polymer right now and here's why.
If you look at reveal.js's code it looks for dom elements with the reveal and slides classes on the main document like this:
dom.wrapper = document.querySelector( '.reveal' );
dom.slides = document.querySelector( '.reveal .slides' );
The problem with that is that Polymer elements have their own local dom which is a different dom tree which can't be accessed using methods like document.querySelector which means reveal.js can't access to them.

Encapsulation of web components and event binding to shadow DOM elements

I started to learn web components in details before to jump in using Polymer.
I was working on a simple example to create a spin button using two <button> and one <input type="text"> elements.
The template is:
<template id="tplSpinButton">
<style type="text/css">
.spin-button > * {
display: inline;
text-align: center;
margin: 0;
float: left;
}
</style>
<div class="spin-button">
<content select=".up-spin-button"></content>
<content select=".display-spin-button"></content>
<content select=".down-spin-button"></content>
</div>
</template>
And the host element is as:
<article>
<spin-button>
<button class="up-spin-button">+</button>
<input class="display-spin-button" type="text" value="0" size="2"/>
<button class="down-spin-button">-</button>
</spin-button>
</article>
And The JS code
var template = document.querySelector('#tplSpinButton');
var host = document.querySelector('article spin-button');
var articleShadowRoot = host.createShadowRoot();
articleShadowRoot.appendChild(document.importNode(template.content,true));
var counterBox = document.querySelector('.display-spin-button');
var upHandler = document.querySelector('.up-spin-button');
var downHandler = document.querySelector('.down-spin-button');
upHandler.addEventListener('click', function(e){
var count = parseInt(counterBox.value);
counterBox.value = count + 1;
}, false);
downHandler.addEventListener('click', function(e){
var count = parseInt(counterBox.value);
counterBox.value = count - 1;
}, false);
document.registerElement('spin-button', {
prototype: Object.create(HTMLElement.prototype)
});
During experiments I came to know that JS/ code does not work as <style> do in shadow DOM being part of <template>
In the above example I am adding insertion points (<content>) and then attaching event listener to distributed elements.
Is there any way to encapsulate the event listener implementation?
Is there any way to move the controls s and elements to shadow dom and then attaching event listener by any way?
Is there any way to encapsulate the event listener implementation?
You could make it part of your element's prototype and construct it inside of a lifecycle callback.
Is there any way to move the controls s and elements to shadow dom and then attaching event listener by any way?
Use getDistributedNodes to select the elements being projected into your content tags.
Here's a jsbin which illustrates both concepts. Hope that helps!

How to remove a shadow root from an HTML element adorned with a Shadow DOM from a template?

I'm exploring imports, templates, shadow DOM and custom elements in Chrome Canary (33.0.1712.3). In a grid layout I have a particular content element (region of the display) that will display different web components or cloned light DOM fragments imported from files.
However, I'm unable to redisplay ordinary HTML DOM once a shadow DOM has been added because I don't know how to remove the shadow root. Once created, the shadow root remains and interferes with the rendering of ordinary DOM. (I've looked at various W3C specs such as intro to web components, shadow DOM, templates, Bidelman's articles on HTML5 Rocks, etc.) I've isolated the problem in a simple example below:
Click "show plain old div"; click "show shadowed template"; click "show plain old div". Inspect in devtools after each click. After the third click, there is no output below the buttons and in devtools I am seeing:
<div id="content">
#document-fragment
<div id="plaindiv">Plain old div</div>
</div>
What do I need to add to removeShadow() to remove the shadow root and fully reset the content element to its initial state?
removing_shadows.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<template id="shadowedTemplateComponent">
<style>
div { background: lightgray; }
#t { color: red; }
</style>
<div id="t">template</div>
<script>console.log("Activated the shadowed template component.");</script>
</template>
<template id="plainDiv">
<div id="plaindiv">Plain old div</div>
</template>
</head>
<body>
<div>
<input type="button" value="show plain old div" onclick="showPlainOldDiv()"/>
<input type="button" value="show shadowed template" onclick="showShadowTemplate()"/>
<div id="content"></div>
</div>
<script>
function removeChildren(elt) {
console.log('removing children: %s', elt);
while (elt.firstChild) {
elt.removeChild(elt.firstChild);
}
}
function removeShadow(elt) {
if (elt.shadowRoot) {
console.log('removing shadow: %s', elt);
removeChildren(elt.shadowRoot); // Leaves the shadow root property.
// elt.shadowRoot = null; doesn't work
// delete elt.shadowRoot; doesn't work
// What goes here to delete the shadow root (#document-fragment in devtools)?
}
}
function showPlainOldDiv() {
console.log('adding a plain old div');
var host = document.querySelector('#content');
removeChildren(host);
removeShadow(host);
var template = document.querySelector('#plainDiv');
host.appendChild(template.content.cloneNode(true));
}
function showShadowTemplate() {
console.log('adding shadowed template component');
var host = document.querySelector('#content');
removeChildren(host);
removeShadow(host);
var template = document.querySelector('#shadowedTemplateComponent');
var root = host.shadowRoot || host.webkitCreateShadowRoot();
root.appendChild(template.content.cloneNode(true));
}
</script>
</body>
</html>
The spec of Shadow DOM moved from v0 to v1.
One of the changes is that in v1 there is no way to create shadow root on itself and the host element may contain only one shadow root.
So it seems like the answer of replacing the shadow root with a new blank shadow root is not valid anymore.
Solution paths:
if the host element self (div in your example) has no special value beside holding that Shadow DOM, one can just replace the host element as a whole
if one still likes to preserve the host, clearing the Shadow DOM with something like e.shadowRoot.innerHTML = '' might be sufficient
You can't remove a shadow root once you add it. However, you can replace it with a newer one.
As mentioned here, http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-301/, the newest shadow root "wins" and becomes the rendered root.
You can replace your shadow root with a new shadow root that only contains the <content> pseudo-element to insert everything from the light DOM back into the shadow DOM. At that point, as far as I know it will be functionally equivalent to having no shadow DOM at all.
rmcclellan is correct that you cannot truely "remove" a ShadowRoot v2. But, you can fake it.
The OuterHTML PARTIAL Solution
elementWithShadowDOMv2.outerHTML = elementWithShadowDOMv2.outerHTML;
HOWEVER, there is a major caveat: although there is no visual change, elementWithShadowDOMv2 still refers to the destroyed element with the ShadowDOMv2 as if elementWithShadowDOMv2.parentNode.removeChild( elementWithShadowDOMv2 ) were called. This also "removes" event listeners on the element. Observe the demo below.
var addShadowHere = document.getElementById("add-shadow-here");
addShadowHere.addEventListener("mouseenter", function() {
addShadowHere.style.border = '2em solid blue';
});
addShadowHere.addEventListener("mouseleave", function() {
addShadowHere.style.border = '';
});
var shadow = addShadowHere.attachShadow({mode:"open"});
var button = shadow.appendChild(document.createElement("button"));
button.textContent = "Click Here to Destroy The ShadowDOMv2";
button.addEventListener("click", function() {
addShadowHere.outerHTML = addShadowHere.outerHTML;
update();
});
update();
function update() {
// This just displays the current parent of the addShadowHere element
document.getElementById("parent-value").value = "" + (
addShadowHere.parentNode &&
addShadowHere.parentNode.cloneNode(false).outerHTML
);
}
<div id="add-shadow-here">Text Hidden By Shadow DOM</div>
addShadowHere.parentNode => <input readonly="" id="parent-value" />
Notice how the blue border stops working after you remove the ShadowDOM. That is because the event listeners are no longer registered on the new element: the event listeners remain registered on the old element that has now been removed from the DOM.
Thus, you must refresh any references to the element and reattach any event listeners. Here is an example of how you could reobtain a reference to the new element.
function removeShadowWithCaveat(elementWithShadow) {
if (!elementWithShadow.parentNode) return elementWithShadow.cloneNode(true);
var parent = elementWithShadow.parentNode;
var prior = elementWithShadow.previousSibling;
elementWithShadow.outerHTML = elementWithShadow.outerHTML;
return prior.nextSibling || parent.firstChild;
}
If you need access to the elements which are naturally hidden by the existing shadow root and which will become exposed after the expulsion of the shadow root, then here is an alternative method that will perfectly preserve these nodes.
function removeShadowWithCaveat(elementWithShadow) {
if (!elementWithShadow.parentNode) return elementWithShadow.cloneNode(true);
var ref = elementWithShadow.cloneNode(true);
while (elementWithShadow.lastChild) ref.appendChild( elementWithShadow.lastChild );
elementWithShadow.parentNode.replaceChild(elementWithShadow, elementWithShadow);
return ref;
}
Working Solution
var createShadowProp = (
"createShadowRoot" in Element.prototype ? "createShadowRoot" : "webkitCreateShadowRoot"
);
function removeChildren(elt) {
console.log('removing children: %s', elt);
while (elt.firstChild) {
elt.removeChild(elt.firstChild);
}
}
function removeShadowWithCaveat(elementWithShadow) {
if (!elementWithShadow.parentNode) return elementWithShadow.cloneNode(true);
var ref = elementWithShadow.cloneNode(true);
while (elementWithShadow.lastChild) ref.appendChild( elementWithShadow.lastChild );
elementWithShadow.parentNode.replaceChild(elementWithShadow, elementWithShadow);
return ref;
}
function showPlainOldDiv() {
console.log('adding a plain old div');
var host = document.querySelector('#content');
removeChildren(host);
// Remove the shadow
host = removeShadowWithCaveat(host);
var template = document.querySelector('#plainDiv');
host.appendChild(template.content.cloneNode(true));
}
function showShadowTemplate() {
console.log('adding shadowed template component');
var host = document.querySelector('#content');
removeChildren(host);
// Remove the shadow
host = removeShadowWithCaveat(host);
var template = document.querySelector('#shadowedTemplateComponent');
var root = host.shadowRoot || host[createShadowProp]({
"open": true
});
root.appendChild(template.content.cloneNode(true));
}
<div>
<input type="button" value="show plain old div" onclick="showPlainOldDiv()"/>
<input type="button" value="show shadowed template" onclick="showShadowTemplate()"/>
<div id="content"></div>
</div>
<template id="shadowedTemplateComponent" style="display:none">
<style>
div { background: lightgray; }
#t { color: red; }
</style>
<div id="t">template</div>
<script>console.log("Activated the shadowed template component.");</script>
</template>
<template id="plainDiv" style="display:none">
<div id="plaindiv">Plain old div</div>
</template>
Also note the misuse of vendor prefixes (a problem that far too many developers have issues with). You are correct that, at the time that this question was asked, there was only the prefixed version of createShadowRoot (which was webkitCreateShadowRoot). Nevertheless, you must ALWAYS check to see if the unprefixed createShadowRoot version is available in case if browsers standardize the API in the future (which is now the case). It might be nice to have your code working today, but it's awesome to have your code working several years from now.
In Chrome:
Press F12, DevTool will open
Click gear icon in DevTool
Uncheck "show user agent shadow DOM" checkbox
Enjoy !