DynAPI 2.9 ChangeLog

Dan Steinman (dan@dansteinman.com)

Contents:

Introduction

DynAPI version 2.9 is my long since due rewrite of the dynapi. This version is a code fork from DynAPI 2.54, so some features and bug fixes in later versions may not be included in this version. This is definately not a drop in replacement for DynAPI 2.5x, a large amount of syntax has changed. The widgets in 2.5x have not yet been converted to this new version. Some things not listed in this document may work different or not be available, so if you have problems refer to the source code and examples provided. Or join the DynAPI Mailing Lists.

File and Object Structure

Due to object reorganizations and the sake of consistancy, the file structure has changed:

DynAPI Object

The master "DynAPI" object is now lowercase, this change is merely for convenience and a duplicate pointer for "DynAPI" is provided for backward compatibility.

The "Methods" object that contained various functions that were needed has been renamed to dynapi.functions. There is a new function called dynapi.functions.getURLArguments(). It will return the arguments passed in a URL, and can be used in 3 ways:

dynapi.functions.getURLArgument(urlString);   // full filename
dynapi.functions.getURLArgument(iframeObject);  // IE/Moz only
dynapi.functions.getURLArgument(layerObject);  // NS4 only

If your file is loaded as "file.html?iam=dynapi&hello=world" and you can find the values easily with:

var args = dynapi.functions.getURLArguments('file.html?iam=dynapi&hello=world');
alert(args['iam']);  // returns "dynapi"
alert(args['hello']);  // returns "world"

A default dynapi.documentArgs property is included for the document that DynAPI is loaded into.

There are also some functions that will be of use in special cases:

dynapi.functions.True();   // returns true
dynapi.functions.False();   // returns false
dynapi.functions.Null();   // returns null

These should be used whenever you need to assign a blank function:

MyObject.prototype.mymethod = dynapi.functions.Null;
this.elm.onmousedown = dynapi.funtions.False;

DynObject

Most duties of DynObject have been delegated to a subobject DynElement in /api/event.js. DynObject no longer contains any child/parent or event listener methods. It now has only these methods:

String getClassName()  // returns the className
Function getClass()  // returns constructor (class) of the object
boolean isClass(sClassName) // returns true if the object is inherited from, or of the type sClassName
void addMethod(sMethodName, function)
void removeMethod(sMethodName)
void setID(id)
String toString() // returns [ClassName]
void inherit()

These methods create a standard class structure to all objects within the DynAPI. It is used in tandem with a function dynapi.setPrototype() to assign the class name and the parent class name. To extend DynObject do the following:

function MyObject() {
	this.inherit('DynObject');  // optional, you can also do:
	// this.DynObject = DynObject;
	// this.DynObject();
}
var p = dynapi.setPrototype('MyObject','DynObject');  // returns the prototype object
p.methodName = function() {};

Some examples of how to use the new methods:

m = new MyObject();
m.addMethod('method2', function() {
	alert('method2');
});
m.removeMethod('method2');

alert(m.isClass('DynObject'));  // returns true
alert(m.getClass());  // returns MyObject

// a private property that may be of use:
alert(m._pClassName);  //returns "DynObject"

dynapi.onLoad works differently now. Instead of using it as the master loading point you now must use it as a method to add "load" functions to the browser onLoad sequence:

dynapi.onLoad(init);
function init() {
	alert('loaded');
}

This replaces the previous DynAPI.addLoadFunction(function() {}) command. If you have a DynAPI.onLoad = function(){} assigned in your page you will get errors. Similarly, there is a dynapi.onUnload(function() {}) which you can add code that is performed when leaving the page. There is some IE garbage collection that is done at onUnload, it is not complete though. When complete DynAPI will (hopefully) not leak memory like it does currently.

DynAPI Library System

I have replaced the previous DynAPI.include() system with a more flexible name-based library system. It can be used to include scripts by name, and automatically handles depencies. There is an optional dynamic loader extension explained below which allows you to load up objects dynamically. The library system is created as an object called dynapi.library.

The way it works is you first add an object to the library, and assign which objects must be loaded before it.

dynapi.library.setPath('path');  // path to dynapi library (eg "dynapi/src/lib");
dynapi.library.add('objectName', '../file.js', 'dependentObject');

