npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

jail-js

v0.3.4

Published

A very basic library for easing the work required to use HTML imports, template tag, and custom components.

Downloads

2

Readme

JAIL.js - Just-Another-Interesting-Library

Build Status npm version License: MIT

Table of Contents

  1. Intro to Library
    1. Example
      1. Without the Library
      2. With the Library
    2. The Pieces
      1. Decorators
        1. Class Decorators
        2. Property Decorators
        3. Method Decorators
      2. Enumerations
      3. Interfaces
        1. Data Interfaces
        2. Component Interfaces
      4. Typeguards
    3. Getting Started
      1. Consume Library
      2. Build Components
        1. Create your Main HTML
        2. Create your HTML Template
        3. Create your Stylesheet
        4. Create your Javascript

Intro to Library

This library provides developers an abstraction from the work required to create and use native web components. It is built with Typescript, and targets the V1 spec of Web Components and Shadow DOM. It also utilizes features from HTML Imports and the Template tag, providing a full native suite of features.

Instead of trying to explain too much of why, I'll provide some examples of using the web components without the library vs using this library. This is a very limited view. You have to think of using these in more realistic projects with tens to hundreds of different components.

Please Note - This was written back when TS Mixins were expressed using decorators. Since TS has added actual mixins, the library needs to be updated to fit these changes. I've currently locked the version of TS to a version that the library was built on for now, so it will build and run.

Example

Without the Library

  • index.html
<html>
<head>
    <link rel="import" href="./basic-element.html" />
</head>
<body>
    <basic-element>
        <div>Hello World</div>
    </basic-element>
</body>
</html>
  • basic-element.html
<template id="basicElement">
  <style>
    p { color: orange; }
  </style>
  <p>Cool, I'm in Shadow DOM. My markup was stamped from a &lt;template&gt;.</p>
  <input type="text" id="basicInput">
</template>

<script src="./script"></script>
  • basic-element.ts

class BasicElement extends HTMLElement {

    /**
     * Property for the input child.
     */
    private _input: HTMLInputElement;

    constructor() {
        super();

        let ownerDocument = (document.currentScript || document._currentScript).ownerDocument;
        let t = ownerDocument.querySelector('#basicElement');
        let clone = t.content.cloneNode(true);

        // Create a shadow DOM
        let shadowRoot = this.attachShadow({mode: 'open'});
        shadowRoot.appendChild(clone);

        this._input = shadowRoot.querySelector("#basicInput");

        // Set an initial state for the component attributes.
        this.setAttribute("attr1", 0);
        this.setAttribute("attr2", true);
    }

    /**
     * List all the attributes that should be watched.
     */
    static observedAttributes() {
        return [ "my-attr1", "my-attr2" ]
    }

    connectedCallback() {
        // Bring in all the DOM children into the shadow DOM, so they get displayed.
        for (let child of this.children) {
            shadowRoot.appendChild(child);
        }
    }

    attributeChangedCallback(attrName, oldValue, newValue) {
        switch(attrName) {
            case "my-attr1":
                this.onAttr1Changed(oldValue, newValue);
                break;
            case "my-attr2":
                this.onAttr2Changed(oldValue, newValue);
                break;
        }
    }

    onAttr1Changed(oldValue: number, newValue: number) {
        let attr2 = this.getAttribute("my-attr2");

        if (attr2) {
            console.log(`Hello, the number value changed from ${oldValue} to ${newValue}`)
        }
    }

    onAttr2Changed(oldValue: boolean, newValue: boolean) {
        if (newValue) {
            console.log(`Hello, your current value is ${this._input.value}`)
        }
    }
}

document.registerElement('basic-element', BasicElement);

Couple things to note with not using the library

  • No css is being used here because its actually a very complex problem to link css from a file in HTML imports. The relative path of the imports will be whatever the hosting HTML file is, not the HTML file that is being imported. The <style> tag is used instead
  • Without using the web-components polyfill or writing something yourself, a race condition will be created where a parent component is finished loading and is beginning to do some of its functionality and a child component hasn't loaded yet; Unexpected behavior will occur if you try to use the child that hasn't loaded.

With the Library

  • index.html
<html>
<body>
    <basic-element>
        <div>Hello World</div>
    </basic-element>
</body>

