<code-block> Web Component

comment

JavaScript widgets are traditionally built using the <div> soup and a mixture of framework-specific scripts. The Web Components aim to change the status quo by introducing a standard way of building visual elements.

Custom elements — part of the Web Components standard — let web developers create their own HTML elements that behave like the built-in ones. Those elements have several advantages compared to the div-based widgets:

  1. encapsulation of their inner structure by using the shadow DOM and scoped styles.
  2. DOM API treats custom elements as first-class (e.g. appendChild accepts custom elements).
  3. elements can be inserted using either HTML markup or scripts.

Chrome (version 31) already supports Web Components but it's necessary to enable:

Enabling the Show Shadow DOM option in DevTools will show encapsulated structure of the Custom Element when it is inspected.

No other browser supports the current version of Web Components APIs. Polymer is a polyfill library that can be used to emulate Web Components.

Using <code-block>

The page below uses the code-block custom element to pretty-print a fragment of a JavaScript source code.

<!DOCTYPE html>
<html>
<head>
    <link rel="import" href="code-block.html">
</head>
<body>
    <p>Code block demo. Right click and select Inspect.</p>
    <code-block language="javascript">function* take(count, seq) {
    for (var i = 0; i &lt; count; i++) {
        yield seq.next().value;
    }
}</code-block>

<p>Adding code-block element using script.</p>
<script>
function add() {
    var codeBlock = new CodeBlockHTMLElement();
    codeBlock.value = "var seq = numbers(2);";
    codeBlock.setAttribute("language", "javascript");
    document.body.appendChild(codeBlock);
}
</script>

<button onclick="add()">Add new code block</button>

</body>
</html>

An instance of the element is inserted using the HTML markup. After clicking the button a new element is created using JavaScript and appended to the <body>.

To run the example your browser has to support:

  • Registering custom elements
  • Importing HTML
Click Render to run the example.

The example page uses import link to point to a document that defines a template used by the custom element and a script:

<html>
    <template id="code-block-template">
        <style>
            @import url("codemirror.css");

            .outer { font-family: monospace; }
        </style>
        <div class="outer"><div class="inner"></div></div>
    </template>
    <script src="codemirror.js"></script>
    <script src="code-block.js"></script>
</html>

The script creates a new prototype inheriting from HTMLElement. createdCallback is invoked by the browser when the element is instantiated. The example below uses the CodeMirror library to highlight the syntax of the code.

It is important to wait until the style sheet has been loaded before enabling CodeMirror as the styles are used to measure elements.

(function() {

var thisDocument = document.currentScript.ownerDocument;

var codeBlockPrototype = Object.create(HTMLElement.prototype);

codeBlockPrototype.createdCallback = function() {
    var root = this.createShadowRoot();
    var template = thisDocument.getElementById('code-block-template');
    root.appendChild(template.content.cloneNode(true));

    var self = this;

    var style = root.querySelector('style');
    var waitForStyles = function () {
        if (style.sheet && style.sheet.cssRules[0].styleSheet) {
            embedCodeMirror();
        } else {
            window.setTimeout(waitForStyles, 10);
        }
    };

    var embedCodeMirror = function () {
        var sourceCode = root.querySelector('.inner');
        self.codeMirror = CodeMirror(function(element) {
            sourceCode.parentNode.replaceChild(element, sourceCode);
        }, {
            value: self.textContent,
            mode: self.getAttribute('language'),
            lineNumbers: true,
            viewportMargin: Infinity,
            readOnly: true
        });
    };

    waitForStyles();
};

Object.defineProperty(codeBlockPrototype, 'value', {
    get: function() {
        if (this.codeMirror) {
            return this.codeMirror.getValue();
        }
        return this.textContent;
    },
    set: function(value) {
        if (this.codeMirror) {
            this.codeMirror.setValue(value);
        }
        this.textContent = value;
    }
});

window.CodeBlockHTMLElement = document.registerElement('code-block', {
    prototype: codeBlockPrototype
});

}());

The custom element is exported as the CodeBlockHTMLElement variable. This constructor is used by the example page to dynamically create new instances of the code highlighter.

Comments

cancel

Revisions

  1. Initial version.
  2. Add information about required Chrome flag — HTML imports.