In that case, dependentObject will always be loaded/included before objectName.

You can also assign groups of objects by assigning a package:

dynapi.library.addPackage('packageName', 'packagePath');

// to add an object to a package, include the package name in front
// the source then becomes relative to the packagePath
dynapi.library.add('packageName.objectName','src');

Here's a full example:

dynapi.library.setPath('/dynapi/src/lib/');
dynapi.library.addPackage('mypackage','/my/package/path/');
dynapi.library.add('mypackageName.DependentObject','depobject.js');
dynapi.library.add('mypackageName.MyObject','sourcefile.js','DependentObject');

When it comes time to include the files in the page you can do it by calling dynapi.library.include('objectName') or dynapi.library.include('packageName.objectName')

dynapi.library.include('mypackage');  // includes all objects in that package (DependentObject, MyObject)
dynapi.library.include('MyObject');  // automatically includes DependentObject beforehand
dynapi.library.include('mypackage.MyObject');  // same as above

Default library associations are already made for the files within DynAPI. You only have to set dynapi.libary.setPath() (which replaces DynAPI.setLibraryPath) in order include the files by name.

You will see in the example scripts the following script commands:

<script language="JavaScript" src="../src/dynapi.js"></script>
<script language="Javascript">
dynapi.library.setPath('../src/lib/');
// includes...
</script>

The full list of possible object names for DynAPI objects is:

dynapi.library.include('dynapi');  // includes debug, library, functions
dynapi.library.include('dynapi.debug');
dynapi.library.include('dynapi.functions');
dynapi.library.include('dynapi.library');

dynapi.library.include('dynapi.api');  // includes all dynapi.api objects
dynapi.library.include('dynapi.api.DynEvent');
dynapi.library.include('dynapi.api.EventObject');
dynapi.library.include('dynapi.api.DynElement');
dynapi.library.include('dynapi.api.DynDocument');
dynapi.library.include('dynapi.api.DynLayer');
dynapi.library.include('dynapi.api.MouseEvent');

dynapi.library.include('dynapi.api.ext');  // includes all api extentions
dynapi.library.include('dynapi.api.ext.DragEvent');
dynapi.library.include('dynapi.api.ext.DynKeyEvent');

dynapi.library.include('dynapi.fx');  // includes all dynapi.fx objects
dynapi.library.include('dynapi.fx.Thread');
dynapi.library.include('dynapi.fx.PathAnimation');
dynapi.library.include('dynapi.fx.GlideAnimation');
dynapi.library.include('dynapi.fx.SlideAnimation');
dynapi.library.include('dynapi.fx.ImageAnimation');

dynapi.library.include('dynapi.gui');  // includes all dynapi.gui objects
dynapi.library.include('dynapi.gui.DynImage');
dynapi.library.include('dynapi.gui.Graphics');

dynapi.library.include('dynapi.util');  // includes all dynapi.util objects
dynapi.library.include('dynapi.util.Cookie');
dynapi.library.include('dynapi.util.IOElement');
dynapi.library.include('dynapi.util.LoadPanel');

Any of those objects can be included without their package names defined, eg. dynapi.library.include('MouseEvent').

Dynamic Library Loading System

The library loading extention to dynapi.library allows you to load scripts on the fly. When you include 'dynapi.library' in your page you are including the dynamic load system which will add these methods:

dynapi.library.load('objectName', handler);
dynapi.library.reload('objectName', handler);
dynapi.library.loadScript('file.js', handler);

When you perform a load() the script will be inserted into the page on the fly, and then the function passed as the handler will be called.

The DynAPI Debugger

When dynapi.debug is included or loaded with the library system it will open the Debugger window. This is a handly tool to help in the debugging process. It captures the browser syntax errors and routes them to it's print output. You can use this output for your own debugging purposes to find logic errors by calling:

dynapi.debug.print('my error string');

The Status field is meant for debugging values that change rapidly, such as for handling mousemove events. It is updated with dynapi.debug.status('error string').

The Evaluate field can be used for real-time debugging of your code. Enter javascript code and it will be evaluated in the scope of the frame that opened it (presumably the document where all your objects are located). You could use it to test creation of DynLayers, resizing them, and method testing.

