I want to rewrite the <input type="number"> element with Polymer so that i can <input is="number-input"> and style it in a way so that it looks and behaves the same on different browsers.
This is where I'm at now:
<link rel="import" href="../polymer/polymer.html">
<polymer-element name="number-input" extends="input" attributes="value">
<script>
Polymer('number-input', {
valueChanged: function(){
console.log(this.value)
}
});
</script>
</polymer-element>
... and using it by <input is="number-input">, but it doesn't fire the valueChanged function.
What am I doin wrong?
Teltrik did a recent article on styling inputs with shadow dom that was pretty interesting: http://developer.telerik.com/featured/comprehensive-guide-styling-file-inputs/
In your case, you're doing everything correctly. The problem however, is that input already has a .value property. You're trying to override the native property which creates unpredictable behavior. The second issues is that Object.observe() cannot observe native properties on elements. For example, if you added the hidden attribute, hiddenChanged would never be called. Likewise for title and titleChanged.
Related
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.
In the Polymer document (https://elements.polymer-project.org/elements/iron-input), I found:
<input is="iron-input" bind-value="{{myValue}}">
And in another official document (https://www.polymer-project.org/1.0/docs/devguide/registering-elements.html#type-extension) , I found:
<dom-module id="main-document-element">
<template>
<p>
Hi! I'm a Polymer element that was defined in the
main document!
</p>
</template>
<script>
HTMLImports.whenReady(function () {
Polymer({
is: 'main-document-element'
});
});
</script>
</dom-module>
<main-document-element></main-document-element>
I was just wondering why the first one <input is="iron-input" bind-value="{{myValue}}"> can't be written as <iron-input bind-value="{{myValue}}">.
Is it for compatibility, which makes it easier to polyfill?
The iron-input element does not contain any HTML in its source code. This means that doing:
<iron-input bind-value="{{myValue}}">
will not produce an actual input on the page for the user to interact with. The iron-input element is really a collection of behaviours that you can apply to a standard HTML input.
I have a polymer element which is a stack of images that need to expand and reveal each of the images upon hovering on the stack. It's supposed to look like this if left untouched:
And upon hovering, the stack would expand vertically.
The code for the element is (do-profile-pic is another element that puts each image down):
<polymer-element name="do-profile-pic-stack" attributes="images">
<template>
<style>...</style>
<div class="stack-container"
on-mouseover='{{onHovered}}'
on-mouseout='{{onUnhovered}}'>
<template repeat="{{picture, i in images}}">
<div class="stack-img-container"
/* the stack is angled so bit of every image is visible */
style="top: {{5 * i}}; z-index: {{10 - i}}">
<do-profile-pic imgurl="{{picture.url}}" showtime="false">
</do-profile-pic>
</div>
</template>
</div>
</template>
<script>
Polymer('do-profile-pic-stack', {
images: [],
...
});
</script>
</polymer-element>
Now i've looked around, and there seem to be two ways to do this. One is to use the polymer selectors. The other is to use user event methods. I'm not using any of these methods to achieve the default angled layout (comment above). To expand the stack vertically, i'll have to play with positioning and layout, but i cannot seem to think of a good way to implement the this.
The polymer selectors appear complicated.
Programmatically, it looks like playing with HTMLElement will be required. I can do that, but angular-js handles this much better. So is there a better way to go about this that i might be mssing? Thanks.
If you want to change appearance on mouse-over I think you can go with a pure CSS solution with two sets of styles one normal and one for mouse-over.
stack-img-container {
// normal style here
}
stack-img-container:hover {
// mouse-over style
// overrides and extends normal style
}
and put it in your style tag (instead of the dots ...).
Use Element.classList to change class dynamically.
<script src="//cdnjs.cloudflare.com/ajax/libs/polymer/0.3.3/platform.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/polymer/0.3.3/polymer.js"></script>
<my-app></my-app>
<polymer-element name='my-app'>
<template>
<style>
.hovered {
background-color:red;
}
</style>
<span on-mouseenter='{{onHover}}'
on-mouseleave='{{onUnhover}}'>
Mouse over me, please.
</span>
</template>
<script>
Polymer('my-app', {
onHover: function(e) {
e.srcElement.classList.add('hovered');
},
onUnhover: function(e) {
e.srcElement.classList.remove('hovered');
}
})
</script>
</polymer-element>
Notes:
Event.srcElement is not standard will and not work for every user.
The standard way to reference an element within an Polymer element is the Polymer.Base $$(slctr):
this.$$('span').classList.add('hovered');
It works in my project, but not in this snippet.
The code above was copied and edited from Peter Burns' code snippet from How can I handle a hover the Polymer way without external libraries?
Curly braces around onHover in the event handler shouldn't be necessary, however, they don't work otherwise in this snippet. Not sure if it's because of the version of Polymer used. I tried to link to the latest but it failed.
Here is a demo of changing class dynamically with plain javascript.
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.
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.