About | Download | Examples | Tutorials | Mostbet App | Mostbet Azerbaycan | Mostbet AZ |
Hello World!Getting StartedDevelopment RolesApplication DevelopmentExtensions DevelopmentTechnical ArticlesFor jQuery UsersPresentations |
|
Part I - Creating Markup ElementsThis 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 classBefore starting implementation of a component you usually go through the design phase where following areas should be tackled:
Now, assume the design phase has been undergone and we know well what our "spinner" component is going to be. Defining element constructorAn 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 namespaceIn 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: http://www.w3.org/2001/xml-events, http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul, http://www.amplesdk.com/ns/aml, etc.) MySpinner.prototype.localName = "spinner"; MySpinner.prototype.namespaceURI = "http://www.mybusiness.com/ns/ui"; Registering element with frameworkThe 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. ample.extend(MySpinner); Defining element APIAn element API comprises properties, methods and events. Let's have a look how they should be declared. Defining attributes default valuesA 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/methodsProperties 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; MySpinner.prototype.select = function() { this.$getContainer("input").select(); }; Dispatching element eventsA 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); this.dispatchEvent(oEvent); Handling incoming eventsAn 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 EventsA 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); break; case "Down": if (nValue - nStep >= this.getAttribute("min") * 1) this.setAttribute("value", nValue - nStep); break; } } Handling DOM Mutation eventsThe 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 (oEvent.target == 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 break; } } }; Defining element presentationThe 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 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>'; }; Pseudo-elementsIn 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-elementWhen 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 facilitiesAmple 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 */ </style> In these stylesheets you can define rules in a standard manner: @namespace my url('http://www.mybusiness.com/ns/ui'); my|spinner { border: solid 1px blue; } my|spinner::input { border: none; color: black; background: transparent; } my|spinner::button-up, 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-up:hover, my|spinner::button-down:hover { background-position-y: -8px; } my|spinner::button-up:active, my|spinner::button-down:active { background-position-y: -16px; } my|spinner::button-up:disabled, 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. |