Events and EventListeners

The EventListener object has been deprecated. Replace all instances with a blank object like so:

var eventlistener = {
	onmousedown : function(e) {
		
	},
	onmouseup : function(e) {
		
	}
}

With this change also comes the removal of the DynEvent.getTarget() method (it was the only reason for having an EventListener object). For events that are created by the widget DynLayer itself, you can merely replace instances of e.getTarget() with e.getSource. But for events that are attached to it's children (eg. an object that listens to a mousedown on a child layer) you can replace e.getTarget() in 2 possible ways.

1) Use e.getSource().parent. This works only when the DynLayer receiving the event is a direct child of the widget object.

2) If you need to attach an event to a sub-child (child of a child), and need a pointer back to the widget you can make a variable which points to "this":

function MyWidget() {
	this.DynLayer = DynLayer;
	this.Dynlayer();
	
	this.pdlyr = new Dynlayer();
	this.dlyr = this.pdlyr.addChild(new DynLayer());
	this.dlyr.widget = this;
	this.dlyr.addEventListener(MyWidget.childEvents);
}
MyWidget.childEvents = {
	oncreate : function(e) {
		e.getSource().widget.update();
	}
};
MyWidget.prototype = new DynLayer;
MyWidget.prototype.update = function() {};

Optionally, you can create the event listener in the constructor and use a local variable:

function MyWidget() {
	this.DynLayer = DynLayer;
	this.Dynlayer();
	
	this.pdlyr = new Dynlayer();
	this.dlyr = this.pdlyr.addChild(new DynLayer());
	
	var widget = this;
	
	var el = {
		oncreate : function(e) {
			widget.update();
		}
	}
	this.dlyr.addEventListener(el);
}
MyWidget.prototype = new DynLayer;
MyWidget.prototype.update = function() {};

The need to include event.js before dyndocument and dynlayer comes from the fact that both objects inherit from a common DynElement object. A new EventObject handles event listeners. You can now create an object that does nothing but listens/invokes events if you inhert from EventObject. DynElement object adds the addChild/removeChild methods that was previously a part of DynObject.

There are 2 new Event methods which are of value when using complex bubbling:

e.getOrigin() - returns the DynObject that first recieved the event

e.getBubbleChild() - returns the child DynLayer that the event bubbled from. This is not the same as getOrigin. For example, when you mousedown on DynLayerB which is a child of DynLayerA, DynLayerB will recieve a mousedown event, bubble up to DynLayerA, and bubble again to dynapi.document. In a listener attached to DynDocument, e.getSource() will return dynapi.document, e.getBubbleChild() will return DynLayerB, and e.getOrigin() will return DynLayerA.

Any object that inherits from EventObject (DynLayer) will use itself as an event listener if an event is invoked and such an event handler is defined:

dlyr = new DynLayer(...);
dlyr.onmousedown = function(e) {
   // code
};

This is meant as a convenience when implementing the objects. You should never attach events within a widget in this manner.

DynLayer

Syntax Changes

These changes weren't altogether necessary except for making things consistant.

DynLayer() Constructor
There were too many arguments getting into the constructor. I'm now restricting it to the following:

new DynLayer(html, x, y, width, height, bgColor)

The first argument used to be the ID. In practice its extremely rare that you need to set any ID's manually. Setting the HTML content is very common, and so it is now the first argument. This won't lead to much problems if you have a lot of code already created using previous versions because they'll probably set the first argument to null or "" anyway.

setLocation(x,y) - replaces moveTo

moveBy() - has been removed, you can perform this operation manually where needed

setCursor("cursor-type") - to set the IE cursor for the div ("hand", "stop", etc). Ignored by Netscape 4/Mozilla. This is also available to DynDocument.

setZIndex({above:[DynLayer]}) - sets zIndex one above the given DynLayer object
setZIndex({below:[DynLayer]}) - sets zIndex one below the given DynLayer object
setZIndex({topmost:true}) - sets zIndex above all other sibling layers

setPageLocation(x,y) - performs both setPageX() and setPageY()

There is now a built-in slideTo() method in DynLayer that is compatible with PathAnimation

BeforeLoad Creation

