Zebra: HTML5 Canvas Rich UI Library
Join the DZone community and get the full member experience.
Join For FreeWhat is Zebra?
The Zebra is a JavaScript library that follows easy OOP concepts and implements a rich set of various self-made UI components. There are a huge number of attractive and powerful WEB UI frameworks on the market. Most of them are stuck to HTML, DOM and CSS. They build UIs by coloring DOM with CSS and manipulating the HTML DOM tree. Zebra UI is different. It is not based on HTML, DOM or CSS. Zebra UI components are implemented and rendered from scratch as a number of widgets organized in hierarchy. How does Zebra manage to build and render UIs on the web? The essential thing required by Zebra abstraction is the existence of a "Canvas-like " component. The "Canvas" component has to have set of graphical methods to paint lines, simple shapes, text, images and be able to catch input (keyboard, mouse, etc) events. For instance Java AWT/SWING, Eclipse SWT, .NET or other platforms will supply "Canvas-like" components. But the new HTML5 standard embeds "Canvas" element. This element is supported by most of the modern browsers and is what makes it possible and reasonable to "drop" Zebra UI into a web context. |
See Zebra demo http://zebra.gravitysoft.org
The table below lists the most significant Zebra UI components, managers view, etc. Components marked by orange background are still in development:
The Zebra Java to JavaScript converter is out of context in this article, nevertheless it plays key role in porting Java-based UI components onto the web. For demo purposes, a slightly outdated version of the Java to JavaScript converter is available: http://j2js.gravitysoft.org
Zebra JavaScript Easy OOP concept
An attractive, easy for use, and understandable programming model is very important in context of having supportable, extendable, elegant code. This is one of the painful problems in web development. Zebra introduces easy OOP concepts as a foundation. ~10.5kb of Zebra code helps to do the following:
To get more information about Zebra OOP see the cheat sheet: Zebra easy OOP concept cheat sheet
Zebra "Hello WEB" application
The Zebra JavaScript demo indents to show an internal window with a "Hello web" title. The window is opened by pressing a button:
// create canvas and store root panel in local variable var c = new zCanvas(html5Canvas), r = c.root; // create button var b = new Button("Hello WEB"); // set border layout manager r.setLayout(new BorderLayout()); // add button to center of root panel r.add(Layout.CENTER, b); // register the button event listener that opens external // window every time the button has been pressed b._(function() { var w = new Window("Hello WEB"); w.setSize(200,200); w.show(); });
Zebra UI features via JavaScript code snippets
Zebra UI component design follows approaches similar to traditional Java AWT/SWING, .NET, and Eclipse SWT. UI components are organized as a hierarchy where some components are laid out on others. But Zebra does many things much more quickly and easily. Below are some Zebra UI features equpped with code snippets:
- Compound components. Zebra UI components often are built as a combination of several other components laid out on a panel. For instance the "Button" component is a panel that keeps a child component as its label. By default title is "Label", but developers can set other UI components as a button label. Take a look at the snippets below:
// by default button uses label component as its content var button = new Button("Label"); // set image as the button label var button = new Button(new ImagePan("b.png")); // set image+label as the button content. The image+label is also // compound component that consists of image and label var bContent = new Panel(new FlowLayout()); bContent.add(new ImagePan()); bContent.add(new Label()); var button = new Button(bContent);
The "BorderPan" UI component is one more example of a compound component. It consists of a label and content panel. Take a look at the snippets below:
// border panel uses simple "Label" as its title by default var bp = new BorderPanel("Label", new Panel()); // border panel uses another border panel as its title var bp = new BorderPanel(new BorderPan("Label"), new Panel()); // border panel uses "Checkbox" as its title var bp = new BorderPanel(new Checkbox("Check me"), new Panel());
- Full control over UI component rendering. Zebra UI components painting is fully in developers hands. Zebra calls "paint", "update", "paintOnTop" UI component methods with a graphical context as an input argument. These methods form a UI component face. Passed graphical context gives developers number of elementary methods to draw and fill primitive shapes, paint text, and images.
The "paint" method defines the UI component "face":
// create inner class instance with redefined component "face" var myEyesCandyComponent = new Panel([ function paint(g) { // paint what ever you want using passed graphical context g.drawLine(...); g.drawRect(...); g.fillArc(...); } ]);
The "update" method forms a component background:
// create inner class instance and override background // rendering with a custom implementation var myEyesCandyComponent = new Panel([ function update(g) { ... } ]);
The "paintOnTop" method may be used, for instance, to render a focus indicator over the component "face":
// render rectangle around component if the component holds focus var myEyesCandyComponent = new Panel([ function paintOnTop(g) { if (this.hasFocus()) g.drawRect(0,0, this.width, this.height); } ]);
Zebra paint manager takes care of triggering the re-painting of invalidated-"dirty" areas. UI components are designed to make direct "repaint" method execution unnecessary. But custom UI components should call thr "repaint([x, y, w, h])" method every time a new dirty area has appeared. The "repaint" method execution triggers paint manager to recalculate current dirty area and schedule repainting when it is possible:
var MyCustomComponent = Class(Panel, [ function setBorderColor(c) { this.color = c; this.repaint(); // inform paint manager the component // has to be completely re-painted } ]);
- Neat events handling. When it is possible, Zebra event handling follows a declarative pattern (see next feature bullet). It allows developers avoiding listeners registration and simplifies the event handling concept. To catch an event:
- Express an intention to get the desired event type by inheritance an appropriate interface. Interface is like a marker.
- Implement function(s) to treat desired event.
For instance, imagine a custom component that needs to handle mouse events. Express the intention by implementing the "MouseListener" interface:
// let Zebra know that the component wants getting mouse // events by implementing"MouseListener" interface var MyComponent = new Class(Panel, MouseListener, []);
Suppose "mouse button has pressed" and "mouse cursor has entered the component" event types have to be handled. To do it, declare the following functions correspondently:
var MyComponent = new Class(Panel, MouseListener, [ function mousePressed(e) { // handle mouse pressed event here }, function mouseEntered(e) { // handle mouse entered event here } ]);
-
Global event handling. The special Zebra event manager keeps track of all events that have occurred for all instantiated UI components. The manager can be utilized to register global event listeners that get all events of the given type. For instance, listening to focus events globally can be done this way:
// instantiate "FocusListener" interface to express that focus // events have to be catched zebra.ui.events.addListener(new FocusListener([ function focusLost(e) { ... }, function focusGained(e) { ... } ]));
-
Catching children components events. The Parent UI component can listen to input events that have happened in its children by implementing the "ChildrenListener" interface and adding appropriate method(s) as follows:
// implements "ChildrenListener" interface to express intention // to get children events var MyComponent = new Class(Panel, ChildrenListener, [ function childInputEvent(e){ // handle children events here } ]);
-
Composite component (event transparent children). Often compound UI components have to prevent their children components from getting any events. Children components are becoming event transparent. This can be done by implementing "Composite" interface and adding the "catchInput" method. The method should return "true" if the passed as argument child has to be event transparent. For instance:
var MyComponent = new Class(Panel, Composite, [ function catchInput(kid){ // return true if the given kid has to be event transparent return true; } ]);
-
Declarative pattern. "First inherit an interface to express an intention to do something and then implement required method(s)". This approach allows Zebra to decouple various functional parts from each other. Extending in this context means adding new declarative patterns instead of overloading Zebra UI classes with new code and APIs. For instance, imagine we need to change the mouse cursor type every time the mouse pointers enter a UI component. Zebra UI components don’t know how to control mouse cursor type. It's the cursor manager ("zebra.ui.cursor") that does it:
// inherits "CursorInfo" interface to let cursor manager know // the component controls mouse cursor var p = new Panel(CursorInfo, [ // declare method that returns mouse cursor type for the component function getCursorType(x, y){ return Cursor.WAIT; } ]);
-
HTML5 Canvas transformation operations can be applied to UI. Rotation, zoom in, zoom out and other graphical transformation effects can be applied to Zebra UI:
// zoom UI in 1.3 times vertically // and horizontally zCanvas.scale(1.3, 1.3); // rotate zCanvas.rotate(0.3); ... // set back to initial stat zCanvas.scale(null); zCanvas.rotate(null, null);
-
Look and feel customization. A lot of Zebra UI components' visual characteristics are defined by a special properties file. If the properties file is not custom enough, a UI Wizard class can be implemented. The instance of the class is notified about all instantiated UI components by calling a "customize" method. It helps to customize UI components' look and feel "on the fly". For instance, let’s define own wizard class to color all label components with red background:
// declare custom Wizard class var MyCustomWizard = Class(Wizard, [ // the method is called every time a new component has been // instantiated function customize(id, comp) { // customize just instantiated label component background if (id == Wizard.LABEL) comp.setBackground(Fill.red); } ]);
Then setup your wizard with the properties file as follow:
... # specify wizard to be used for UI customization wizard = MyCustomWizard()
-
Layered architecture. Zebra Canvas is the root panel that consists of a number of layers. Layers are a standard Zebra UI "Panel" that are stretched over the whole Canvas surface. Zebra holds layers as a stack. Every time an event occurs the Zebra event manager "asks" (starting from top to bottom layer) who wants to take control. The first met layer that grabs control is selected as the target. The picture below explains it:
Let’s develop a layer that freezes (blocks any interaction) and un-freezes UI components by pressing the "CTRL + SHIFT + ALT" keys combination. Freezing is indicated by dimming UI components:
// declare custom layer class that inherits "BaseLayer" class var Freezer = Class(BaseLayer, [ function () { this.$super("FREEZER"); // call super with unique layer ID this.isActive = false; // set background to be 100% transparent this.setBackground(null); }, function layerKeyPressed(code, mask) { var rm = KeyEvent.CTRL + KeyEvent.SHIFT + KeyEvent.ALT; if ((rm & mask) == rm) { // CTRL+SHIFT+ALT keys combination has been pressed if (this.isActive) this.setBackground(null); else this.setBackground(new Fill(255,255,255, 0.7)); this.isActive = ! this.isActive; } }, // methods below indicate if the layer is in active state (take control) function isLayerActive(){ return this.isActive;}, function isLayerActiveAt(x,y){return this.isActive; } ]); ... // add the layer to zebra canvas zCanvas.add(new Freezer());
The result of the layer work is demonstrated below:Un-frozen UI Frozen UI -
Layout management. Layout specifies a rule that says how to shape a number of child components on the given panel. Rule-based positioning is much better than absolute locations and fixed size usage. Gained advantages are the same as "vector vs pixel graphics". Zebra layout managers are independent from the Zebra UI package and can be re-used to layout other objects, for instance HTML elements. Let’s see how, for instance, diagonal layouts can be implemented. The custom layout manager lays out children components aligning its top left corners to diagonal:
// declare diagonal layout manager var DiagonalLayout = Class(Layout, [ // "layout" method positions and shapes visible // children of target component function layout(target) { var x = 0, y = 0; for (var i=0; i<target.kids.length; i++) { var kid = target.kids[i]; if (kid.isVisible) { kid.setLocation(x, y); kid.toPreferredSize(); x += kid.width; y += kid.height; } } }, function calcPreferredSize(target) { ... } ]); // setup DiagonalLayout manager for a panel and add // three children labels var p = new Panel(new DiagonalLayout()); p.add(new Label("Label1")); p.add(new Label("Label2")); p.add(new Label("Label3"));
- Focus, events, paint and other managers. Zebra delegates key functional parts to so-called manager classes.
- Paint manager is called to define painting strategy
- Events manager decides how the given event has to be distributed over UI components and modules.
- Focus manager knows how to switch a focus between components.
All the listed above managers are relatively simple Zebra classes that can be redefined when it is necessary. Developers can implement a new focus manager that introduces a new focus navigation strategy or they can create a more efficient and fast paint manager.
-
Views and Renders as the simplest reusable visual elements "bricks". UI components use many visual elements to build their look and feel such as backgrounds, borders, etc. Zebra declares special classes ("View" and "Render") to be used for the "bricks" implementation. Zebra provides a number of important ready to use views and renders. Take a look at the few snippets below:
var p = new Panel(); // set gray, solid 2px border p.setBorder(new SimpleBorder(SimpleBorder.SOLID, Color.gray, 2)); // set "Gradient" background p.setBackground(new Gradient(Color.gray, Color.black)); // set image as the component background p.setBackground(new ImgRender(“bg.png”));
// set custom diagonal stripped lines background p.setBackground(new View([ function paint(g, x, y, w, h, target) { var s = 8; g.setColor(Color.gray); for(var i=0; i < 2*w/s; i++) { g.drawLine(x+s*i, y, x, y+s*i); } } ]));
Zebra summary
Easy OOP concepts, a rich set of UI components, full control over UI rendering, graphical effects, customizable look and feel, software engineering for the web, Model-View-Controller, free from DOM manipulation, small and fast, multi-platform, and open source. These are the main characteristics that define Zebra.Opinions expressed by DZone contributors are their own.
Comments