<X>
v2.0.3 beta - for v1, click here
X-Tag is a lightweight, power-packed Web Components library that gets you there fast, and stays in its own lane.
-
Standard
Built on Web Components APIs -
Efficient
Powerful features in 3k min/gzip -
Pluggable
Plays nice with other libraries
What is X-Tag?
Originally developed at Mozilla, now supported by devs at Microsoft, X-Tag is an open source JavaScript library that wraps the W3C standard Web Components family of APIs to provide a compact, feature-rich interface for component development. While X-Tag makes it easy to leverage all the Web Components APIs (Custom Elements, Shadow DOM, Templates, and HTML Imports), it only requires Custom Elements API support to operate. In the absence of the native Custom Elements APIs, X-Tag uses the same polyfill Google's Polymer framework relies on. You can view our package options in the Builds section
xtag.create('x-clock', class extends XTagElement {
connectedCallback () {
this.start();
}
start (){
this.update();
this._interval = setInterval(() => this.update(), 1000);
}
stop (){
this._interval = clearInterval(this._data.interval);
}
update (){
this.textContent = new Date().toLocaleTimeString();
}
'tap::event' (){
if (this._interval) this.stop();
else this.start();
}
});
Browser Support
Install from NPM:
npm install x-tag
Download the build that works best for you:
Getting Started
The most important method in the X-Tag library is xtag.create()
. The create
function on the X-Tag object is what you'll use
to create new custom element definitions, which can include things like lifecycle callbacks, attribute-linked getters/setters (accessors),
and event listeners. Here's what defining a simple custom element looks like with X-Tag:
const Frank = xtag.create('x-frankenstein', class extends XTagElement {
constructor (){
alert("It's moving...it's alive!");
}
});
Notice the class definition extends the class XTagElement
. If you are not extending from a previously defined Custom Element,
you must always extend from the XTagElement
class for X-Tag to properly construct your Custom Element definition. The tag name argument (and subsequent automatic
element registration) is optional - here's what it looks like without it, and how to register your element with a tag name separately:
const Frank = xtag.create(class extends XTagElement {
constructor (){
alert("Muhahaha!");
}
});
xtag.register('x-frankenstein', Frank);
A Word on Extensions and Pseudos
In the next sections you're going to hear about a few features that are delivered via two different types of modular extensibility systems the
library offers for imbuing your elements with advanced functionality. These two systems are called extensions
and pseudos
.
Both are different in their intent, scope of effect, range of capabilities. You will use them frequently in your Custom Element class definition
blocks, here's what they look like:
xtag.create('x-foo', class extends XTagElement {
'alert::foo' (){
}
'count:bar' (){
}
'send::attr:bar:zaz' (){
}
});
Notice how the alert method has a ::foo
attached to it - this is an extension. Extensions are little modules you can created that are called
into service by tagging a property with ::
+ the name of the extension. Extensions are a sizable API of their own that let's you manipulate
far more than just the property they are tagged to. You can only use one extension per property.
The other API for adding custom functionality to components is called pseudos, which are seen above in the example property count:bar
. When a pseudo is tagged to a property, the pseudo's
own function wraps the target function so that the pseudo is executed first, then the target, which receives its return value. You can chain pseudos, and
they are compiled from left to right so that they left most pseudo function wrapper fires first, passing each function's return value down to the next, until
finally the original target function is executed. You can see what this looks like in the above example property send::foo:bar:zaz
.
There are separate guides for extensions and pseudos that talk about how to create and use your own, but for now just be aware that they are one of the primary means the X-Tag library delivers its own features.
Content is King
Many of the components you create will require child elements for structure and presentation. X-Tag includes a content templating
extension
that makes this quick and easy. Simply add a ::template
function to your component definition
and return a string or ES6 Template String. In the example below, notice the true
parameter being passed to the extension.
This tells the template extension to render the content immediately when an instance is created. If you don't want a template to render
automatically like this, don't pass anything to it.
xtag.create('x-frankenstein', class extends XTagElement {
name (){ return 'Frankenstein'; }
'::template(true)' (){
return `<h2>I am ${this.name()}</h2>
<span>I was created by a mad scientist</span>`
}
});
Manual Rendering of Templates
If you elect not to have your template automatically rendered when your component instances are created, you'll need to manually trigger render.
const Frank = xtag.create('x-frankenstein', class extends XTagElement {
name (){ return 'Frankenstein'; }
'::template' (){
return `<h2>I am ${this.name()}</h2>
<span>I was created by a mad scientist</span>`
}
});
const FrankNode = new Frank();
FrankNode.render();
Multiple Templates
The template extension can be used multiple times within the same class definition block, which allows you to specify different templates
for use in the same component. The templates are cached and reference based on the property name you put in front of the ::template
extension
token. To render your templates, simply pass the name of the template you want to use to the render
function, as in the following example:
const Frank = xtag.create('x-frankenstein', class extends XTagElement {
'foo::template(true)' (){
return `<h2>My name is Frankenstein</h2>
<span>I work for a mad scientist</span>`
}
'bar::template' (){
return `<h2>My friends call me Frank</h2>
<span>I work for a mad scientist</span>`
}
});
const FrankNode = new Frank();
// Because the foo template indicated it was to be automatically rendered, its content is already present in your element instance.
FrankNode.render('bar');
// Calling the render function with the bar template name swaps out the current foo template content with the bar content.
Say Hello to Custom Attribute Accessors
Accessors are custom attributes linking to a corresponding pair of setters/getters, and X-Tag has a built-in extension that makes adding accessors to
your custom element definition stupid simple. To use this extension, add an ::attr
modifier to one of your class definition properties.
This tells X-Tag to wire up a getter/setter to an HTML attribute of the same name. When attributes are linked to a getter/setter their gets, sets, and
state changes will be synced without having to write any additional code.
You can also tell the accessor extension that type of accessor you are creating, and it will help by sanitizing values and modifying the gets/sets to match the behavior of the type you declare. I
xtag.create('x-foo', class extends XTagElement {
set 'maxVolume::attr' (value){
// X-Tag automatically maps camel cased getter/setter names to their
// dashed attribute equivalents. In this example, the `maxVolume`
// getter/setter pair maps to the `max-volume` attribute.
}
});
xtag.create('x-bar', class extends XTagElement {
'::template(true)' (){ return '<input />' }
set 'disabled::attr(boolean)' (value){
// The ::attr extended property links node.disabled to gets/sets of
// the disabled="" attribute. Because it is declared as a boolean,
// all values passed into its getter/setter will be a Boolean.
this.firstElementChild.disabled = value;
}
});
The DL on Events
X-Tag provides a powerful event system you'll use often in developing your components. We'll cover the basics here, and leave the more advanced features for a special tutorial dedicated to the topic.
To add event listeners to your component (either native or custom events), you'll be using the event
extension. To do so, add a property to your
Custom Element definition that matches the name of the event you want to listen for, plus the ::event
extension flag. When your class definition
is processed by X-Tag's create method, the event
extension will remove the property, add a listener using the supplied property name with the
function you supplied as the handler.
xtag.register('x-foo', {
'click::event': function(){
// attaches a click listener that calls this function when
// the user clicks an element inside the custom element
},
'focus::event': function(){
// attaches a focus listener that calls this function when
// something inside your custom element is focused.
}
});
Gettin' Jedi with Pseudos
One advanced feature of X-Tag you should be aware of right off the bat is the delegate
pseudo.
X-Tag features a function modifier system called pseudos
which allows you to seamlessly wrap
functions anywhere in your custom element defintion object (lifecycle callbacks, methods, accessors, and events)
to extend their functionality. The delegate
pseudo enables you to quickly add event delegation
(filtering of event targets based on CSS expressions) to any event function you add to your component. Here's an example:
xtag.register('x-foo', {
content: '<input/>',
events: {
'tap:delegate(input)': function(){
// Perform an action only when the user taps on an
// <input/> element within your component.
}
}
});
Create your own Pseudo:
Once you create a pseudo and add it to the main xtag.pseudos object, as show below, you can then use it on any key of your custom element definition objects that has a function as its value.
xtag.pseudos.foo = {
onParse: function(ctor, property, args, fn){
/*
Parse is called when the pseudos specified in a declaration are being
compiled and wrapped around the function they are being applied to.
ctor: the Custom Element Class definition
property: property the pseudo is being applied to (ex: ping:foo, property = ping)
args: arguments from the pseudo definition (ex: prop:foo(1, 2), args = [1, 2])
fn: the function the pseudo is wrapping in the pseud chain
*/
},
onInvoke: function(node, obj){
/*
Invoke is called any time the pseudo-wrapped function is called. This is where
you perform whatever action you desire on the node or object the pseudo targets.
node: the element
obj: object composed of these properties: { fn: fn, args: args, detail: detail }
*/
}
};
Inheritance
X-Tag uses the standard Custom Elements mechanism for inheritance: ES6 class extension. The return value of the create
method is an X-Tag compiled ES6 class, which
can be itself extended either via generic ES6 class declaration, or by again passing it into create
, if you wanted to add additional features to your new extended class
using X-Tag APIs.
const Foo = xtag.create('x-foo', class extends XTagElement {
set 'foo::attr' (){ /* Handle foo attribute change */ }
});
Bar extends Foo {
bar (){ }
}
const Zaz = xtag.create('x-zaz', class extends Bar {
'click::event' (){ /* do something when clicked */ }
});
API Reference
xtag.create(TAG_NAME, DEFINITION)
Create and (optionally) register a Custom Element definition for use as a new tag in HTML markup or via DOM APIS.
Argument | Type | Description |
---|---|---|
TAG_NAME(optional) | String | The tag name you want to use when using your Custom Element (must contain a dash). When this argument is passed, the Custom Element definition is automatically added to the document's Custom Element registry. |
DEFINITION | Class | The class definition block for your Custom Element |
const Frank = xtag.create('x-frankenstein', class extends XTagElement {
constructor (){
alert("It's moving...it's alive!");
}
});
xtag.addEvent(ELEMENT, TYPE, HANDLER, CAPTURE)
Add a native, custom, or X-Tag library event listener to an element. The TYPE field also accepts pseudo chains for even more powerful event handling.
Argument | Type | Description |
---|---|---|
ELEMENT | DOM element reference | The element you would like to attach an event listener to. |
TYPE | String | The event name, with any event pseudos you may want to chain. |
HANDLER | Function | The event handler you want called when the event occurs. |
CAPTURE | Function | Boolean argument you can pass if you want the event to use capturing vs bubbling. |
xtag.addEvent(myElement, 'tap:delegate(img)', function(event){
alert('An image element within myElement was just tapped');
});
xtag.addEvents(ELEMENT, OBJECT)
Add multiple native, custom, or X-Tag library event listeners to an element using one function. Pseudo chains are avaliable for use in your event object keys.
Argument | Type | Description |
---|---|---|
ELEMENT | DOM element reference | The element you would like to attach the event listeners to. |
OBJECT | Object | An object composed of multiple events - keys are event names, and values are the event handlers. |
xtag.addEvents(myElement, {
'tap': function(event){
alert('myElement was just tapped');
},
'keypress:keypass(13)': function(event){
alert('The enter key was pressed, all other keys are blocked');
}
});
xtag.fireEvent(ELEMENT, NAME, OPTIONS)
Add a native, custom, or X-Tag library event listener to an element. The TYPE field also accepts pseudo chains for even more powerful event handling.
Argument | Type | Description |
---|---|---|
ELEMENT | DOM element reference | The element you would like to attach an event listener to. |
NAME | String | The event name you wish to fire on the target element. |
OPTIONS | Object |
The event options you want to use in firing your event. These include:
|
xtag.fireEvent(myElement, 'show', {
detail: {
x: 123,
y: 456
}
});