The dynapi.document object is now always present instead of being created after onLoad. This change was necessary in order to allow adding DynLayers to the document before the page loads.

You can now create DynLayer objects and insert their layer/div elements at any time. In order to insert the elements before load you must call dynapi.document.insertAllChildren() inside the body:

<script>
var dlyr = new DynLayer(...);
dynapi.document.addChild(d);
</script>

<body>

<script>
dynapi.document.insertAllChildren();
</script>

</body>

This will do one massive document.write() inserting all the layer/div tags at once and makes a large impact on how the layers are rendered. If you only add DynLayers to the document and do not call dynapi.document.insertAllChildren() the layer elements will be created after onLoad, but before any dynapi.document.onLoad handlers are called.

If you want to insert a single DynLayer object into the page instead call dynapi.document.insertChild(dlyr).

Create Event

The "create" event that DynLayer invokes works notably different now. Previously children DynLayers called the create event before their parents did. At the time we made this decision it made sense, but since we're moving ahead now I've put this back to what it should be. This does impact widget creation in some manners. In cases where a child layer is resized to it's content size during the create sequence this information is no longer available to the parent DynLayer (where previously it was). If your parent DynLayer/widget needs to know the size of its children a recommended workaround is to add a "precreate" event listener which attaches a "create" event to the last child for use in the widget.

function MyWidget() {
	this.DynLayer = DynLayer;
	this.DynLayer();
	
	this.dlyr = new DynLayer('hi');
	
	var el = {
		oncreate : function(e) {
			e.getSource().parent.update();
		}
	}
	this.dlyr.addEventListener(el);
}
MyWidget.prototype = new DynLayer;
MyWidget.prototype.update = function() {
	alert('child done');
}

Resize Event

I've decided to scrap the use of a "resize" event to handle resizing of widgets, it made things unnecessarily cumbersome. When you perform setSize(), setWidth(), or setHeight() no event will be called. For widgets this means if you need to build in resizing functionality of your own you have to overwrite the setSize method:

MyWidget.prototype._DynLayer_setSize = DynLayer.prototype.setSize;
MyWidget.prototype.setSize = function(w,h) {
	var r = this._DynLayer._setSize(w,h);  // returns true if the layer has changed size
	if (r) {
		// resize children
	}
}

Anchoring

You may not need to overwrite the setSize() method because there is a built-in anchoring system in DynLayer that will automatically handle many common resizing tasks. Anchoring provides an automatic way to align, stretch, or center layer/divs in an absolute position environment. It calculates the positions based on it's parent's size, and updates the dimensions when the parent resizes. It does not update the anchor position when the layer itself is changed though.

It all works through a single setAnchor() method that you pass an "anchor" property object. Depending on what you pass, and what the width/height of the layer is will determine what happens to size and location of the layer.

setAnchor(anchor)

The anchor object can have a combination of the following properties:

Aligning

Aligning a layer to it's parent's edges is a common task. If you assign one of left or right, and one of top or bottom anchor properties then it will align the layer accordingly. The setAnchor() method should only be called once, you can pass multiple properties if you need.

Given these 2 layers:

var A = new DynLayer('', 10, 10, 100, 100, 'red');
var B = new DynLayer('', 10, 10, 50, 50, 'blue');

// anchor commands go here

A.addChild(B);
dynapi.document.addChild(A);

You could do one of the following anchor commands:

B.setAnchor({top:0});   // top align, essentially do the same as setY(0)
B.setAnchor({left:0});   // left align, essentially do the same as setX(0)

B.setAnchor({bottom:0});   // bottom align, will set the Y position so that the bottom edge of this layer is always at the bottom edget of it's parent
B.setAnchor({right:0});   // right align, will set the X position so that the right edge of this layer is always at the right edget of it's parent

B.setAnchor({top:10});   // same as setY(10)
B.setAnchor({top:-10});   // same as setY(-10)

B.setAnchor({bottom:10});   // bottom align, but offset 10px from the bottom edge
B.setAnchor({bottom:-10});   // bottom align, but overlapping by 10px

B.setAnchor({left:10});   // same as setX(10)
B.setAnchor({left:-10});   // same as setX(-10)

B.setAnchor({right:10});   // right aligned, but offset 10px from the right edge
B.setAnchor({right:-10});   // right aligned, but overlapping by 10px

