Wiki  |   Blog  |   Sitemap  |   Search  
JavaScript UI FrameworkTutorialsExtensions DevelopmentPart I - Creating UI Element

Part I - Creating Markup Elements

This tutorial will guide you through a series of steps explaining how you create a custom UI element and register it with Ample SDK Framework. For the practical purpose we will take well-known component "spinner" as a good use case in this tutorial.

Setting up new element class

Before starting implementation of a component you usually go through the design phase where following areas should be tackled:

  1. Functional Design - What API (properties, methods and events) shall component implement
  2. Interaction Design - How shall component behave upon user interaction
  3. Visual Design - How shall component look

Now, assume the design phase has been undergone and we know well what our "spinner" component is going to be.

Defining element constructor

An element definition is a class that will be instantiated when it is referred from a UI page. That is why when defining component you should use a function constructor. The prototype chain of the constructor must contain Element so that all base DOM functionality available from that object would get inherited to the newly defined component.

The constructor is usually used in order to instantiate component collections that (for example, keep XHTML form elements children in the "elements" property, to initialize some private properties or create anonymous DOM subtree). Note, the component is not part of the application tree at the moment when constructor is called, thus it does not make any good sense to try and access its DOM properties. Also, the component visuals are not created by this moment either, so interacting with them would result into a run-time exception.

In the spinner component constructor we do not need to do anything, thus its body is left empty.

var MySpinner	= function() {

MySpinner.prototype	= new ample.classes.Element;

Defining element name and namespace

In order to identify your new UI element you need to provide a name and namespace. The namespace token can be anything (including empty string) however it is strongly recommended that you use unique identifiers, that does neither collide with other domains in your business, nor with any third-parties' businesses. A common practice here is to use your business domain name with an additional path items clustering your needs. (Compare:,,, etc.)

MySpinner.prototype.localName		= "spinner";
MySpinner.prototype.namespaceURI	= "";

Registering element with framework

The last step you need to undertake in order to integrate your new component into framework is to register it with the framework using ample.extend function.


Defining element API

An element API comprises properties, methods and events. Let's have a look how they should be declared.

Defining attributes default values

A component often has attributes that are normally responsible for keeping its state. Attributes might have default values, whose specification can be done on a component attributes static property.

MySpinner.attributes	= {
	min:	"0",
	max:	"9",
	step:	"1",
	value:	"0"

Defining properties/methods

Properties are opposed to attributes, and it is not that simple to explain when to use what. The common practice (if you take XUL or XHTML markup languages' elements) is to have component properties duplicating all attributes while leaving application developer with a recommendation to interact with properties. Within Ample SDK framework it is recommended not to use properties for public APIs at all, since it is not possible in every browser to define proper properties handling mechanisms (setters/getters). Still your component can have properties enabled although [mutually] read-only ones. An example - selectedIndex property of some selection-based component. Methods are always valuable. For example, you could have a method close() on a "window" component (although, note, this same functionality could have been enabled by implementing open attribute)

In case with our "spinner" component, we definitely need select method and we can also have value property that will keep the value as Number.

MySpinner.prototype.value	= 0;	= function() {

Dispatching element events

A component dispatches an event usually when its state changes. For example, an XHTML input component dispatches "change" event once its value changed.

In case of our "spinner" component we shall dispatch a change event as well upon the component value change. The event has to bubble and should not be preventable (since it gets dispatched after change has taken place).

var oEvent	= this.ownerDocument.createEvent("Event");
oEvent.initEvent("change", true, false, null);

Handling incoming events

An element while running in the Ample SDK execution container gets notified with events coming from the other components and distributing through the DOM tree as well as with a set of generic events defined in DOM-Events-Level-3 specification. Although component instance can have multiple instance-level event handlers associated (registered with "on..." attributes or by calling addEventListener method), it is also possible to specify component class-level handlers. You can specify class-level event handlers by defining a handlers static property on the component class and registering event handlers by their names with it.

MySpinner.handlers   = {};

Handling UI Events

A class-level event handler for a UI event will only be called if the default action of the event is not prevented. This leaves application developer with opportunity to prevent reaction of component to a certain UI interaction thus modifying its behavior.

MySpinner.handlers.keydown	= function(oEvent) {
	var nValue	= this.getAttribute("value") * 1,
		nStep	= this.getAttribute("step") * 1;
	switch (oEvent.keyIdentifier) {
		case "Up":
			if (nValue + nStep <= this.getAttribute("max") * 1)
				this.setAttribute("value", nValue + nStep);

		case "Down":
			if (nValue - nStep >= this.getAttribute("min") * 1)
				this.setAttribute("value", nValue - nStep);

Handling DOM Mutation events

The DOM Mutation events are usefull for tracking changes of attribute values and of component insertion/removal from the document.

MySpinner.handlers.DOMAttrModified   = function(oEvent) {
	if ( == this) {
		switch (oEvent.attrName) {
			case "value":
				// Set public property to reflect the state
				this.value	= oEvent.attrValue * 1;
				// Update input field
				this.$getContainer("input").value	= oEvent.attrValue;
				// Dispatch "change" event here

Defining element presentation

The visual presentation of a UI element (that is usually referred to as shadow tree) is usually created in pure HTML. This part of the component lives its own life in the browser's DOM. The template for the shadow tree is created with JavaScript (opposed to, for example, XBL 2.0 technology where it is specified in XML) with help of $getTagOpen and $getTagClose members.

MySpinner.prototype.$getTagOpen		= function() {
	return '<div class="my-spinner' + (this.hasAttribute("class") ? ' ' + this.getAttribute("class") : '') + '">' +
				'<input class="my-spinner--input" type="text" autocomplete="off"'
					' value="'+	this.getAttribute("value") + '"' +
					' onchange="ample.$instance(this)._onChange(event)"/>' +
				'<button class="my-spinner--button-up"/>' +
				'<button class="my-spinner--button-down"/>';

MySpinner.prototype.$getTagClose	= function() {
	return '</div>';


In the code snippet about we have seen button class="my-spinner--button-up" and other strange classes. The --button-up part here indicates that this HTML element is a pseudo-element.

"gateway" pseudo-element

When an element is designed to accept children in the host document (which is not the case here in spinner, as it doesn't need any children), it should have a special pseudo-element declared "gateway". This will be the point at which the shadow content of element children to be rendered.

Enabling element styling facilities

Ample SDK enables styling facilities by implementing CSS features, such as namespaces, pseudo-classes and pseudo-elements. In order to make use of those, you will need to specify different mime-type for your stylesheets on your pages, namely text/ample+css.

<link type="text/ample+css" href="style.css"/>
<style type="text/ample+css">
	Inline stylesheet

In these stylesheets you can define rules in a standard manner:

@namespace my url('');

my|spinner {
	border: solid 1px blue;
my|spinner::input {
	border: none;
	color:	black;
	background:	transparent;
my|spinner::button-down {
	background-image:	url('media/spinner.gif');
	background-repeat:	no-repeat;
	height: 8px;
	width:	16px;
my|spinner::button-down {
	background-position-x: -16px;
my|spinner::button-down:hover {
	background-position-y: -8px;
my|spinner::button-down:active {
	background-position-y: -16px;

my|spinner::button-down:disabled {
	background-position-y: -24px;

The pseudo-elements (whose names appear after :: selector) classes will be applied to elements in the shadow tree that had their pseudo-elements defined by providing a corresponding class name, for example: my-spinner--button-up. The pseudo-classes are set by the component at the run-time by calling $setPseudoClass element method.