<X>
+
v2.0 alpha (be warned) - 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.
@@ -99,627 +192,1256 @@
X-Tag is a
Standard
+Efficient
+Pluggable
++ +
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
Install from NPM:
+ +npm install x-tag
- -
Download the build that works best for you:
+Getting Started
+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),
+
+ to create new custom element definitions that can be enhanced by using the built in lifecycle callbacks, attribute-linked getters/setters (accessors), rendering features, cached templating,
+
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 {
+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 {
+
+
+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' (){
-
- }
-});
+
+
+Gettin' Jedi with Pseudos and Extensions
+
+
+
+
+
+ Two advanced features of X-Tag you need to be aware of in detail are the pseudos and extensions API.
+
+ Here are examples:
+
+
+
+Create your own Pseudo:
+
+
+
+
+
+ Once you create a pseudo and add it to the main xtag.pseudos object, as shown 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){
+ /*
+ Description:
+
+ onParse is called when the pseudos specified in a declaration are being
+
+ compiled and wrapped around the function they are being applied to.
+
+ Parameters:
+
+ 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 pseudo chain
+ */
+
+ },
+
+ onInvoke: function(node, obj){
+
+ /*
+ Description:
+
+ 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.
+
+ Parameters:
+
+ node: the element
+
+ obj: object composed of these properties: { fn: fn, args: args, detail: detail }
+
+ */
+
+ }
+
+};
+
+xtag.create("x-foo", class extends XTagElement{
+
+ "godzilla:foo"(){/* TODO APPLICATION LOGIC */}
+
+});
+
+
+Create your own Extension
+
+
+
+ Creating an extension and using them can be simple, but you can create complex relationships and bindings using this simple method
+
+ to organize data and display it.
+
+
+
+// Lets create our X-Tag extension.
+
+xtag.extensions.gobble = {
+ mixin: (base) => class extends base {
+ connectedCallback(){
+
+ /* TODO APPLICATION LOGIC */
+
+ }
+
+ toGobble() { /* TODO APPLICATION LOGIC */ }
+
+ onGobbling() { /* TODO APPLICATION LOGIC */ }
+
+ onGobbled() { /* TODO APPLICATION LOGIC */ }
+ }
+};
+
+// Next we create our XTagElement using the extension method.
+
+xtag.create("x-gobble", class extends XTagElement.extensions("gobble"){
+
+ "toGobble::gobble"(){ /* TODO APPLICATION LOGIC */ }
+
+ "onGobbling::gobble"(){ /* TODO APPLICATION LOGIC */ }
+
+ "onGobbled::gobble"(){ /* TODO APPLICATION LOGIC */ }
+
+});
+
+
+
+
+
+
+To an Extended Voyage
+
+ 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 */ }
+
+});
+
+
+
+
+
+
+
+ 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::foo: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
+
+ they are compiled from left to right so that the 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.
-
+
+
+
+
+
+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.
+
+
+
+
+ In addition to binding attributes the ::attr extension also allow you to declare a specific data type.
+
+
+
+
+
+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;
+
+ }
+
+});
+
+
+
+
+
+
+
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.
+
+ 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.
+
+
+
+ The XTagElement's `render()` method is the work horse of the `template` `extension`and makes it possible to create feature rich application
+
+ parts. For more information regarding this check out the section.
+
+
+
+
+
+
xtag.create('x-frankenstein', class extends XTagElement {
+
'::template(true)' (){
+
return `<h2>I am Frankenstein</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.
-
+The Elements Render
+
+
+
+ The XTagElement's render() method is the work horse of the template extension. It get's attached to every X-Tag element that you create and can be reused for your own extensions and pseudos. Below is an example of using an event to initiate a mode accessor.
+
+
+
-
- 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.
+
+var _rendered = xtag.create( "x-mode", class extends XTagElement {
+
+ set 'mode::attr'(_mode) {
+
+ this.render(_mode);
+
+ return _mode;
+
}
-});
-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;
+ get 'mode::attr'(){
+
+ return this.getAttribute("mode");
+
}
-});
-
+
+ 'myModeSnippet::template'() {
+
+ return "My creation has come to be,Rendered.
";
+
+ }
+
+ 'click::event'(e){
+
+ e.target.mode = e.target.mode;
+
+ }
+
+} );
+
+
+
+
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.
- }
- }
-});
-
+ 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
-Create your own Pseudo:
+ 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.
-
- 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.
+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
- 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 }
- */
- }
-};
-
+ 'focus::event': function(){
-
+ // attaches a focus listener that calls this function when
-Inheritance
+ // something inside your custom element is focused.
-
- 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
+
}
+
});
+
+
+