B.setAnchor({top:0,left:0});   // top/left align
B.setAnchor({top:0,right:0});   // top/right align
B.setAnchor({bottom:0,left:0});   // bottom/left align
B.setAnchor({bottom:0,right:0});   // bottom/left align
Stretching

Stretching works the same way except you only have to set both the left and right, or both the top and bottom anchor values. This will overwrite any width or height you have assigned to the DynLayer.

B.setAnchor({top:0,bottom:0});   // stretch vertically - performs setY(0), setHeight(parent.h) automatically
B.setAnchor({left:0,right:0});   // stretch horizontally

B.setAnchor({top:0,bottom:0; right:10});   // stretch vertically, right aligned 10px from the right edge
B.setAnchor({left:0,right:0, bottom:0});   // stretch horizontally, bottom aligned

B.setAnchor({left:0,right:0,top:0,bottom:0});   // stretch both vertically and horizontally - similar to doing width=100% and height=100% in a relative position environment

B.setAnchor({left:0,right:0,top:20,bottom:0});  // leaves 20 pixels at the top
Centering

Centering is mutually exclusive to the align/stretch features. If you horizontally center you can only vertically align/stretch, and vica-versa.

B.setAnchor({centerV:0}) // vertically center
B.setAnchor({centerH:0}) // horizontally center

B.setAnchor({centerV:10}) // vertically center, but offset 10px down
B.setAnchor({centerV:-10}) // vertically center, but offset 10px up
B.setAnchor({centerH:10}) // horizontally center, but offset 10px right
B.setAnchor({centerH:-10}) // horizontally center, but offset 10px left

B.setAnchor({centerV:0,left:0,right:0}) // vertically center, stretch horizontally
B.setAnchor({centerH:0,top:0,bottom:0}) // horizontally center, stretch vertically

B.setAnchor({centerV:0,right:0}) // vertically center, right align
B.setAnchor({centerH:0,bottom:0}) // horizontally center, bottom align

B.setAnchor({centerV:0,top:0,bottom:0}) // center overrides stretch/align

As mentioned these values will hold true when the DynLayer's parent resizes. So if after these layers are created DynLayer A is resized, the anchored position will be updated automatically. However if the need arises where the DynLayer itself (DynLayer B in this case) is resized you will need to manually call the DynLayer.updateAnchor() method.

DragEvent

DragEvent has a new option in DragEvent.setBoundary(), there are 3 possible uses:

DragEvent.setDragBoundary(dlyr);  // boundary is it's parent;

DragEvent.setDragBoundary(dlyr, {left:10,right:10,top:10,bottom:10});  // boundary is it's parent with 10pixel inset on all edges, this changes with it's parent

DragEvent.setDragBoundary(dlyr, top, right, bottom, left);  // static boundary, same as dynapi 2.5x

Path, Slide, and Glide Animation

As meantioned, DynLayer now includes a slideTo() method that is compatible with PathAnimation's slideTo. So any widgets that require slide animation do not require Path and Thread anymore.

PathAnimation used to only invoke a "pathstop" event. This was undescernable between when the path finishes on it's on, and when it was cancelled manually. At one point I need to know the difference and so I have differentiated the two. So instead of calling "pathstop", it now calls "pathfinish" and "pathcancel".

The Slide animation that is based on PathAnimation is a separate file which can be included by dynapi.library.include('SlideAnimation'). But these no longer add a prototype onto DynLayer. The slideTo, and slideStop method (used to be named stopSlide) must now be added manually to any objects that require it:

dlyr.setMethod("slideTo",PathAnimation.slideTo);  // will overwrite the built in DynLayer.slideTo()
dlyr.setMethod("slideStop",PathAnimation.slideStop);
dlyr.slideTo(x,y,5,10);
dlyr.slideStop();

The GlideAnimation code from DynAPI1 has now been ported to a PathAnimation extension. Use dynapi.library.include('GlideAnimation'), and similarly add the methods manually:

dlyr.setMethod("glideTo",PathAnimation.glideTo);
dlyr.setMethod("glideStop",PathAnimation.glideStop);
dlyr.slideTo(x,y,5,10);
dlyr.slideStop();