$Source: /spark/Website/resources/downloads/afettes/draft.html,v $ $Revision: 1.2 $ $Date: 2004/04/23 02:09:04 $
********************************************** ** SVG Programmers Application Resource Kit ** ********************************************** INTRODUCTION: - todo ------------- - Why is SPARK needed? - Paper outline BACKGROUND: - todo ----------- - SVG - Viewers (Adobe, Batik) HISTORY: - todo -------- - Philip and Chris L. start SPARK fall 2002 - Chris L. - CGUI - Chris Peto - SVG editor - Glenn MacDonald - skinning example - Alastair Fettes - SVGOpen - initial thoughts - SchemaSoft project - spring 2004 GOALS: - todo ------ - a framework (not just a set of widgets) - interoperability - MVC - modularity - low coupling, high cohesion - serialization - easy to use SPARK ARCHITECTURE: ------------------- The SPARK framework describes a set of rules and techniques for creating SVG user interface widgets. The architecture describes the interfaces that the differently widget types must follow in order for the interoperability of the widgets. There are two main sides of the SPARK framework - SVG and Code (ECMAScript and/or Java). The SVG structure has been standardized to a certain extent to ensure interoperability of widgets and for possible future applications that use the framework outside of a application user interface context. The coding side of the framework is in place to ensure widgets are able to talk each other successfully and to enforce certain pertinent paradigms. Initially most widgets will be written in ECMAScript but support for Java (and any other Scripting/Programming language available for use in the future) is a design goal of the project in the long term. SPARK follows a number of well established programming paradigms. These have been used in order to enforce good design principles. The paradigms include Model-View-Controller separation, information hiding, Observer/Observable paradigm. It also describes the document object model to a certain degree (visibility of widgets, widget base types, etc). These are in place to enforce the afore mentioned paradigms and avoid dependency issues. BASIC SPARK WIDGET STRUCTURE (SVG) ---------------------------------- <g id="uniqueID" class="SPARK widgetType widgetName [CSSClass]"> <desc>...Widget Descriptive Information...</desc> - required <metadata>...Widget State Information (Model)...</metadata> - optional ...contents... - optional </g> All widgets must: - have a root SVGElement that contains all the widgets members - the suggested element type is a group (<g/>) element. - have a document wide unique ID. - have a class attribute that follows the following rule <g class="SPARK widgetType widgetName CSSClass"/> where: - SPARK - static identifier for application to recognize a widget - widgetType - "atom" or "container" - widgetName - the class name, can also used for CSS purposes - CSSClass - optional use - have a <desc/> element that contains information relating to the widget (i.e. for accessibility, what the widget is for, etc) - optionally have a <metadata/> element that contains state information (i.e. for serialization). OBSERVER PARADIGM ----------------- SPARK widgets implement the Observer/Observable interface. The "Observable" interface: <code> /** * Observable interface. * * @see org.SchemaSoft.dom.SPARK.Observer */ package org.SchemaSoft.dom.SPARK; public interface Observable { /** * Add an observer to the set of listeners for this widget. * @param in_pObserver The observer to add. */ public void addObserver( Observer in_pObserver ); /** * Remove an observer from the set of listeners for this widget. * @param in_pObserver The observer to remove. */ public void removeObserver( Observer in_pObserver ); } </code> The "Observer" Interface: <code> /** * Observer interface. */ package org.SchemaSoft.dom.SPARK; public interface Observer { /** * Called by an observable when it handles an event. */ public void notify( Observable in_pObservable); } </code> SPARK CLASS DEFINITION ---------------------- Java Abstract Class: <code> /** * SPARK abstract class. Defines basic SPARK widget structure * including member variables and public methods. * * @see org.SchemaSoft.dom.SPARK.Observer */ package org.SchemaSoft.dom.SPARK; public abstract class SPARK { /** * The class attribute of the widget. */ public static final string CLASS; /** * A regular expression that defines the class attribute * of the widget. */ public static final string REGEX; /** * The document-wide unique id of this widget. */ public final string ID; /** * Retrieve the widget's state. * @return The widgets state value. */ public Object getState(); /** * Set the widgets state. * @param in_state The new state value. */ public void setState( Object in_state ); } </code> ECMAScript Abstract Prototype: <code> /** * Atom abstract class. */ function SPARK(){} /** * The class attribute of the widget. */ SPARK.CLASS; /** * A regular expression that defines the class attribute * of the widget. */ SPARK.REGEX; /** * The document-wide unique ID of this widget. */ SPARK.ID; /** * Retrieve the widget's state. * @return The widgets state value. */ SPARK.prototype.getState = function(){} /** * Set the widgets state. * @param in_state The new state value. */ SPARK.prototype.setState = function( in_state ){} </code> WIDGET TYPES: ------------- All widgets are of type "atom" or of type "container". An atom is defined by a widget that contains no other widget. A container is defined by a widget that may contain a mixed, unordered set of "atom" and "container" widgets. This set may be entirely made of "atom" widgets or conversely of entirely "container" widgets. It may also be an empty set. VISIBILITY STRUCTURE OF WIDGETS: -------------------------------- - "atom" widgets only have knowledge of themselves. - "container" widgets have knowledge of their own widget contents as well as any "atom" or "container" widgets that they themselves contain (are the direct container of). The result of this visibility structure is one that enforces the paradigm of information hiding. The one way visibility is a directed graph downwards through the SPARK DOM tree. The only link that a lower widget (with respect to the SPARK DOM tree) to a higher level widget is through the observer/ observable paradigm (described later). The driving force behind this is the idea that a top level widget should not need to know about a bottom level widget. If this information is required then the widget should be placed as an immediately childNode of the widget that requires the information. ATOM WIDGET DEFINITION ---------------------- All atomic widgets should provide: - A static factory method used to create a DOM version of the widget. SVG: <g id="uniqueID" class="SPARK atom widgetName CSSClass"> <desc/> - required <metadata/> - optional ...view... - optional </g> Example: <g id="abc123" class="SPARK atom MyAtom MyAtomCSS"> <desc>This is MyAtom!</desc> <metadata>Hello World</metadata> <circle cx="100" cy="100" r="50" fill="red"/> <text>Click me!</text> </g> Java Abstract Class: <code> /** * Atom abstract class. * * @see org.SchemaSoft.dom.SPARK.Observer * @see org.SchemaSoft.dom.SPARK.SPARK */ package org.SchemaSoft.dom.SPARK; public abstract class Atom extends SPARK implements Observerable { /** * Interface members. */ public void addObserver( Observer in_pObserver ); public void removeObserver( Observer in_pObserver ); } </code> ECMAScript Abstract Prototype: <code> /** * Atom abstract class. */ function Atom( in_node ); /** * Interface members. */ Atom.prototype.addObserver = function( in_pObserver ); Atom.prototype.removeObserver = function( in_pObserver ); </code> CONTAINER WIDGET DEFINITION --------------------------- All container widgets should provide: - A static factory method used to create a DOM version of the widget. - load any expected child widgets themselves during their constructor (using helper methods if needed). SVG: <g id="uniqueID" class="SPARK container widgetName"> <desc/> - required <metadata/> - optional ...view... - optional <g class="SPARK contents"/> - optional, zero to many - contains all member "atoms" and or "containers" - may be placed anywhere inside container; only container itself must have advanced knowledge of location. </g> Example: <g id="abc123" class="SPARK container MyContainer"> <desc>A container to hold widgets.</desc> <circle cx="100" cy="100" r="50" fill="red"/> <g class="SPARK contents"> <desc>Buttons located near top of widget.</desc> <g id="abc456" class="SPARK atom MyAtom MyAtomCSS"> <desc>This is a button.</desc> <circle cx="100" cy="100" r="50" fill="red"/> </g> </g> <g class="SPARK contents"> <desc>Buttons located near bottom of widget.</desc> <g id="abc789" class="SPARK atom MyAtom MyAtomCSS"> <desc>This is another button.</desc> <metadata>25</metadata> <circle cx="100" cy="100" r="50" fill="red"/> </g> </g> </g> Java Abstract Class: <code> /** * Container abstract class. * * @see org.SchemaSoft.dom.SPARK.Observer * @see org.SchemaSoft.dom.SPARK.Observerable * @see org.SchemaSoft.dom.SPARK.Atom * @see org.SchemaSoft.dom.SPARK.SPARK */ package org.SchemaSoft.dom.SPARK; public abstract class Container extends SPARK implements Observer, Observable { /** * Add a container or atom to this container widget. * @param in_pContents The atom or container to add. */ public void add( SPARK in_pContents ); /** * Remove a container or atom from this container widget. * @param in_pContents The atom or container to remove. */ public void remove( SPARK in_pContents ); /** * Interface members. */ public void notify( Observable in_pObservable ); public void addObserver( Observer in_pObserver ); public void removeObserver( Observer in_pObserver ); } </code> ECMAScript Abstract Prototype: <code> /** * Constructor */ function Container(); /** * Add a container or atom to this container widget. * @param in_pContents The atom or container to add. */ Container.prototype.add = function( in_pContents ); /** * Remove a container or atom from this container widget. * @param in_pContents The atom or container to remove. */ Container.prototype.remove = function( in_pContents ); /** * Interface members. */ Container.prototype.notify = function( in_pObservable ); Container.prototype.addObserver = function( in_pObserver ); Container.prototype.removeObserver = function( in_pObserver ); </code> DESIGN DECISIONS: ----------------- 1. SVG 1.1 features supported Backwards compatible for most SVG viewers currently in use. 2. SVG 1.2 features not supported To be added (when functionality is added to viewers) in SPARK v2.0 3. No XUL or XForms These languages are too constrictive for the SPARK framework. Therefore they have been ignored. It is suggested however to look at these languages as good examples of an initial widget set. 4. No XBL See design decision 2. XBL is a very interesting technology and is part of the SVG 1.2 draft. Careful thought will be given for implementing XBL in SPARK 2.0. Decision will be based primarily upon implementation in the browser and relevance to SPARK. 5. SVG Decisions 5.1 <desc/> Element The reasons for the desc element are threefold: a. Accessibility for UI. b. Description of UI element invaluable. c. Workaround for bug in Adobe SVG view. ASVG will cause the browser to crash if a pointer is kept to an SVG node and an EventListener is added to it through script. The desc element ensures that the programmer may always retain an anchor to the SVG node if required. 5.2 <metadata/> Element A flexible storage container. Any type of XML information may be contained within. This leaves it up to the programmer to decide how their information is stored. Also, model (state) information is easily queried by retrieving the metadata element through the use of the getElementByID function and retrieving the metadata node. This also enables MVC separation as the model is separated from the View in the SVG structure. 5.3 <g class="SPARK contents"/> Element This is used primarily for future applications that wish to create SPARK based UI's using an interface similar to the Visual Basic programming environment. With a 'g' element that is specifically used for containing other widgets then it is easy to decide where in the SVG to insert the new member widget. 5.3 @class="SPARK widgetType widgetName widgetClass" The first two classes are used to identify the SVG node as a SPARK widget. In general the use of CSS classes are advised due to the ease of skinning an application. 6. Script generated SVG code It is definitely not advised to have a large amount of script generated SVG code. Reasons: - static representation of UI (if no script engine present) - XSLT created/modified UI - enable RAD applications to create UI (ex. Visual Basic drag and drop UI creation). - serialization of UI 7. Atom and Container Paradigm These are the lowest common denominators of all widgets. Any widget is able to be broken down into one of these two categories. It also mimics other well known applications storage structure (structured storage libraries), object hierarchies and file structures. 8. Observer/Observable Interface This paradigm enables information hiding, encapsulation and low coupling of widgets all of which are good software engineering design principles. This also enforces the one way visibility structure of the UI DOM tree. FUTURE OF SPARK: ---------------- 1. SPARK 2.0 Adjust for new features in the SVG 1.2 draft. This version will not necessarily be backwards compatible with SPARK 1.0 and SVG 1.1 *only* viewers. SPARK 1.0 will be depreciated once the majority of viewers are SVG 1.2 enabled. On particular update that will most likely be included is the use of XBL. 2. <metadata/> It is a possibility that there will be a suggested metadata structure. In the end it is still up to the developer to use their own content model. 3. Widgets, widgets and more widgets The only way to ensure that SPARK is successful is to steadily increase the number of widgets available. 4. Call for feedback More suggestions, input and support are always welcome. With more community support the framework will become more robust and popular throughout the SVG community. REFERENCES: - todo ----------- 1. SVG 1.1 and 1.2 draft 2. Philip Mansfield 3. Darryl Fuller 4. SPARK presentation at SVGOpen 2003 (author list) 5. Kevin Lindsey ACKNOWLEDGEMENTS: - todo ----------------- 1. Christoper Lewis 2. Glenn MacDonald 3. Chris Peto APPENDIX: --------- 1. SVG EXAMPLES: ---------------- 1.1 Atom Examples: <g id="spark-5" transform="translate(50,0)" class="SPARK atom Radiobutton"> <desc>Radiobutton - red</desc> <metadata>red</metadata> <rect width="100" height="40"/> <text x="10" y="20">red</text> </g> 1.2 Container Examples: <g id="fillcolor" transform="translate(350,50)" class="SPARK container RadiobuttonGroup"> <desc>RadioGroup for picking colors.</desc> <g id="spark-5" transform="translate(50,0)" class="SPARK atom Radiobutton"> <desc>Radiobutton - red</desc> <metadata>red</metadata> <rect width="100" height="40"/> <text x="10" y="20">red</text> </g> <g id="spark-6" transform="translate(50,100)" class="SPARK atom Radiobutton"> <desc>Radiobutton - blue</desc> <metadata>blue</metadata> <rect width="100" height="40"/> <text x="10" y="20">blue</text> </g> <g id="spark-7" transform="translate(50,200)" class="SPARK atom Radiobutton"> <desc>Radiobutton - green</desc> <metadata>green</metadata> <rect width="100" height="40"/> <text x="10" y="20">green</text> </g> </g> 2. ECMAScript EXAMPLES: - current out of date; update needed ----------------------- 2.1 Atom Examples: MyWidget.js (ECMAScript): <code> /** * Regular expression defining the class attribute for a MyWidget * SVGElement root element. */ MyWidget.REGEX = /^SPARK atom MyWidget/; /** * String expression defining the class attribute for a MyWidget * SVGElement root element. */ MyWidget.CLASS = "SPARK atom MyWidget"; /** * Create a new MyWidget instance using an existing SVGElement * as the root. * @param in_node The root SVGElement. */ function MyWidget( in_node ) { this.id = in_node.getAttribute( "id" ); }; /** * Interface member. * @param in_observer The observer to add to the list of observers. * MyWidget.prototype.addObserver = function( in_observer ) { this.observers.push( in_observer ); }; /** * Interface member. * @param in_observer The observer to remove from the observers list. **/ MyWidget.prototype.removeObserver = function( in_observer ) { var bFound = false; for( var i = 0; i < this.observers.length; i++ ) { if( this.observers[i] == in_observer ) { this.observers[i] = null; bNotFound = false; break; } } if( bFound ) { for( var i = 0; i < this.widgets.length-1; i++ ) { if( this.observers[i] == null ) { this.observers[i] = this.widgets[i+1]; this.observers[i+1] = null; } } } }; /** * This widget was selected. */ MyWidget.prototype.select = function() { for( var i = 0; i < this.observers.length; i++ ) { if( this.observers[i] != null ) { var observer = this.observers[i]; observer.notify( this ); } } }; /** * Factory function for creating a new MyWidget. * @param in_parent The parent SVGElement to this widget. * @return The new MyWidget. */ MyWidget.createWidget = function( in_parent ) { var SVGDoc = in_parent.ownerDocument; var node = SVGDoc.createElement( "g" ); node.setAttribute( "id", "abc123" ); node.setAttribute( "class", MyWidget.CLASS ); in_parent.appendChild( node ); var widget = new MyWidget( node ); return( widget ); }; </code> 2.2 Container Examples: MyContainer.js (ECMAScript) <code> /** * Regular expression defining the class attribute for a MyContainer * SVGElement root element. */ MyContainer.REGEX = /^SPARK container MyContainer/; /** * String expression defining the class attribute for a MyContainer * SVGElement root element. */ MyContainer.CLASS = "SPARK container MyContainer"; /** * Create a new MyContainer instance using an existing SVGElement * as the root. * @param in_node The root SVGElement. */ function MyContainer( in_node ) { this.id = in_node.getAttribute( "id" ); this.atoms = new Array(); this.containers = new Array(); }; /** * Add an atom. * @param in_atom The atom to add. * MyContainer.prototype.addAtom = function( in_atom ) { this.atoms.push( in_atom ); }; /** * Remove an atom. * @param in_atom The atom to remove. **/ MyContainer.prototype.removeAtom = function( in_atom ) { var bFound = false; for( var i = 0; i < this.atoms.length; i++ ) { if( this.atoms[i] == in_atom ) { this.atoms[i] = null; bNotFound = false; break; } } if( bFound ) { for( var i = 0; i < this.atoms.length-1; i++ ) { if( this.atoms[i] == null ) { this.atoms[i] = this.atoms[i+1]; this.atoms[i+1] = null; } } } }; /** * Add a container. * @param in_container The container to add. * MyContainer.prototype.addContainer = function( in_container ) { this.containers.push( in_container ); }; /** * Remove a container. * @param in_container The container to remove. **/ MyContainer.prototype.removeContainer = function( in_container ) { var bFound = false; for( var i = 0; i < this.containers.length; i++ ) { if( this.containers[i] == in_container ) { this.containers[i] = null; bNotFound = false; break; } } if( bFound ) { for( var i = 0; i < this.containers.length-1; i++ ) { if( this.containers[i] == null ) { this.containers[i] = this.containers[i+1]; this.containers[i+1] = null; } } } }; /** * Interface member. */ MyContainer.prototype.notify = function( in_atom ) { // do state change }; /** * Factory function for creating a new MyContainer. * @param in_parent The parent SVGElement to this widget. * @return The new MyContainer. */ MyContainer.createWidget = function( in_parent ) { var SVGDoc = in_parent.ownerDocument; var node = SVGDoc.createElement( "g" ); node.setAttribute( "id", "abc123" ); node.setAttribute( "class", MyContainer.CLASS ); in_parent.appendChild( node ); var widget = new MyContainer( node ); return( widget ); }; </code>