<!--The classes of all the components in your application need to be loaded.-->
<!--This part could easily be one line if you already are using index module exports.-->
<script src="./basic-element"></script>
</html>
  • basic-element.css
p {
    color: orange;
}
  • basic-element.html
<template>
  <p>Cool, I'm in Shadow DOM. My markup was stamped from a &lt;template&gt;.</p>
  <input type="text" id="basicInput">
</template>
  • basic-element.ts
import { Component, Attributes, Attribute, AttributeChangedListener } from "jail-js/decorators";

/**__dirname isn't available for projects that aren't node based. See web example for how to deal with getting file locations.**/
@Component({
    tagName: "basic-element",
    templateUrl: `${__dirname}/basic-element.html`,
    styleUrl: `${__dirname}/basic-element.css`
})
@Attributes([
    { name: "my-attr1"},
    { name: "my-attr2", value: true }
])
class BasicElement extends HTMLElement {

    /**
     * Property for the input child.
     */
    @QuerySelector("#basicInput") private _input: HTMLInputElement;

    @Attribute private myAttr1 = 0; // This value could have also been set using the @Attributes decorator by adding a value to the attribute info.
    @Attribute private myAttr2;

    @AttributeChangedListener("my-attr1")
    onAttr1Changed(oldValue: number, newValue: number) {
        if (this.myAttr2) {
            console.log(`Hello, the number value changed from ${oldValue} to ${newValue}`)
        }
    }

    @AttributeChangedListener("my-attr2")
    onAttr2Changed(oldValue: boolean, newValue: boolean) {
        if (newValue) {
            console.log(`Hello, your current value is ${this._input.value}`)
        }
    }
}

The Pieces

Getting Started

A more detailed guide can be found here

To start building your own application using the library start by NPM installing:

npm install --save jail-js

No other steps need to be made to start using the library.

Consume Library

To consume the library in your JavaScript/Typescript:

import { [decorators to import] } from "jail-js/decorators";
import { [interfaces to import] } from "jail-js/interfaces";
import { [typeguards to import] } from "jail-js/typeguards";
import { [enumerations to import] } from "jail-js/enumerations";

Build Components

Create your main HTML

  • index.html
<html>
<body>
    <my-element></my-element>
    <script src="./my-element.js"></script>
</body>
</html>

Create your HTML template

  • my-element.html
<template>
    <div id="child"></div>
</template>

Create your stylesheet

  • my-element.css
#child {
    color: red;
}

Create your Javascript

First import the pieces you need from the library, write your code, and prosper.

  • my-element.ts
import { Component, Attribute, QuerySelector, Attributes, AttributeChangedListener } from "jail-js/decorators";
import { IOnConnected } from "jail-js/interfaces";

@Component({
    tagName: "my-element",
    templateURL: "path/to/my-element.html",
    styleURL: "path/to/my-element.css" // or ["path/to/style1.css", "path/to/style2.css"]
})
@Attributes([
    { name: "first-attribute" },
    { name: "second-attribute", value: 0 }
])
export class MyElement extends HTMLElement implements IOnConnected {
    /**
     * Reads and writes the `first-attribute` attribute to the DOM.
     */
    @Attribute public firstAttribute: boolean = true;

    /**
     * Reads and writes the `second-attribute` attribute to the DOM.
     */
    @Attribute private secondAttribute: number;

    /**
     * Reads the child with the #child selector from the DOM.
     */
    @QuerySelector("#child") private _child;

    /**
     * If you add a constructor, you must always call super() as the first line.
     */
    constructor() {
        super();

        // Bind the handler to this event.
        this.onChildClicked = this.onChildClicked.bind(this);
    }

    /**
     * Hook up the child clicked event.
     */
    onConnected() {
        this._child.addEventListener("click", this.onChildClicked)
    }

    /**
     * Increment the attribute when the child is clicked.
     */
    onChildClicked() {
        this.secondAttribute++;
    }

    /**
     * Log when first-attribute is changed.
     */
    @AttributeChangedListener("first-attribute")
    onFirstAttributeChanged(oldValue, newValue) {
        console.log("first-attribute was changed");
    }

    /**
     * Log when second-attribute is changed.
     */
    @AttributeChangedListener("second-attribute")
    onSecondAttributeChanged(oldValue, newValue) {
        console.log("second-attribute was changed");
    }
}