While working on my NFO editor for Kodi I ran into an issue when dealing with fanart
since Kodi supports multiple fanart and my simple JavaScript function had no way to broadcast an event back to the main script. I had played with Web Components a bit in the past but was usually frustrated by the code. While there are plenty of great resources out there they rarely broke down clearly what I wanted to accomplish so I thought not only was this a great opportunity to learn something new but also pass along some things that might be helpful for others getting into the game.
I'm organizing this similarly to how the MDN examples at web-components-examples are set up but with each component .js
file named the way the component is used. There is a sample html
file to show off the functionality but I definitely need to work on better documentation.
The individual component .js
files can be downloaded and used as they are sel-contained but there is a full test suite if you download the release:
npm i
npm start
then go to http://http://localhost:3600/ to test all of the components including for submission support.
A replacement for the standard <input type="file">
supporting normal file selection and drag/drop. The sample page demonstrates adding/removing new instances of the component and a 'Save' button will console.log()
the current files.
<file-upload types=*.*|file.svg application/msword|doc.svg application/vnd.openxmlformats-officedocument.wordprocessingml.document|docx.svg application/pdf|pdf.svg" lookup-types="text/plain|txt.svg" addable removable>
All attributes are optional.
addable
indicates the user has the ability to add another instance of the component. You can use.addEventListener('add', function)
to handle the event.lookup-types
are theMIME
types the component should use to provide additional types you want to visually represent but don't want to restrict the user from choosing in the dialog. As withtypes
use a pipe character after the lookup type and provide a path to the icon to display such asimage/png|img/png.png
.placeholder
is the text to show when no file is set. The default is "Choose or drop a file".required
indicates a file must be set before<form>
validity will be true.removable
indicates the user has the ability to remove that instance of the component. You can use.addEventListener('remove', remove)
to handle the event.types
are theMIME
types the component should accept separated by spaces. If none are provided all file types are are accepted through the*.*
type. Dropping an unacceptable type produces bothwindow.alert()
andconsole.error()
messages of the droppedMIME
type and a list of the acceptable types. This should be customizable in the future with a custom event trigger. You may also provide icons to represent the types by adding a pipe character after the type and then providing a path to the icon to display such asapplication/pdf|img/pdf.svg
.value
contains both the filename of the provided file as well as its encoded content separated by a;
. Fireschange
event when the value changes, current value can be retrieved fromevent.target.value
.
When focused Spacebar
will act the same as a click
event, opening the OS dialog to choose a file. +
and -
will trigger the add
or remove
events if those attributes are enabled.
The component can easily be styled and provides a number CSS Parts to customize the internal elements. See the sample html
file for examples of styling.
file
is the placeholder<div>
element which is the parent of either the.file-image
is the placeholder's<img>
element.file-name
is the placeholder's<span>
element.actions
is the toolbar with theadd
,clear
andremove
action buttons.
The component is fully compatible inside <form>
elements. Seeting the required
attribute will deny validity until a media file has been set. As this component encodes the chosen file multipart
form handling is not required.
An image-based option chooser with option-group
and multiple
behavior. Insert <img>
elements inside the component to generate the options. At a minimum an id
is necessary to generate each option's id
. Additionally you can specify both a title and and initial checked state through dataset
attributes data-title
and data-checked
respectively.
<image-option-group multiple><img id="test" src="test.png" data-title="Test" data-checked="false"></image-option-group>
disabled
indicates the user cannot interact with the component.multiple
to allow selection of multiple options. If not included only a single option within the group may be selected, clicking/tapping another removes the checked status of the others.required
indicates at least one option must be chosen whenmultiple
is true, or one option chosen when not.value
is a semi-colon separated list ofid
s that are checked.
change
is fired everytime the number of selected options changes.
disabled
returns or sets thedisabled
attribute as a boolean.multiple
returns or sets themultiple
attribute as a boolean.required
returns or sets therequired
attribute as a boolean.value
returns an array ofid
s representing which options are checked or passes an array ofid
s to set as checked..
When focused both Enter
and Spacebar
with toggle a non-disabled
option, and tab
and shift-tab
will navigate between options.
There are a few standard styles all of which can be overwriiten by targeting the approriate part.
option
is the container<div>
element which by default isdisplay: flex; flex-direction: column;
.image
is the option's<img>
element.status
is the option's check box which at the moment uses a hard-coded gray-outlined circle to indicate unchecked and a filled-green circle with white checkmark to indicate checked. By default it isposition: absolute; top: 0; right: 0;width: 32px; height: 32px;
I have plans to allow passing the images used to indicate checked status in the future.title
is the optional<div>
element that contains the title.
The component is fully compatible inside <form>
elements. Seeting the required
attribute will deny validity until at least one option has been chosen when the multiple
attribute is set, .
There are many image upload with preview solutions out there, this one is mine. It supports file selection and drag/drop, images and video, add and remove events as well as a simplistic zoom. I hope to make the zoom more full-featured and add crop functionality in the future. The sample page demonstrates adding/removing new instances of the component and a 'Save' button will console.log()
the current media.
<media-upload types="image/jpeg image/png image/webp" value="sample.png" addable removable>
All attributes are optional.
addable
indicates the user has the ability to add another instance of the component. You can use.addEventListener('add', function)
to handle the event.required
indicates a media file must be set before<form>
validity will be true.removable
indicates the user has the ability to remove that instance of the component. You can use.addEventListener('remove', remove)
to handle the event.types
are theMIME
types the component should accept separated by spaces. If none are provided all possible types are set which areimage/avif
,image/gif
,image/jpeg
,image/png
,image/svg+xml
,image/webp
,video/mp4
andvideo/webm
. Dropping an unacceptable type produces bothwindow.alert()
andconsole.error()
messages of the droppedMIME
type and a list of the acceptable types. This should be customizable in the future with a custom event trigger.value
is an existing media reference to show when the component first appears. When saving you can.getAttribute('value')
orelement.value
to get the current media. Fireschange
event when the value changes, current value can be retrieved fromevent.target.value
.
When focused Spacebar
will act the same as a click
event, opening the OS dialog to choose a file. Z
or zwill zoom the current media.
Escapewill clear the current media or close zoom.
+and
-will trigger the
addor
remove` events if those attributes are enabled.
The component has a number of internal styles that I probably need to work on since my pages are based on a full reset. In your own CSS
you will need to specify dimensions for the component so all the internal elements can scale to that size. The example page shows how using aspect-ratio
you can hint to the user the dimensions of the image you require. You can style the border
of the component and hopefully in the future I will support styling the media formats supported icons as well as the buttons.
The component is fully compatible inside <form>
elements. Seeting the required
attribute will deny validity until a media file has been set. As this component encodes the chosen file multipart
form handling is not required.
A fancy (for me π) progress bar as a replacement for the standard <progress>
element. It uses the same attributes so it can be a drop-in replacement.
<progress-bar value="0" max="100" gradient="linear-gradient(43deg, #4158D0 0%, #C850C0 46%, #FFCC70 100%)" nostripes></progress-bar>
value
is the current progress of the control. It defaults to 0 and if it exceedsmax
it is set tomax
.max
is the maximum progress. It defaults to 100.gradient
allows a custom CSSbackground-image
for the progress bar. This should be in the standard CSS format.nostripes
will disable the animated stripes normally applied to the progress bar.
Standards are set in terms of the border-radius
and background-image
fill gradient which is green-ish. The primary element is filled with an inherited background-color
. The component scales based on font-size
.
fill
controls the inner bar that indicates progress. Settingbackground-image
changes the look (see example) but you can also changeborder-radius
,background
and other styles.
Another common pattern, this one was very frustrating owing to how document.createElement
and svg
don't play well together. No half-stars here, I'd have to find a different way to render the stars and probably lose the fancy schmancy rounded corners (unless som CSS
genius wishes to step forward π), but Kodi only uses one to five for it's userrating
. But I'm happy with the live highlight of stars as you hover over the component. Clicking on the current number of stars is how you remove the value.
<star-rating stars="10" value="3"></star-rating>
All attributes are optional.
stars
is the number of stars to display. The default value is5
and the minimum is1
. There is no limit set but, y'know...value
is the number of stars set. Rather than supporting0
stars excluding this attribute, or selecting the current number of stars, is meant to indicate no value set. Fireschange
event when the value changes, current value can be retrieved fromevent.target.value
.
When focused ArrowLeft
will decrease value
until null
. ArrowRight
will increase value
until the value of stars
then will set as null
allowing the user to wrap around and continue pressing the key indefinitely.
You can style the stroke
and fill
of the stars at the top level and use ::part
to style the three states of the stars. There are default fill colors: white for unselected stars, yellow for selected stars and gold when hovering. Otherwise it will scale with font-size
as my use-case is inline but there's no reason you can't put it into a block-level container.
star
is the is the default state.star-selected
is for any star equal to or less than the currentvalue
.star-hover
is for stars when hovering. This is an adaptive state in that the hover styles are only applied to stars equal to or great than the currentvalue
whilestar
is applied to any star greater than the one you're currently hovering over, all stars are set tostar
when you hover over the currentvalue
, andstar-selected
is applied to any star greater than the one being hovered and less than or equal tovalue
. See demo page for how this works.
The component is fully compatible inside <form>
elements. Seeting the required
attribute will deny validity until thew number of selected stars is at least one.
An approximation of the iOS toggle as a replacement for the standard <input type="checkbox">
form control.
<toggle-switch checked disabled></toggle-switch>
All attributes are optional.
checked
indicates the component is currently in theon
state. Using.getAttribute('checked')
orelement.checked
will return aboolean
indicating whether the switch ison
oroff
. Fireschange
event when the checked attribute changes, current value can be retrieved fromevent.target.checked
.disabled
indicates whether the control can be toggled by the user.
.toggle()
can be used to simulate user interactivity with the component. Callingelement.toggle()
is the same as sending aclick
event which will be ignored if the component currently has thedisabled
attribute set. Usingelement.setAttribute(state)
is the only way to change the state regardless ofdisabled
.
When focused both Enter
and Spacebar
with toggle a non-disabled
instance.
Another opinionated display in terms of colors, using greys for the off
state background and off-green for on
. Opacity is used to indicate disabled
. The component will resize height based on font-size
of the containing element.
The component is fully compatible inside <form>
elements.