Polymer: wrap parent element around child - polymer

with Polymer it's easy to extend another web component. You can use <shadow> to mark the place in the child's template where the shadow dom of the Parent should be (polymer docs).
I'm looking to extend an element, but in a way that a specific part of child gets wrapped by the parent. This kind of setup I've used with some template engines. Can this be done with html includes?
Parent ::
<link rel="import" href="/bower_components/polymer/polymer.html">
<polymer-element name="tpl-parent" noscript>
<template>
<section>
<content></content><!-- put the child template here -->
</section>
</template>
</polymer-element>
Child ::
<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="parent.html">
<polymer-element name="tpl-child" extends="tpl-parent" noscript>
<template>
<shadow>
<p>whatever I put here should be "wrapped" by the _section_ in parent</p>
</shadow>
</template>
</polymer-element>

You can't do it declaratively like that. But I think you can leverage automatic node finding to move a child into the parent's section if it has an id.
<polymer-element name="x-foo">
<template>
<style>
#wrapper p { color: red; }
</style>
<section id="wrapper">
<p>Hello from x-foo</p>
</section>
</template>
<script>
Polymer({
});
</script>
</polymer-element>
<polymer-element name="x-bar" extends="x-foo">
<template>
<p id="foo">I was wrapped by x-foo</p>
<p>I was not wrapped</p>
<shadow></shadow>
</template>
<script>
Polymer({
domReady: function() {
this.$.wrapper.appendChild(this.$.foo);
}
});
</script>
</polymer-element>
<x-bar></x-bar>

Even though #robdodson's answer is completely correct, it kind of feels.. too "loose".
When discussing this further at work, somebody noted the principle of "Composition over inheritance". This led to me viewing this situation from a different (and I think better) perspective.
I ended up with this (also includes an example of passing arguments):
-- x-foo.html
<polymer-element name="x-foo" arguments="status">
<template>
<style>
#wrapper.valid ::content p { color: green; }
#wrapper.invalid ::content p { color: red; }
</style>
<section id="wrapper">
<p>x-bar will be put in the content tag below</p>
<content></content>
</section>
</template>
<script>
Polymer({
status: '',
statusChanged: function(oldVal, newVal) {
if (['valid', 'invalid'].indexOf(newVAl) === -1) {
this.$.wrapper.setAttribute('class', newVal);
}
}
});
</script>
</polymer-element>
-- x-bar.html
<link rel="import" href="x-foo.html" arguments="status">
<polymer-element name="x-bar">
<template>
<x-foo status="{{status}}">
<p>color me</p>
</x-foo>
</template>
<script>
Polymer();
</script>
</polymer-element>
-- index.html
<link rel="import" href="x-bar.html" arguments="status">
<x-bar></x-bar>
The only "disadvantage" is you have to pass on "status". But this feels better then having to append to a "floating" div#wrapper in the super class

Related

Propagating CSS variables in composited elements

I've got an element (the host) that includes another element (the child).
How can I propagate the value of a CSS variable from the host to the child when the CSS variable is set on the host?
Example:
<!-- Host Element, includes <child-element> -->
<dom-module id="host-element">
<template>
<style>
child-element {
--button-color: var(--menu-button-color);
}
</style>
<child-element><child-element>
</template>
</dom-module>
<!-- Child Element, is declared within <host-element> -->
<dom-module id="child-element">
<template>
<style>
button {
color: var(--button-color);
}
</style>
<button> I should be a red button </button>
</template>
</dom-module>
And ideally I'd be able to use it like so:
<style>
host-element {
--menu-button-color: red;
}
</style>
<host-element><host-element>
Seems to be working(chrome), run the code snippet below
<!doctype html>
<head>
<base href="https://polygit.org/polymer+1.7.1/components/">
<script src="webcomponentsjs/webcomponents-lite.js"></script>
<script>
// Setup Polymer options
window.Polymer = {
dom: 'shadow'
};
</script>
<link rel="import" href="polymer/polymer.html">
</head>
<style is="custom-style">
host-elem {
--menu-button-color: red;
}
</style>
<body>
<host-elem></host-elem>
<!-- Host Element, includes <child-element> -->
<dom-module id="host-elem">
<template>
<style>
child-elem {
--button-color: var(--menu-button-color);
}
</style>
<child-elem><child-elem>
</template>
<script>
Polymer({ is: 'host-elem'});
</script>
</dom-module>
<!-- Child Element, is declared within <host-element> -->
<dom-module id="child-elem">
<template>
<style>
button {
color: var(--button-color);
}
</style>
<button>I should be a red button </button>
</template>
<script>
Polymer({ is: 'child-elem'});
</script>
</dom-module>
</body>

Getting Child Element From Polymer.dom(event)

What is the best way to grab a hold of the element paper-ripple to attach a event listener for the animation end event? This element will be in a dom repeat. I tried getting children from Polymer.dom(event).localTarget; but had no success. The on-tap needs to stay in the parent.
<div on-tap="goToSingleListing" data-listingID="[[url.listing_id]]">
<paper-ripple></paper-ripple>
...
goToSingleListing: function(event) {
var el = Polymer.dom(event).localTarget;
var firstChildElementName = Polymer.dom(el).firstChild;
...
firstChildElementName.addEventListener('transitionend', ()=> {
page(url);
});
Instead of trying to get a reference to the <paper-ripple> from your tap event handler, you could use an annotated event listener directly on the <paper-ripple> for its transitionend event.
// dom-module template
<template is="dom-repeat" items="[[items]]">
<div on-tap="...">
<paper-ripple on-transitionend="_onItemTransitionEnd"></paper-ripple>
</div>
</template>
// dom-module script
Polymer({
...
_onItemTransitionEnd: function(e) {
...
}
}
HTMLImports.whenReady(() => {
Polymer({
is: 'x-foo',
_onItemTap: function(e) {
console.log('tap', e.model.index);
},
_onItemTransitionEnd: function(e) {
console.log('transitionend', e.model.index);
}
});
});
<head>
<base href="https://polygit.org/polymer+1.7.1/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="polymer/polymer.html">
<link rel="import" href="paper-ripple/paper-ripple.html">
</head>
<body>
<x-foo></x-foo>
<dom-module id="x-foo">
<template>
<template is="dom-repeat" items="[1,2,3,4]">
<div on-tap="_onItemTap">
<h2>Item [[item]]</h2>
<paper-ripple on-transitionend="_onItemTransitionEnd"></paper-ripple>
</div>
</template>
</template>
</dom-module>
</body>
codepen

Polymer: Using selectors

I am using Polymer to create a web component. I want to select the div gutter when the ::host element gets the class full
<link rel="import" href="/polymer/polymer.html">
<dom-module id="smooth-header">
<style>
.full ::content gutter {
color: red;
}
</style>
<template>
<content>
<div class="gutter"></div>
</content>
</template>
<script>....</script>
</dom-module>
But I am not able to select the gutter element with the above selector.
Why should the above selector fail?
Help with an alternative to select it correctly.
You should use :host(selector) to select a class on the host element:
:host(.fill) .gutter {
color: red;
}
<head>
<base href="https://polygit.org/polymer+1.5.0/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="polymer/polymer.html">
</head>
<body>
<x-foo></x-foo>
<x-foo class="full"></x-foo>
<dom-module id="x-foo">
<style>
:host(.full) .gutter {
color: red;
}
</style>
<template>
<content>
<div class="gutter">
<span>{{foo}}</span>
</div>
</content>
</template>
<script>
HTMLImports.whenReady(function() {
Polymer({
is: 'x-foo',
properties : {
foo: {
type: String,
value: "Hello world!"
}
}
});
});
</script>
</dom-module>
</body>
codepen

Polymer: Wrap <paper-item> in own element

I want to create my own <menu-item> element to remove some boilerplate code. I wrapped <paper-icon-item> in my own element in the following way:
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/paper-item/paper-icon-item.html">
<dom-module id="menu-item">
<template>
<paper-icon-item>
<iron-icon icon="[[icon]]" item-icon></iron-icon>
<content></content>
</paper-icon-item>
</template>
</dom-module>
<script>
Polymer({
is: "menu-item",
properties: {
icon: { type: String }
}
});
</script>
The problem when using my <menu-item> is, however, that it behaves differently as to writing the template code directly in the HTML file. I suspect that the item is missing some interaction capabilities with the menu to function properly, but I can't figure it out. I tried using behaviors Polymer.IronControlState and Polymer.IronButtonState just like the paper item does, but no luck.
What am I missing to make my <menu-item> behave like a regular <paper-item>?
Answer
Following code is largely copied from the paper-icon-item.html.
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/iron-behaviors/iron-control-state.html">
<link rel="import" href="bower_components/iron-behaviors/iron-button-state.html">
<link rel="import" href="bower_components/iron-flex-layout/iron-flex-layout.html">
<link rel="import" href="bower_components/paper-styles/typography.html">
<link rel="import" href="bower_components/paper-styles/color.html">
<link rel="import" href="bower_components/paper-styles/default-theme.html">
<link rel="import" href="bower_components/paper-item/paper-item-behavior.html">
<link rel="import" href="bower_components/paper-item/paper-item-shared-styles.html">
<link rel="import" href="bower_components/iron-icon/iron-icon.html">
<link rel="import" href="bower_components/iron-icons/iron-icons.html">
<dom-module id="menu-item">
<template>
<style include="paper-item-shared-styles"></style>
<style>
:host {
#apply(--layout-horizontal);
#apply(--layout-center);
#apply(--paper-font-subhead);
#apply(--paper-item);
#apply(--paper-icon-item);
}
.content-icon {
width: var(--paper-item-icon-width, 56px);
#apply(--layout-horizontal);
#apply(--layout-center);
}
</style>
<div id="contentIcon" class="content-icon">
<iron-icon icon="{{ico}}" item-icon></iron-icon>
</div>
<content></content>
</template>
<script>
Polymer({
is: 'menu-item',
properties: {
ico: {
type: String,
value: "icons:stars"
}
},
behaviors: [
Polymer.PaperItemBehavior
]
});
</script>
</dom-module>
On Extending Elements
I think the simplest way of changing the template of an element is by just copying it, modifying the template and its name. This approach is not without problems, it becomes very difficult to keep the new element up to date with changes in the original one. I am really craving for a better solution!.
On Using Behaviors
You should try to avoid consuming both an element and a behavior used by that element at the same time, you might get into trouble. For example the reason your menu-item doesn't look proper is because the paper-item inside it doesn't acquire the attribute focused. The paper-item doesn't acquire that particular attribute is because its parent element menu-item has the focus, menu-item has the focus because both menu-item and paper-item have tabindex="0" set on them. Why do both these elements have it set on them?, it is because of Polymer.IronControlStateBehavior in both of them.

Setting the height of core-pages elements?

I'm having a lot of trouble getting a core-pages element to have a non-zero height within my custom element. What is the best practice for having the core-pages height be the same as its selected content. Here's a trivial example which clearly breaks:
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Polymer</title>
</head>
<body>
<script src="http://www.polymer-project.org/platform.js"></script>
<link rel="import" href="http://www.polymer-project.org/components/polymer/polymer.html">
<link rel="import" href="http://www.polymer-project.org/components/core-pages/core-pages.html">
<polymer-element name="x-foo">
<template>
<core-pages id="pages" selected="{{selected}}">
<content></content>
</core-pages>
</template>
<script>
Polymer('x-foo', {
ready: function() {
this.selected = 0;
}
});
</script>
</polymer-element>
<polymer-element name="x-bar">
<template>
<div><content></content></div>
</template>
<script>
Polymer('x-bar', {});
</script>
</polymer-element>
<p>BEFORE</p>
<x-foo>
<x-bar>some text here</x-bar>
<x-bar>some other text here</x-bar>
</x-foo>
<p>AFTER</p>
</body>
</html>
And the jsbin to see the results: http://jsbin.com/xowoxakuwu/1/edit (notice how the core pages content overlaps with the next element)
This example shows a core-pages element within a custom element. The content that gets injected into the core-pages are also custom elements.
Whats the best practice here?
You can apply a style to the currently selected page in the x-foo element which sets display: block and position: relative so x-bar will inherit the height of it's content.
I've also added the "block" attribute to the x-foo element so it too inherits the height of the selected page. Other general attributes are here -> https://www.polymer-project.org/docs/polymer/layout-attrs.html#general-purpose-attributes
<script src="http://www.polymer-project.org/platform.js"></script>
<link rel="import" href="http://www.polymer-project.org/components/polymer/polymer.html">
<link rel="import" href="http://www.polymer-project.org/components/core-pages/core-pages.html">
<polymer-element name="x-foo" block>
<template>
<style>
::content > .core-selected {
position: relative;
display: block;
}
</style>
<core-pages id="pages" selected="{{selected}}">
<content></content>
</core-pages>
</template>
<script>
Polymer('x-foo', {
ready: function() {
this.selected = 0;
}
});
</script>
</polymer-element>
<polymer-element name="x-bar">
<template>
<div>
<content></content>
</div>
</template>
<script>
Polymer('x-bar', {});
</script>
</polymer-element>
<p>BEFORE</p>
<x-foo>
<x-bar>some text here</x-bar>
<x-bar>some other text here</x-bar>
</x-foo>
<p>AFTER</p>