Pergola's API allows you to build from the simplest presentation to the most complex web app of systemic type. It is class based and defines an OOD model designed to be used by its classes, internally, and by the developer, in a consistent manner throughout all the develoment phases.
DOM
Pergola does not define any pseudo-language or proprietary representation of the DOM and its methods. Instead it exposes the SVG library (the browser's implementation) directly thanks to its progressive DOM helper (see article svgmagazine.com/jul2011/dom-helper.html) which is at the core of all the building blocks of the Pergola classes as well as the user's objects. This is done through the method pergola.createSVGElement and its sibling method pergola.createHTMLElement, with shortcuts $C and $html respectively. The idea of leveraging directly on the vocabulary and grammar of SVG (and HTML) voids any need to replicate classes that already exist.
The method expects an object literal, or reference, containing that which is expected by the implementation: an element name and a collection of attributes. The required property element and the utility property appendTo are processed. The properties designating attributes are not processed:
var pacman = $C({
element: "circle",
r: 50,
transform: "rotate(160 100 100)",
fill: "none",
stroke: "yellow",
"stroke-width": "100px",
"stroke-dasharray": "150,50",
appendTo: pergola.user
});
The method returns a reference to the newly created element. If the appendTo property is not set and the call is referenced, the element remains available for manipulation at a later stage.
The method is designed to process one other optional utility property: textNode. Its value is a string and it can be set while creating <text> or <tspan> elements. The text node will be automatically created and appended to the element. In HTML it can be used with any element that can have a text node.
Contexts
Pergola runs equally well in a standalone SVG context or in HTML. All the examples are shown in HTML and you will find the same examples 100% SVG in the package. The selection is done in the configuration file bound to each project:
pergola.container = document.getElementById("svg");
pergola.doc = $C({
element: "svg",
width: "600px",
height: "400px",
appendTo: pergola.container
});
This presupposes that an html element with id "svg" exists (you can use other selection methods), or you can define one on the fly:
pergola.container = $html({
element: "div",
style: "width: 600px; height: 400px; overflow: hidden;",
appendTo: document.body
});
In all cases you select a suitable HTML container and then override the property pergola.doc, initially set to document.documentElement. Pergola uses an OOD model where the critical objects of the DOM are referenced, as well as all those that are intended or likely to be manipulated. This allows interactivity and dynamic manipulation without the tedious work of selection and referencing. You will be able to focus entirely and right away on the development of your application.
Classes
One of the core components of Pergola is a comprehensive collection of classes specializing in the production of system objects, widgets and other utilities. Classes are bound to the pergola namespace, and most of them inherit from the Class superclass which defines prototype methods and other generic properties. The classes define prototype extensions, basically specific properties and methods. Classes are broadly divided in two categories, those (the majority) which build interface controls and other graphical constructions, and those which accomplish particular tasks. Some utility classes from the second category are: pergola.Load for loading SVG fragments; pergola.Layout which defines layout methods; pergola.Dragarea, a smart class with several options for which one unique and reusable instance is created; pergola.Timer (see article http://www.svgmagazine.com/nov2011/js_timer.html).
Instantiation of a class in the first category is made using a split method: 1) call to constructor for inheritance; 2) call to the build() method of the class. This technique opens a few opportunities like defining properties in between the two calls (instance methods for example), placing the call to the build() method in a distant file or execute it dynamically, self-referencing of the object with access to its prototype.
The constructor expects one optional argument, a string that is assigned to the property name, useful for visible strings and for debugging, and from which an XML ID is derived and assigned to the property id. If no name is passed, a unique XML name is generated:
var myWindow = new pergola.Button("My Window");
The call to the build() method is consistent with the technique used for the DOM helper, and for all the classes of the first category. The function expects one object, with the difference that the properties become properties of the instance. Here you can override prototype properties and define instance properties for your own usage. To reduce memory footprint Pergola deletes properties that are only necessary for the initial configuration of the object.
var button = new pergola.Button();
button.build({
parent: g,
x: 80,
extra: {
rx: 3,
transform: "rotate(-45)"
},
symbol: {
symbol: pergola.symbols.arrow.large.left,
x: 5,
y: 5
},
quickTip: {tip: "this button is rotated but|its symbol is straight"},
ev: "mouseup",
target: legend.tip6,
fn: legend.hilight
});
The example shows how the framework let's you activate some of the class's features for a particular instance. The documentation contains a quick reference guide of the properties for each class, clearly showing which properties can be overridden, which ones are user define, and which ones relate to the physical structure of the object (referenced DOM nodes). The properties and techniques are reviewed in the example pages.
Some classes are enabled to process the text property. For example you could define it for a button, like this:
text: {
x: 44,
y: 36,
textNode: "TALK",
fill: "none",
stroke: "#FFFFFF",
'stroke-width': 1.5,
'font-family': "'Arial Black'",
'font-size': 22,
'font-weight': "bold",
transform: "scale(1 .5)",
'text-anchor': "middle",
'letter-spacing': 2,
kerning: 0,
'pointer-events': "none"
}
i.e., you define the text element in the usual format (but without the element property). In all cases the text property mutates to become a reference to the newly built text element. On some event you could then say: button.text.firstChild.data = "SILENCE!";.
Most proptotype methods are intended for internal use. Pergola defines hardly any utility functions or Object extensions, leaving that task to specialized libraries which can be used in conjuntion with Pergola. For instance, the mapping application example uses a custom version of Polymaps.
However, some utility functions, all bound to the pergola namespace, are available and more will be added. They count: pergola.mousePoint returning an object with x and y properties set to evt.clientX and evt.clientY, which are subject to the matrixTransform method if Pergola is embedded in a HTML container; pergola.width and pergola.height which use window.getComputedStyle if available or the non standard window.innerWidth and window.innerHeight; pergola.use, a simulation of the SVG <use> element for using shapes from the library; pergola.symbol which is inherently used by the class constructors when you define a library symbol for an object (like a button), and which you can use directly for placing symbols anywhere or for building icons, for example; pergola.message (shortcut $M) which expects a string (can contain the "\n" escape sequence), and an optional object defining x and y properties. It displays the reusable pergola.notification dialog box as an alternative to the system alert. Functions for extensive operations on colors among which: pergola.colorConvert for parsing and conversion of any format to rgb, hex, or array of rgb values depending on the parameters passed; pergola.shade which expects deviation (or neutral) and brightness parameters and returns a computed variant in hex format based on the theme reference color specified in the config file. The algorithm manages well extreme zones of the spectrum; pergola.luminosity which expects three parameters (r, g, b) and returns a Number in the range 0 - 255; pergola.rgbToHex; pergola.hexToRGB;
Libraries
Pergola libraries define essentially SVG objects. A library is an object bound to the pergola namespace. The libraries defined by Pergola are:
pergola.shapes – A shape object defines an SVG element and its geometrical attribues only. No paint attributes or style. Use cases in the buttons example page. Shapes are just defined, they're not appended to the defs. A shape object:
hexagon: {
element: "polygon",
geometry: {
points: "8,27.713 0,13.856 8,0 24,0 32,13.856 24,27.713"
}
}
You can use shapes directly or for those (few) classes that are designed to process shapes (use cases in the buttons example), in which case you can also define a shape on the fly using the same format. To use a shape directly you invoke the utility function pergola.use which simulates the SVG <use> element:
pergola.use({
parent: g,
shape: pergola.shapes.triangle,
attributes: {
transform: "rotate(12 32 27.7128) translate(406 -80) scale(4)",
fill: "lemonchiffon",
stroke: "gray",
"stroke-width": 2,
"stroke-linejoin": "round"
}
});
The function simply formats the object and calls the DOM helper function. It returns the reference to the newly created element. Note the interest of defining only the geometry for shapes, which gives total control over stroke width/scaling.
pergola.symbols – a symbol is a proprietary format, it is not an SVG symbol. It's an array containing one or more SVG element definitions in the DOM helper format. Symbols are just defined, they're not appended to the defs. Use cases in the buttons example page. When "using" a symbol you can override its properties. The utility function pergola.symbol processes and builds the symbol. It returns a <g> containing the symbol's element(s). Symbols can also be used to produce standalone icons, in which case you must invoke the utility function explicitly using the call method. All these operations are well documented. A symbol object:
lens: [
{element: "path", d: "M4.5 3.4l6.5,5", stroke: "#323232", "stroke-width": 1.25, "stroke-linecap": "round"},
{element: "circle", r: 5.75, fill: "#FFFFFF", stroke: "#727272", "stroke-width": 1.5, "fill-opacity": .6}
]
pergola.markers – Marker definitions are functions – properties of the pergola.marker object. The function creates and appends the marker to the defs if it does not exist. It returns the URL string. You assign the call directly to "marker-start", "marker-mid" and "marker-end" properties, or to a variale if the marker is to be reused. A marker definition:
cross : function () {
var id = "cross";
if (!document.getElementById(id)) {
$C({
element : "marker",
id : id,
viewBox : "0 0 12 12",
refX : 6,
refY : 6,
markerUnits : "userSpaceOnUse",
markerWidth : 12,
markerHeight : 12,
appendTo : pergola.defs
})
.appendChild($C({
element : "path",
d : "M12,0L0,12 M0,0L12,12",
fill : "none",
stroke : "red",
"stroke-width" : 1.25
}));
}
return "url(#" + id + ")";
}
$C({
element : "path",
d : "M370.5,425v100",
fill : "none",
stroke : "#D0D0D0",
"marker-start" : pergola.marker.cross(),
appendTo : g
});
pergola.patterns – Patterns are just defined, they are not appended to the defs at runtime. A pattern definition is a function which creates a pattern with a default set of properties. The function returns the URL string. You can assign the call directly to the fill and/or stroke properties of an element, or to a variable for a pattern that you plan to reuse. You can pass parameters to override the predefined values. The pattern gets a unique ID based on the function name + values, and is created and appended to the defs if it does not exist:
var myPattern = pergola.pattern.bar({
colors: ["none", "url(#fade_redGradient)"],
size: 40,
spacing: 0,
transform: "rotate(45)"
});
pergola.filters – Filters are just defined, they are not appended to the defs at runtime. A filter definition is a function. The filter is created if it does not exist. The function returns the URL string. A filter:
dropShadow_1 : function () {
var id = 'dropShadow_1';
if (!document.getElementById(id)) {
var f = $C({element : 'filter', id : id, appendTo : pergola.defs});
$C({element : 'feGaussianBlur', stdDeviation : 2, appendTo : f});
$C({element : 'feOffset', result : 'offsetBlur', dx : 3.2, dy : 3.2, appendTo : f});
$C({element : 'feFlood', 'flood-color' : '#000000', 'flood-opacity' : .4, appendTo : f});
$C({element : 'feComposite', in2 : 'offsetBlur', result : 'shadow', operator : 'in', appendTo : f});
$C({element : 'feComposite', 'in' : 'SourceGraphic', in2 : 'shadow', appendTo : f});
}
return 'url(#' + id + ')';
}
$C({
element : "rect",
x : 100
y : 100,
width : 200,
height : 200,
fill : "gainsboro",
filter :pergola.filter.dropShadow_1(),
appendTo : node
});
pergola.cursors – Cursors are defined and appended to the defs at runtime. For maximum cross-compatibility only .png images are used. A cursor:
$C({
element: "cursor",
id: "lensCursor",
"xlink:href": path + "cursor_lens_empty.png",
x: 6,
y: 6,
appendTo: defs
});
Look & Feel
Pergola has a versatile skin library. A skin is a function, and the name of the skin to use for a project is set as a property of the pergola.settings object in the config file: skin: "rubber". There you also set the base color for the skin: theme: "nürburgring".
The function creates the pergola.presentationAttributes object, where initial values for the prototype properties of all classes are defined. Those prototype properties are then simply references, the values are hardcoded in the skin file, so you don't need to go through the headache of hacking a class. You edit the skin instead. So far then the skin behaves much like a style sheet for SVG presentation attributes. Where it becomes interesting is that you can have multiple definitions of the same classes in one unique file. This means that different projects can share the same sheet. Unlike what happens for style sheets, the new class definitions do not override the previous ones.
To create a new skin you simply copy and paste the default skin and tweak it to your taste and needs. The artist in you will know how to appreciate this low level control. The names of the properties are very explicit, they're simply the same as the classes.
The function also builds gradients and patterns referenced by the skin properties, and any other gradient or pattern that you may need to define for your objects.
gradient = $C({
element: "linearGradient",
id: "sliderPatternGrad",
x1: "0%",
y1: "0%",
x2: "100%",
y2: "100%",
gradientUnits: "objectBoundingBox",
appendTo: defs
});
$C({element: "stop", offset: "0%", "stop-color": shade([100, 100, 100], 20), appendTo: gradient});
$C({element: "stop", offset: "100%", "stop-color": shade([100, 100, 100], -20), appendTo: gradient});
System logic and hierarchy
At runtime Pergola establishes layers hierarchy: pergola.desktop is a reference to the outmost <g> element, created and appended to pergola.doc (document.documentElement); the <g> elements pergola.user and pergola.system are then created and appended to pergola.desktop.
The layer pergola.user is a safe residence for your objects, but this is in no way a requirement, you can append Pergola objects or other elements anywhere in the document, knowing however that they might partially mask some higher ranking layers which carry preemptive system objects (dragarea, dialog and message boxes in particular).
All system classes implement system logic. The system is unobtrusive and transparent, and you keep total freedom over whether to use it or not. If you do use any of its components you have the insurance of proper functioning. For example, The handleEvent method of the Menu class registers a “mousedown” event listener on the pergola.desktop group on activation of any menu instance. A “mousedown” event triggered by any object in the interface will propagate and cause an opened menu to close. The event listener is then removed. Note that an object with systemic behavior doesn't mean that it's necessarily appended to the pergola.system layer, naturally you append your menus anywhere you like.
Some system objects like pergola.dragarea or pergola.colorpicker are instantiated at runtime and you just use them.
Interactivity and Accessibility readiness
User events and functions are implemented in all the low level component classes. They are defined during instantiation by setting the instance properties ev and fn. The property ev gets an array of one or more event types which are all registered with the same handler assigned to fn. The property fn gets multiple types, function/method name, function literal, or string, to cope with a variety of situations. This consistent and powerful feature is well documented and widely used throughout the examples. This, together with the possibility of overriding properties and methods, allows the addition of accessibility extensions by developers with the right know how. They may also find interesting the possibility of defining a global accessibility “skin” in the skin.es file.
Compatibility
Pergola is compatible with all the major SVG implementations: Firefox, Opera, Safari, IE 9, Chrome, ASV *.
*Compatibility with ASV is ensured up to version 1.3.7, except for mapping extensions. It was abandoned definitively from version 1.3.8.