Web Components

HTML Imports

Inspired by component object models available in many object-oriented languages, Web Components API has been implemented in Chromium-based browsers this year. In the latest stable release of Firefox software componentry is still behind the flag (dom.webcomponents.enabled). As for Internet Explorer, its roadmap does not exclude the implementation of Web Components in the next version of Microsoft's browser: the status.modern.ie resource listing technologies supported in IE marks component-related functionality as a feature under consideration.

A component can be defined as a reusable mix of markup, style rules and scripting routines based on the following traits:

  • components are portable: once created, they can be bound to various HTML and XML documents without restrictions; one of client-side mechanisms allowing the developer to link a component from an external file to a front-end Web page is HTML Imports;
  • components are dynamic: the developer is able to customize them at any point of time without redesigning the rest of a Web application;
  • components encapsulate functionality related to both style and interactivity: HTML Templates facilitate the use of a markup fragment as a blueprint, Custom Elements API creates behavioral patterns for a component to communicate with other objects of a Web application;
  • components technology helps the developer to separate the user interface from the application logic: this feature is especially valuable for developers building a new MVC framework or improving an existing one;
  • a code snippet defining properties, methods and events for a certain component must abide by JavaScript-specific object notation;
  • a Shadow DOM subtree can be built to prevent collisions among components from various sources.
HTML Imports

HTML Imports enable the developer to include a reusable HTML markup in other documents:

linking an imported file
<link rel="import" href="file-to-import.html">

To link a file to another Web page, the <link> element with the specific import attribute is created. The implementation status of HTML Imports can be checked in a routine returning a boolean value:

function isImplemented() {
 var implemented=typeof document.createElement("link").import=="object" ? true : false;
 return implemented;
}

If HTML Imports technology is supported, access to the imported document is provided by getting the import property of the link element:

if(isImplemented==true) {
 console.info("Your browser supports HTML Imports");
 var link=document.querySelector("link[rel='import']");
 var importedDocument=link.import; // HTMLDocument
 . . .
}

The most straightforward way to render the contents of the imported document is to copy the inner HTML of its body to the body of the current document:

document.body.innerHTML=importedDocument.body.innerHTML;

There's an example below creating a "pixel snatcher" - a minimalistic Web application displaying RGBA values of a pixel extracted from a local image. Both CSS rules and "heavyweight" scripting are present in the imported HTML document, so the main document remains as brief as possible:

main-file.html
. . .
<link rel="import" href="file-to-import.html">
. . .
<script>
 var link=document.querySelector("link[rel='import']");
 var importedDocument=link.import;
 document.body.innerHTML=importedDocument.body.innerHTML;
</script>
. . .

The imported document contains three <div> elements:

button emulation
<div id="file-selector"></div>
canvas container
<div id="canvas-holder"></div>
logging area for displaying RGBA values
<div id="rgba-data-holder"></div>

The <div> element emulating an "Open File" button gets its style according to the rules defined in the imported document: CSS rules listed in the <style> element will be applied to the main document.

CSS rules declared in the imported document
<style type="text/css">
 #file-selector {
  background-image: url(folder.png);
  background-position: 50% 50%;
  background-repeat: no-repeat;
  cursor: pointer;
  height: 48px;
  width: 48px;
 }
 #canvas-holder {
  position: relative;
  left: 0%; top: 0%;
 }
</style>

Scripting routines from the imported document will be executed in the context of the main document:

scripting routines defined in the imported document
var red=document.createElement('span'); // creating span for holding red value
red.style.color='red';
red.textContent='R'; // temporary placeholder

. . . creating three more spans for holding green, blue and alpha values . . .

document.body.onload=init;

The document obect from the snippet above is the main document. If access to the imported document is required, the document.currentScript.ownerDocument property can be employed:

console.log(document.URL); // absolute path to main-document.html
console.log(document.currentScript.ownerDocument.URL); // absolute path to file-to-import.html

In the init() function the spans created above will be appended to the <div> container. Besides, a click event listener is instantiated for the <div> element emulating button behavior:

function init() {
 var rgbaDataHolder=document.querySelector("#rgba-data-holder");
 rgbaDataHolder.appendChild(red);

 . . . appending three more spans - green, blue and alpha . . .

 document.querySelector("#file-selector").addEventListener("click", createInputElement, false);
}

The click handler creates an input element for selecting a file, makes the element invisible and dispatches a click event to it:

function createInputElement() {
 var fileSelector=document.createElement("input");
 fileSelector.type="file";
 fileSelector.accept="image/*";
 fileSelector.style.display="none";
 fileSelector.addEventListener("change", readImageAsDataURL, false);
 document.body.appendChild(fileSelector);
 fileSelector.click();
}

An image selected by the user is passed to an instance of FileReader:

function readImageAsDataURL() {
 if(this.files!=0) {
  var file=this.files[0];
  var fileReader=new FileReader();
  fileReader.onload=renderImage;
  fileReader.readAsDataURL(file);
 }
}

The image parsed as a data URL is rendered on the canvas:

function renderImage(event) {
 var canvasHolder=document.querySelector("#canvas-holder");
 var url=event.target.result;
 var image=new Image();
 image.src=url;
 var canvas=document.createElement('canvas');
 canvas.width=image.width;
 canvas.height=image.height;
 var context=canvas.getContext('2d');
 context.drawImage(image, 0, 0);
 canvas.onmousemove=showPixelData;
 canvasHolder.appendChild(canvas);
}

To get image data from the canvas, API members of the canvas rendering context are used in combination with event-specific properties:

function showPixelData(event) {
 document.title=event.layerX+", "+event.layerY;
 var canvas=event.target;
 var context=canvas.getContext('2d');
 var imageData=context.getImageData(event.layerX, event.layerY, 1, 1);
 red.textContent=imageData.data[0];
 green.textContent=imageData.data[1];
 blue.textContent=imageData.data[2];
 alpha.textContent=imageData.data[3];
}