Ample SDK | Downloads | Examples | Reference | Bug Tracker |
Part III - Writing Application LogicIn this tutorial we will go through a series of techniques and technologies used when developing logic. Overview
Introduction to the programming modelNow that we have looked at how to layout and style Application UI we can go deeper into the questions about how to build the application logic. We will be mainly concerned with the aspect of handling View interaction events and implementing Mediators, that you might known being attributes of the MVC Implementing application [View] logic is about receiving UI events and updating UI state appropriately. In Ample SDK the Application UI is represented by a DOM tree (mainly built with UI elements), that you can access or modify in a standard and consistent way. UI events are fired on elements in the tree with which the user interacts. Once dispatched get propagated through the tree and can be picked up for handling. When developing Application Logic with Ample SDK there is no need to bother with any differences that you know exist in DOM implementations of different web browsers. You can safely use and benefit from standard cross-browser APIs. Embedding JavaScript View Logic into a web pageThere are two ways to embed Ample SDK JavaScript Logic into a web page: Inline, using a script tag with type="text/javascript"<script type="text/javascript"> /* JavaScript UI Logic */ </script> Referencing a resource, using script tag with src attribute<script type="text/javascript" src="application.js"></script> The ample scripting objectThe ample scripting object in the Ample SDK is similar to the document scripting object available in the browser. It provides the starting point for the Ample SDK Document Object Model. Note! The Ample SDK Document Object Model contains Ample SDK UI fragments only. Implementing View LogicSince you very likely have a decent experience in coding against the DOM, you will find much of this section's content familiar. A nice short recap (or intro) can still be worth spending a couple of minutes on, so let's take a quick look at the APIs available and their usage examples. Implementing View Logic in a client-side application is mainly about:
Navigating Document TreeWhen User Interface XML markup is parsed, a Document Object Model (DOM) is created for it. The DOM (or application tree) can easily be traversed with help of the "DOM Core" and "Selectors API" APIs. Let's create a simple application layout and try using different APIs to navigate it. <div id="root"> <div class="wrapper"> <h3>Header</h3> <div>First child</div> <hr /> <div>Second child</div> <hr /> <div>Third child</div> </div> </div> The three sets of APIs listed below all do the same task - they allow for selecting nodes in the Application Tree:
The first set is the base set of properties that make up the DOM structure. You can use it, however it is not very efficient. Here is an example of how this API could be used to find the second HR element (supposing we have a reference to the root element retrieved by some means and we need to find the second HR element): var oElement = oRoot.firstChild.lastChild.previousSibling; The second set of APIs is a little bit more sophisticated. These functions wrap implementations of traversing the DOM thus making the task as whole simplier to specify. var oElement = ample.getElementById("root").getElementsByTagName("hr")[1]; The third API has been standardised recently. Probably this one is the most sophisticated yet easy to learn. It allows you to use the CSS selectors syntax to match elements in the document. Let's see how the previous tasks could be executed with that approach. var oElement = ample.querySelectorAll("#root hr")[1]; Altering the Document TreeDuring the application runtime you may need to modify the Application Tree by changing its structure and element's attribute values in order to dynamically add more controls or modify their states. To do that you need to know three sets of DOM Core API:
The first set of API functions allows for dynamic modification of component states or some aspects of its visual representation. The example below shows how to set the fill color on an SVG rect element: var oElement = ample.getElementById('my_circle'); oElement.setAttribute("fill", "red"); The second set allows for dynamic instantiation of components, inserting them into the tree or moving them around. For example in the code below we add a new XUL menulist item to the list of the items in dropdown: var sXulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; var oPopup = ample.getElementById('my_menulist'), oItem = ample.createElementNS(sXulNS, "xul:menuitem"); oPopup.appendChild(oItem); oItem.setAttribute("label", "New item"); Although text nodes are supported in Ample SDK, it is recommended to avoid using them. This greatly improves the coding experience and makes application markup cleaner. It is often good practice to not load the entire application UI at once but to construct its parts when the user really needs them. You could use DOM APIs to create elements, text nodes, add them to the tree, set attributes, etc., but this is not always efficient. Instead you can load a piece of UI markup from the server and add it to the Application Tree as a whole. var oRequest = new XMLHttpRequest; oRequest.open("GET", "settings.xml", true); oRequest.onreadystatechange = function() { if (this.readyState == 4) { var oElement = ample.importNode(this.responseXML.documentElement, true); ample.getElementById("settings_area").appendChild(oElement); } } oRequest.send(null); It is usually not allowed to append nodes directly from one document to another. A special operation needs to be executed first. importNode method imports an XML DOM node into Ample DOM and returns a reference to the imported structure. Right after that procedure, the imported structure can be added to the Ample DOM, for example by calling appendChild at the desired location. Note! The importNode method expects exactly two arguments, where the second one should indicate whether the entire subtree should be imported or only the root node. Registering Event Handlers and Handling EventsEvent handlers (or listeners) are pieces of JavaScript code that get executed upon the occurence of an event. You can specify an event handler in UI markup or attach it at the runtime by the means of JavaScript.
Event flow in Ample SDK supports all 3 phases of the propagation: capturing, target and bubbling. Using UI Managers for common UI tasksUI Managers are mini-services living in the Ample SDK Runtime. They usually listen to the very primitive events and talk to the base APIs in order to generate other more sophisticated events and APIs. UI Managers add their functionality to Element and/or Document base nodes thus making it available to any elements in your UI. Drag And Drop ManagerThe Drag and Drop manager implementation has been greatly inspired by the one present in the Internet Explorer browser, which is now also part of HTML5 standardization effort. In Ample SDK any visual element can be dragged or dropped onto. In order for an element to become draggable it should have a property $draggable set to true, to become droppable - a $droppable property needs to be set to true. Once you have marked element as draggable or droppable you can start listening to the Drag And Drop events. Many of the events have a default action associated with it, and canceling those events default action is possible. Below is the overview of events available for handling during Drag And Drop session:
Below you can see an example where items from one box can be dragged onto another box: <div xmlns:aml="http://www.amplesdk.com/ns/aml" ondrop="if (event.relatedTarget.parentNode != event.target) event.target.appendChild(event.relatedTarget)"> <div aml:droppable="true" class="droppable"> <div aml:draggable="true" class="draggable"> Container 1: drag me and drop to another container </div> <div aml:draggable="true" class="draggable"> Container 2: drag me and drop to another container </div> </div> <div aml:droppable="true" class="droppable"> </div> </div> And a stylesheet for it: @namespace "http://www.w3.org/1999/xhtml"; .draggable { width: 100px; height: 100px; background-color: pink; border: solid 1px red; cursor: move; } .droppable { width: 300px; height: 320px; padding: 10px; float: left; background-color: lightgreen; border: solid 1px green; } div:drag { border-color: black; position: relative; } div:drop { border-style: dashed; } In this example we marked elements as being draggable or droppable declaratively with the help of attributes from the AML language global attributes module. (You should include this language implementation in the head of your application page.) Also we have used dynamic pseudo-classes to style elements in their drag or drop states. During the Drag And Drop session no other MouseEvents are fired. See also Resize ManagerThe Resize Manager allows you to define resizable behavior of elements. This feature might be used for example to allow user input elements to be resized by the user. You can indicate that an element is resizable (i.e. the user can modify its size with the mouse) by setting a property $resizable to the value of true. It is also possible to set the behavior of all instances of a certain element on a page to become resizable by prototyping its $resizable property. This is used for example in the XUL language implementation to provide all window and wizard components with resizable behavior. Below is the overview of events available for handling during a Resize session:
Below you can see an example where a div is set to be resizable: <div aml:resizable="true" class="resizable" xmlns:aml="http://www.amplesdk.com/ns/aml"> Resize me </div> And a stylesheet for it: @namespace "http://www.w3.org/1999/xhtml"; div.resizable { width: 250px; height: 250px; background-color: pink; border: solid 1px red; max-width: 500px; } div.resizable:resize { background-color: lightyellow; } Here again we used the AML language global attributes to indicate declaratively that the element is resizable. We also used the resize dynamic pseudo-class to change the visual appearance of the element during the Resize operation. See also Browser History ManagerThe Browser History Manager implements functionality that is needed to handle user navigation in a SPI (Single Page Interface). It is assumed that Internet Explorer has pioneered the hashchange event implementation, however worth mentioning is that Ample SDK has had support for that event since earlier. Whenever a hash part of the URL in the address bar changes a hashchange event is dispatched to the document, which can be picked up and handled according to the UI needs. With the method $bookmark you can add application states to the browser history, which will appear as hash values in the address bar. Below is the list of members implemented by the Browser History Manager that are available to the developer:
The example below illustrates in greater details how to work with the Browser History Manager: #marker { width: 50px; height: 50px; position: relative; background: #add8e6; border: 1px solid #4a6f7b; left: 0px; } <div> <button onclick="onButtonClick('position1')" style="width:120px;">location 1</button> <button onclick="onButtonClick('position1')" style="width:120px;">location 2</button> <button onclick="onButtonClick('position3')" style="width:120px;">location 3</button> </div> <div id="history">marker</div> // turn on IE initial hash state fix ample.domConfig.setParameter("ample-module-history-fix", true); function onButtonClick(sLocation) { doHistoryMove(sLocation); this.ownerDocument.$bookmark(sLocation); } function doHistoryMove(sWhere) { var oMarker = ample.getElementById("marker"); switch(sWhere) { case "position1": oMarker.$play('left:100px', 500, AMLElement.EFFECT_DECELERATE); break; case "position2": oMarker.$play('left:250px', 500, AMLElement.EFFECT_DECELERATE); break; case "position3": oMarker.$play('left:400px', 500, AMLElement.EFFECT_DECELERATE); break; default: oMarker.$play('left:0px', 500, AMLElement.EFFECT_DECELERATE); break; } } ample.addEventListener("hashchange", function(oEvent) { doHistoryMove(oEvent.detail); }, false); ample.addEventListener("load", function(oEvent) { var sHash = window.location.hash.replace(/^#/, ''); if (sHash) doHistoryMove(sHash); }, false); Clicking a button shifts the marker div to a predefined location and also puts its position in the browser history. Later on, trying to navigate with the browser back/forward buttons will lead to restoring the position of the marker saved in history. Internet Explorer doesn't handle an empty hash properly, which is why in your application you need to set a special configuration parameter that will enqueue an initial history item to fix that problem. See also Capture ManagerThe Capture Manager facilitates implementations of modality (for example modal dialogs) in your application. When you need a certain area of the UI to become modal you can set a capture to that area, which will lead to all UI events happening outside it to be forwarded to the captured area. You can release the capture from the document by calling $releaseCapture on the ample document object. Below is the list of members implemented by the Capture Manager that are available to the developer:
Take a look at the example below. If you click the 'alert' button first, you will get an alert box displayed. However if you set a capture to the 'dialog' by clicking the 'set capture' button first and then try to click 'alert', you will get nothing. The capture can be removed from the dialog by clicking the 'release capture' button. <div> <button onclick="alert('Hello')">alert</button> <div id="dialog" style="border: solid 1px red"> <button onclick="this.parentNode.$releaseCapture(true)">release capture</button> </div> <button onclick="ample.getElementById('dialog').$setCapture(true)">set capture</button> </div> See also Selection ManagerSelection Manager helps managing text selectability in your application. This is indeed a rare task you would want to perform, however sometimes preventing certain parts of the application or page to be selectable can be useful. Below is the list of members implemented by the Selection Manager that are available to the developer:
The example below demonstrates how text selectability could be managed with the aml:selectable attribute. <div xmlns:aml="http://www.amplesdk.com/ns/aml"> <div style="margin:10px; padding:10px; border:solid 1px red"> selectable <div style="margin:10px; padding:10px; border:solid 1px green" aml:selectable="false"> not selectable </div> selectable </div> <div style="margin:10px; padding:10px; border:solid 1px red" aml:selectable="false"> not selectable <div style="margin:10px; padding:10px; border:solid 1px green"> not selectable </div> not selectable </div> <div style="margin:10px; padding:10px; border:solid 1px red" aml:selectable="false"> not selectable <div style="margin:10px; padding:10px; border:solid 1px green" aml:selectable="true"> selectable </div> not selectable </div> </div> See also Using SMIL 3 for AnimationsSMIL 3.0 is an XML-based language that allows authors to write interactive multimedia presentations. In Ample SDK there is an implementation of selected modules from that language specification. The amount of modules we implemented is sufficient for defining advanced animation effects in your application. The example below demonstrates how a pulsing behavior could be implemented for an SVG circle element. It will start when a user clicks on the circle, will repeat 3 times and then revert to its initial state. <svg:svg viewBox="0,0 400,400" height="400px" width="400px" xmlns:svg="http://www.w3.org/2000/svg" xmlns:smil="http://www.w3.org/2008/SMIL30/"> <svg:circle cx="200" cy="200" r="10" fill="red" opacity="1" stroke="black" stroke-width="1"> <smil:animate begin="click" dur="500ms" decelerate="0.5" repeatCount="3" to="200" attributeName="r" attributeType="XML"/> </svg:circle> </svg:svg> SMIL animations can run over attributes or style properties of any visual elements found in your application. See also Using XSLTProcessorThe XSLTProcessor object is used to perform XSL-T transformations. It transforms one XML document into another XML document by applying an XSL-T stylesheet. You may want use XSL-T transformations in order to transform your server response into another format. If you need to insert the result of a transformation into an Ample document, you should first import the generated document by calling the importNode function. In the example below we load an input document and a stylesheet. Then we transform the input document with the help of a stylesheet into the output document. In the end we alert the result of the transformation (output document), serialized with the help of XMLSerializer object. var oXMLHttpRequest = new XMLHttpRequest; // Load stylesheet document oXMLHttpRequest.open("GET", "stylesheet.xsl", false); oXMLHttpRequest.send(null); var oStylesheet = oXMLHttpRequest.responseXML; // Load input document oXMLHttpRequest.open("GET", "input.xml", false); oXMLHttpRequest.send(null); var oInput = oXMLHttpRequest.responseXML; // Create XSLTProcessor and import stylesheet into it var oXSLTProcessor = new XSLTProcessor; oXSLTProcessor.importStylesheet(oStylesheet); // Transform input document to output document and alert result var oOutput = oXSLTProcessor.transformToDocument(oInput); alert(new XMLSerializer().serializeToString(oOutput)); See also |