Skip to content

Instantly share code, notes, and snippets.

@kwltrs
Created October 12, 2015 12:38
Show Gist options
  • Save kwltrs/bb7fccb70a774d991baa to your computer and use it in GitHub Desktop.
Save kwltrs/bb7fccb70a774d991baa to your computer and use it in GitHub Desktop.
Code extracted from my talk "Fancy Input Elements with Web Components" held at Fronteers 2015 Jam Session. https://fronteers.nl/congres/2015/jam-session
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<link rel=import href=star-rating.html>
<style>input[type=range] { font-size: 3em; } output { font-size: 2em; padding-left: 2em; }</style>
</head>
<body>
<form>
<input id=input type=range is=star-rating>
<output id=aout></output>
</form>
<script>
(function () {
'use strict';
const ratings = ['I hate it', 'I don\'t like it', 'It\'s okay', 'I like it', 'I love it'];
const rating = document.getElementById('input');
const aout = document.getElementById('aout');
rating.addEventListener('change', (evt) => {
let v = rating.value, s = ratings[v-1];
aout.value = `${s} (${v} stars)`;
});
}());
</script>
<a class="shameless-plug" href="http://www.knowit.no/systemutviklere/">We're hiring</a>
</body>
</html>
<template id=star-rating-template>
<style>
:host {
-webkit-appearance: none;
color: inherit; background-color: inherit;
}
:host(:focus) { outline: none; }
.star { color: papayawhip; text-shadow: 0 0 1px indigo; cursor: pointer; }
.star.selected { color: orange; }
.stars:hover .star { color: lightskyblue; }
.star:hover ~ .star { color: papayawhip; }
.star:hover ~ .star.selected { color: orange; }
</style>
<span class=stars>
<span class=star>★</span><span class=star>★</span><span class=star>★</span><span class=star>★</span><span class=star>★</span>
<span>
</template>
<script>
(function () {
'use strict';
const toArray = (_) => Array.prototype.slice.call(_);
const markSelected = (el) => el.classList.add('selected');
const markDeselected = (el) => el.classList.remove('selected');
const importDoc = document.currentScript.ownerDocument;
const template = importDoc.querySelector('#star-rating-template');
document.registerElement('star-rating', {
extends: 'input',
prototype: Object.create(HTMLInputElement.prototype, {
createdCallback: {
value: function () {
this.min = 0; this.max = 5; this.step = 1;
this.value = this.attributes.value || 0;
const root = this.createShadowRoot();
root.appendChild( template.content.cloneNode(true) );
const stars = toArray( root.querySelectorAll('.star') );
const ratingOfStar = (el) => stars.indexOf(el) + 1;
const isStarElement = (el) => stars.indexOf(el) !== -1;
const highlightStar = (value) => {
stars.slice(0, value).forEach( markSelected );
stars.slice(value).forEach( markDeselected )
};
const setRating = (newValue) => {
highlightStar( newValue );
if (this.value != newValue) {
this.value = newValue;
this.dispatchEvent( new Event('change') );
}
};
this.addEventListener('click', (evt) => {
let el = evt.path[0];
if ( isStarElement(el) ) {
setRating( ratingOfStar(el) );
}
});
setRating( this.value );
}
}
})
});
}());
</script>
@kwltrs
Copy link
Author

kwltrs commented Oct 12, 2015

The code works on chromium 45. As I mentioned during my talk, calling Element.createShadowRoot() for an element which already hosts a user-agent root is deprecated. So the approach I showed here is not going to work in future.

Please leave a comment or get in touch if you know a better approach to implement a widget behaving like a native HTMLInputElement, e.g. will be included in form.elements or FormData.get, without adding an hidden input field to the host document.

Me: https://www.npmjs.com/package/@kwltrs/about-me

@tvler
Copy link

tvler commented May 6, 2016

Hey, I was working on implementing the same thing and ran into the multiple shadow root problem too. Did you ever figure out how to work around that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment