Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Branch: master
Fetching contributors…

Cannot retrieve contributors at this time

687 lines (648 sloc) 29.652 kB
<html>
<head>
<title>Carena</title>
<script type="text/javascript" src="lib/carena.js"></script>
<style>
body {
font-family: helvetica, arial;
font-size: 0.85em;
width: 40em;
margin: 0 auto;
}
pre.data {
border:1px solid orange;
background: black;
color: lightgreen;
}
em {
font-size:0.86em;
}
</style>
</head>
<body>
<h1>Carena</h1>
<p>
A lightweight scene-tree for use with html5's canvas.
<a href="http://github.com/tmpvar/carena">fork me!</a>
</p>
<h2>Core Features</h2>
<p>
Carena's core feature-set is currently very minimal, providing only a small set methods:
<ul>
<li><em>carena</em>.<strong>addFeature(name, fn)</strong>
<p>
Registers <em>fn</em> with carena's feature loader
</p>
</li>
<li><em>carena</em>.<strong>require(name, arguments)</strong>
<p>
This is generally used inside of a feature to make sure that the incoming object has all of the required features.
<br /><br />
Example:
</p>
<pre>
carena.addFeature("some.feature", function(obj, options, storage) {
// some.feature requires obj, to have carena.Node features
carena.require("carena.Node", arguments);
});</pre>
</li>
<li><em>carena</em>.<strong>applyProperties(obj, props)</strong>
<p>
All properties from the <em>props</em> argument are copied onto the <em>obj</em>, allowing for the copying of enumerable getters/setters.
</p>
</li>
<li><em>carena</em>.<strong>erect(structure, features)</strong>
<p>
builds a tree with a common set of <em>features</em>. (Still under r&amp;d)
</p>
</li>
<li><em>carena</em>.<strong>build(obj, features, options)</strong>
<p>
builds an object to your specifications. <em>obj</em> is your original object that you want to build on top of. <em>features</em> is an array of carena enabled features (for the base list see the Dynamic Features section below). <em>options</em> is simply an object that gets passed to every feature that is used to build your new object.
</p>
</li>
<li><em>carena</em>.<strong>design(obj, features, options)</strong>
<p>
Use carena.design for building a factory that builds nodes that share the same features. The signature is exactly the same as <em>carena</em>.<strong>build</strong>, except for the fact that it returns a function that also has the same signature, and behaves exactly like <em>carena</em>.<strong>build</strong>.
</p>
</li>
<li><em>carena</em>.<strong>commonAncestor(obj1, obj2)</strong> (Will move soon!)
<p>
This method finds the common ancestor between two nodes. Example
<pre>
[a]
/ \
[b] [c]
/ \
[d] [e]
</pre>
The common ancestor of <strong>d</strong> and <strong>e</strong> in the previous example is <strong>c</strong>.
</p>
</li>
</ul>
</p>
<h2>Dynamic Features</h2>
<p>
Dynamic features are used to augment the features of an existing object.
<br />
Utilize the <em>carena</em>.<strong>build</strong> method to augment objects with new features; this is generally done by calling <em>carena</em>.<strong>build(obj, [carena.feature.{feature1}, carena.feature.{feature2}])</strong>. This will return you your original <em>obj</em> augmented with <strong>feature1</strong> and <strong>feature2</strong><br />
<br />
Built objects contain a <em>node</em>.<strong>dehydrate</strong>() method, which makes it really simple to serialize the tree.<br />
<br />
Features are functions that take 3 arguments:
<ul>
<li><em>obj</em> object on which the feature will hang methods/properties.</li>
<li><em>options</em></li>
<li><em>storage</em> shared object across features of a <em>node</em>, which is used by dehydrate to provide a lightweight representation of the <em>node</em>'s state.</li>
</ul>
</p>
<h3>Node</h3>
<p>
The Node is the carena's core feature which allows nodes to positioned, styled, organized into trees, and traversed.
</p>
<h4>Getters / Setters</h4>
<p>
<ul>
<li><em>node</em>.<strong>x</strong>
<p>
on <em>read</em>: returns the current value<br />
on <em>write</em>: This node and parents are marked dirty. If this node is Eventable, a "node.x" event is triggered:
<pre>
{
node : <em>node</em>,
current : `node.x`,
previous : `previous node.x`
}</pre>
with data.previous set to the old x value, and node.current set to the current value.
</p>
</li>
<li><em>node</em>.<strong>y</strong>
<p>
on <em>read</em>: returns the current value<br />
on <em>write</em>: <em>node</em> and parents are marked dirty. If this node is Eventable, a "node.y" event is triggered:
<pre>
{
node : <em>node</em>,
current : `current node.y`,
previous : `original node.y`
}</pre>
</p>
</li>
<li><em>node</em>.<strong>z</strong>
<p>
on <em>read</em>: returns the current value<br />
on <em>write</em>: This node and parents are marked dirty. If this node is Eventable, a "node.z" event is triggered:
<pre>
{
node : <em>node</em>,
current : `current node.z`,
previous : `original node.z`
}</pre>
</p>
</li>
<li><em>node</em>.<strong>width</strong>
<p>
on <em>read</em>: returns the current value<br />
on <em>write</em>: This node and parents are marked dirty. If this node is Eventable, a "node.width" event is triggered:
<pre>
{
node : <em>node</em>,
current : `current node.width`,
previous : `original node.width`
}</pre>
</p>
</li>
<li><em>node</em>.<strong>height</strong>
<p>
on <em>read</em>: returns the current value<br />
on <em>write</em>: This node and parents are marked dirty. If this node is Eventable, a "node.height" event is triggered:
<pre>
{
node : <em>node</em>,
current : `current node.height`,
previous : `original node.height`
}</pre>
</p>
</li>
<li><em>node</em>.<strong>children (read only)</strong>
<p>
returns the children of node (array)
</p>
<li><em>node</em>.<strong>dirty</strong>
<p>
Dirtyness is a way for node's caches to be re-calculated. For instance, if a node hasn't changed since the last render then there is no reason to re-render the node, as it would simply waste cycles.<br /><br />
on <em>read</em>: returns true or false<br />
on <em>write</em>: a "node.dirty" event will be triggered:
<pre>
{
node : <em>node</em>,
current : `node.parent`,
previous : `previous node.parent`
}</pre>
If set to true, and the current node is currently clean, this node and all parent nodes will be marked dirty.
</p>
</li>
<li><em>node</em>.<strong>parent</strong>
<p>
on <em>read</em>: return the current node's parent or null<br />
on <em>write</em>: This node and parents are marked dirty. If this node is Eventable, a "node.parent" event is triggered:
<pre>
{
node : <em>node</em>,
current : `node.parent`,
previous : `previous node.parent`
}</pre>
</p>
</li>
<li><em>node</em>.<strong>bounds (read only)</strong>
<p>
If the <em>node</em> is dirty, the bounding box of this node will be computed by descending into it's children (recursively) to find the biggest bounding rectangle that fits all of its children.
<br /><br />
<strong>NOTE:</strong> at the time of this writing, re-calculating the bounds also marks the node clean (node.dirty = false).
</p>
</li>
</ul>
</p>
<h4>Methods</h4>
<p>
<ul>
<li>
<em>node</em>.<strong>add(child)</strong>
<p>
Adds <em>child</em> to the current node's children array, reparents the <em>child</em>, and triggers "node.child" event:
<pre>
{
node : <em>node</em>,
child : <em>child</em>
}</pre>
<strong>returns</strong> <em>node</em> or <em>false</em> if a recursive structure is detected.
</p>
</li>
<li>
<em>node</em>.<strong>remove(child)</strong>
<p>
If <em>child</em> is a child of <em>node</em> then it removes the child and triggers a "node.remove" event:
<pre>
{
node : <em>node</em>,
child : <em>child</em>
}</pre>
<strong>returns</strong> <em>node</em>
</p>
</li>
<li><em>node</em>.<strong>walk</strong>(<em>map</em>, <em>callback</em> [, <em>depth</em>])</strong>
<p>
Generic tree walking utility. <em>map</em> resolve nodes that need to be traversed, every new node is passed as a parameter into <em>callback</em> along with a <em>walker</em> object. The <em>walker</em> object tracks the depth and will stop map from going past the level specified (if any).<br /><br />
<em>map</em>(<em>obj</em>, <em>fn</em>) provides a generic means for iterating through object properties. The <em>map</em> function takes two parameters, an object and the callback passed into walk.<br /><br />
<em>callback</em>(<em>obj</em>, <em>walker</em>) called on every node that map encounters. <em>walker</em> is an object that allows you to cancel the entire traversal by calling <em>walker</em>.<strong>stop()</strong>. Returning <em>false</em> from the <em>callback</em> will cancel the traversal down the current branch.<br /><br />
<em>depth</em> Optional; if specified it forces map to only recurse <em>depth</em> times. If not specified, <strong>walk</strong> will recurse until all <em>map</em>s on the traversed nodes are exhausted.<br /><br />
<strong>Example:</strong>
<pre>
function ascend(obj, fn, depth) {
return obj.walk(function(node, callback) {
if (node && node.parent) {
callback(node.parent);
}
},fn, depth);
};</pre>
This will traverse up the tree from obj, to the root calling <em>fn</em> every time it encounters a new node.<br /><br />
<strong>return</strong> <em>walker</em>
</p>
</li>
<li>
<em>node</em>.<strong>ascend</strong>(<em>fn</em>, <em>depth</em>)
<p>
Uses <em>node</em>.<strong>walk</strong> to walk up the tree by using <em>node</em>.<strong>parent</strong> while passing nodes it encounters back to <em>fn</em>.<br /><br />
<strong>return</strong> walker (see: <em>node</em>.<strong>walk</strong>)
</p>
</li>
<li>
<em>node</em>.<strong>decend</strong>(<em>fn</em>, <em>depth</em>)
<p>
Uses <em>node</em>.<strong>walk</strong> to walk down the tree by using <em>node</em>.<strong>children</strong> while passing nodes it encounters back to <em>fn</em>.<br /><br />
<strong>return</strong> walker (see: <em>node</em>.<strong>walk</strong>)
</p>
</li>
<li>
<em>node</em>.<strong>child</strong>(<em>idx</em>)
<p>
<strong>return</strong> child at idx, or <em>null</em>
</p>
</li>
<li>
<em>node</em>.<strong>containsPoint</strong>(<em>x</em>, <em>y</em> [, <em>bounding</em>])
<p>
Using <em>node</em>.<strong>bounds</strong> (if <em>bounding</em> is <em>true</em>) or <em>node</em>.<strong>x</strong> and <em>node</em>.<strong>y</strong> to calculate whether a point lies in the area occupied by <em>node</em>.
<br /><br />
<strong>return</strong> <em>true</em> if found or <em>false</em> otherwise
</p>
</li>
<li>
<em>node</em>.<strong>clean</strong>()
<p>
Marks <em>node</em>.<strong>dirty</strong> false.<br />
<br />
<strong>return</strong> <em>node</em>
</p>
<li>
<em>node</em>.<strong>unshift</strong>(<em>child</em>)
<p>
Inserts <em>child</em> at the begining of the <em>node</em>.<strong>children</strong> array. <br /><br />
<strong>return</strong> <em>node</em>
</p>
</li>
<li>
<em>node</em>.<strong>childIndex</strong>(<em>child</em>)
<p>
<strong>return</strong> the numeric index of the <em>child</em> or -1 if not found.
</p>
</li>
<li>
<em>node</em>.<strong>nodesByPoint</strong>(<em>x</em>, <em>y</em>)
<p>
Uses <em>node</em>.<strong>walk</strong> to walk down the tree looking for nodes that contain <em>x</em> and <em>y</em>.<br /><br />
<strong>return</strong> an array of nodes that contain <em>x</em> and <em>y</em> (does not use <em>node</em>.<strong>bounds</strong>)
</p>
</li>
</ul>
</p>
<h3>Eventable</h3>
<p>
The Eventable feature provides a means for objects to communicate via events.<br />
<h4>Event Flow</h4>
<p>
All events that are triggered are handled first by the current node's bound event handlers, and then the event flows upward towards the root of the scene in a "bubbling" fashion. Bubbling is typically achieved by calling trigger on a parent node, if the parent node is Eventable.<br /><br />
An event handler (<strong>fn</strong> parameter to the <em>node</em>.<strong>event.bind</strong> event below) that returns false will cause the "bubbling" to stop immediately. No other event handlers will be called.
</p>
<h4>Feature Overview</h4>
Eventable is attached to objects by way of a namespace property "event". All methods useful to event capabilities are nested under the <strong>event</strong> namespace.
</p>
<p>
Many events are namespaced for organization's sake. Examples of namespaced events are "node.x", "node.parent", "drag.start", etc..
<br /><br />
All functions exposed by the Eventable feature take a <strong>name</strong> parameter, which may contain a 'namespace.*' for applying the operation to the entire namespace and any children namespaces (recursively).
</p>
<p>
Eventable adds the following capabilities to an object:
<ul>
<li><em>node</em>.<strong>event.bind(name, fn)</strong>
<p>
<strong>name</strong> is the name of the event that you would like to capture. Event names may contain .*'s for capturing entire namespaces of events at a time (i.e: node.* will capture every node event).<br /><br />
<strong>fn</strong> is a callback function that takes two arguments: <strong>name</strong> and <strong>data</strong>. <strong>name</strong> is the actual name of the event, and data is an object of important information regarding that event.<br /><br />
<strong>NOTE:</strong> returning false from the <strong>fn</strong> callback will cause the current event to stop bubbling.
</p>
</li>
<li>
<em>node</em>.<strong>event.unbind(name, [ fn ])</strong>
<p>
Use this to unbind events from a node. <strong>fn</strong> is optional, and not providing it will remove all callbacks associated with the <strong>name</strong>.
</p>
</li>
<li>
<em>node</em>.<strong>event.trigger(name, data)</strong>
<p>
Emits an event on <em>node</em>. <strong>data</strong> is an that is sent along with the event. If none of the event handlers on <em>node</em> cancel the event (return <em>false</em>), then the same event is triggered on the parent of <em>node</em> (if possible).<br /><br />
<strong>return</strong> <em>false</em> if the event was handled or cancelled
</p.>
</li>
</ul>
</p>
<p>
When binding or triggering events, you may trigger by the entire name or by namespace. For example, If you bound to "node.*" the following events would be caught:
<ul>
<li>node.x</li>
<li>node.y</li>
<li>node.arbitrary.namespace.event</li>
</ul>
</p>
<h3>Renderer</h3>
<p>
The Renderer feature is used to hold an instance of the rendering context, manage when and how a tree is partially re-rendered, and when to clear the context and start rendering from a blank context.
</p>
<p>
<strong>NOTE:</strong> Renderer requires you provide an <em>options</em>.<strong>canvas</strong>
object which implements getContext("2d"), and <em>options.canvas</em>.<strong>getContext</strong>() returns
an object compatible with the html5 canvas element's
<a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#2dcontext">2d context</a>.
</p>
<h4>Getters / Setters</h4>
<p>
<ul>
<li>
<em>node</em>.<strong>canvas</strong> (read only)
<p>
on <em>read</em> returns the DOM Canvas element
</p>
</li>
<li>
<em>node</em>.<strong>context</strong> (read only)
<p>
on <em>read</em> returns the raw 2d context object
</p>
</li>
<li>
<em>node</em>.<strong>clearColor</strong><br />
This is the background color when nothing is rendered, it defaults to black
<p>
on <em>read</em> return the current clear color<br />
on <em>write</em> mark the renderer dirty, which will trigger a complete re-draw on the next render pass.
</p>
</li>
</ul>
</p>
<h4>Methods</h4>
<p>
<ul>
<li>
<em>node</em>.<strong>renderTree</strong>(<em>root</em>)
<p>
When called the <em>root</em>.<strong>walk</strong> method is invoked in
a way that will climb down into <em>root</em>'s children recursively,
calling <em>node</em>.<strong>render</strong> on each one.
</p>
<p>
Dirty nodes will not be rendered infact, because of the way dirty works,
entire branches of the tree are skipped if their root is marked dirty.<br /><br />
<strong>return</strong> <em>walker</em> (see: <em>node</em>.<strong>walk</strong>)
</p>
</li>
<li>
<em>node</em>.<strong>render</strong>(<em>root</em>)
<p>
This method clears the context to the
<em>node</em>.<strong>clearColor</strong>
<!-- if <em>node</em>.<strong>dirty</strong> is true -->.<br /><br />
<em>root</em> is then passed into <em>node</em>.<strong>renderTree</strong>for rendering of the rest of the target tree.<br /><br />
<strong>return</strong> <em>node</em>
</p>
</li>
</ul>
</p>
<h3>Camera</h3>
<p>
The job of this feature is to provide a way to view branches of the scene tree
using the <em>Renderer</em> feature. The camera is attached to a <em>node</em>
by setting its <em>camera</em>.<strong>target</strong> property. While attached
to a <em>target</em> the <em>Camera</em> will forward all events triggered or
bubbling through its target to itself.
</p>
<p>
<strong>NOTE:</strong> This feature requires an <em>options.render</em> be
set to a build <em>Renderer</em> object.
</p>
<p>
This allows for some interesting behavior. If you made a camera a <em>Node</em>
you could attach children to the camera and use them as a menuing system that is
not affected by the render order of the scene tree (they are always rendered last).
</p>
<h4>Events</h4>
<p>
Aside from proxying events, there are some events that the camera does trigger.
<ul>
<li>mouse.move - mouse just moved</li>
<li>mouse.in - mouse first moved over a <em>node</em> in the scene tree</li>
<li>mouse.out - mouse moves off of a <em>node</em> in the scene</em>
<li>mouse.down - when <em>mousedown</em> DOM event is handled</li>
<li>mouse.up - when a <em>mouseup</em> DOM event is handled</li>
<li>mouse.click - single <em>mousedown</em> and <em>mouseup</em></li>
<li>mouse.click.[2-n] - multiple <em>mousedown</em>s and <em>mouseup</em>s within the timeout</li>
</ul>
</p>
<h4>Getters / Setters</h4>
<p>
<ul>
<li>
<em>node</em>.<strong>target</strong>
<p>
on <em>read</em> returns <em>node</em> or <em>null</em><br />
on <em>write</em> Unbinds "*" and the <em>node</em>.<strong>eventProxy</strong> handler from the old target and binds "*" to the incoming <em>node</em>
<p>
</li>
</ul>
</p>
<h4>Methods</h4>
<p>
<ul>
<li>
<em>node</em>.<strong>eventProxy</strong>()
<p>
This is an extendable method who's default implementation takes an incoming event, and <em>node</em>.<strong>trigger</strong>s it on <em>node</em>.<br /><br />
<strong>return</strong> the result of the <em>node</em>.<strong>trigger</strong>
</p>
</li>
<li>
<em>node</em>.<strong>render</strong>()
<p>
This method uses the <em>options.renderer</em>'s <em>render</em> method to
render the <em>camera</em>'s current target. Once this is done,
<em>options.renderer</em>.<strong>renderTree</strong> is called on all of
<em>node</em>.<strong>children</strong> if available.
</p>
</li>
</ul>
</p>
<h3>Draggable</h3>
<p>
This feature allows nodes to be "picked up" and "dragged" around the canvas.
It works by binding to the "mouse.move", "mouse.down", and "mouse.up" events,
and triggering it's own events.
</p>
<h4>Events</h4>
<p>
<ul>
<li>drag.start - mouse down on <em>node</em></li>
<li>drag.move - mouse is held down on <em>node</em> and moving</li>
<li>drag.end - mouse is released on the previously held <em>node</em></li>
</ul>
</p>
<h4>Methods</h4>
<p>
<ul>
<li>
<em>node</em>.<strong>dragstart</strong>(<em>node</em>, <em>mouse</em>)
<p>
Sets up the <em>node</em>'s offset to the <em>mouse</em> coordinates.
<br /><br />
<strong>return</strong> <em>Draggable</em> Object
</p>
<li>
<em>node</em>.<strong>dragging</strong>(<em>node</em>, <em>mouse</em>)
<p>
Sets <em>node</em>.<strong>x</strong> and <em>node</em>.<strong>y</strong> based on the difference between the old <em>node</em> position, the current <em>mouse</em> position, and the originally calculated offset.<br /><br />
<strong>return</strong> <em>undefined</em>
</p>
</li>
<li>
<em>node</em>.<strong>dragend</strong>(<em>node</em>, <em>mouse</em>)
<p>
Resets the <em>node</em>'s offset to 0,0<br /><br />
<strong>return</strong> <em>undefined</em>
</p>
</li>
</ul>
</p>
<h3>DropTarget</h3>
<p>
Enables dropping of draggable elements onto the target node. This feature emits a
various events upon different states of the drop action.
</p>
<h4>Events</h4>
<p>
<ul>
<li>drop.start - a <em>Draggable</em> is first moved over this <em>node</em></li>
<li>drop.move - a <em>Draggable</em> is moved while over this <em>node</em></li>
<li>drop.end - a <em>Draggable</em> is dropped on this <em>node</em></li>
</ul>
</p>
<h4>Methods</h4>
<p>
<ul>
<li>
<em>node</em>.<strong>dropFilter</strong>(<em>source</em>, </em>mouse</em>)
<p>
This method inspects <em>source</em> and returns whether or not it is allowed to be dropped in <em>node</em><br /><br />
<em>source</em> the incoming object <br />
<em>mouse</em> contains the current <em>x</em> and <em>y</em> coordinates of the mouse
</p>
<p>
<strong>return</strong> <em>true</em> if allowed and <em>false</em> if denied
</p>
</li>
<li>
<em>node</em>.<strong>dropstart</strong>(<em>source</em>, </em>mouse</em>)
<p>
This is an extendable handler for the "drop.start" event. By default it does nothing<br /><br />
<em>source</em> the incoming object <br />
<em>mouse</em> contains the current <em>x</em> and <em>y</em> coordinates of the mouse
</p>
</li>
<li>
<em>node</em>.<strong>dropping</strong>(<em>source</em>, </em>mouse</em>)
<p>
This is an extendable handler for the "drop.move" event. By default it does nothing <em>node</em><br /><br />
<em>source</em> the incoming object <br />
<em>mouse</em> contains the current <em>x</em> and <em>y</em> coordinates of the mouse
</p>
</li>
<li>
<em>node</em>.<strong>dropend</strong>(<em>source</em>, </em>mouse</em>)
<p>
This is an extendable handler for the "drop.end" event. By default it reparents
the <em>source</em> object to <em>node</em><br /><br />
<em>source</em> the incoming object <br />
<em>mouse</em> contains the current <em>x</em> and <em>y</em> coordinates of the mouse
</p>
</li>
</ul>
</p>
<h3>DropManager</h3>
<p>
Manages in-flight <em>Draggable</em> objects and attempts to link them up with
<em>DropTarget</em>s by utilizing <em>node</em>.<strong>nodesByPoint</strong>
to collect nodes that the <em>Draggable</em> is currently residing over
</p>
<h4>Events</h4>
<p>
<ul>
<li>drop.end</li>
</ul>
</p>
<p>
When a "drag.end" event is detected, the list of <em>DropTarget</em>s is evaluated
by calling <em>node</em>.<strong>dropFilter</strong> and finding the best match
for the drop. If an acceptable <em>DropTarget</em> is found, a "drop.end" event is triggered
on the found <em>node</em>
</p>
<h3>RelativeToParent</h3>
<p>
This feature moves a node whenever it's parent node moves. Re-parenting a node will unbind from the current parent and bind to the new parent. How the child reacts to the parent's movement is controlled by the positionChange method.
<ul>
<li>
<em>node</em>.<strong>positionChanged(name, data)</strong>
<p>
This is an event handler for parent.x and parent.y changes. <strong>name</strong> is the name of the event, and data contains the following properties:
<ul>
<li>
<strong>data.previous</strong>
<p>
Previous value of the parent's x or y (use <strong>name</strong> to determine which)
</p>
</li>
<li>
<strong>data.current</strong>
<p>
Current value of the parent's x or y (use <strong>name</strong> to determine which)
</p>
</li>
</ul>
</p>
</li>
</ul>
</p>
<h3>Style</h3>
<p>
Provider for evented CSS properties. Provides a <em>node</em>.<strong>style</strong> property which
Currently supports:
<ul>
<li><em>node.style</em>.<strong>paddingLeft</strong></li>
<li><em>node.style</em>.<strong>paddingRight</strong></li>
<li><em>node.style</em>.<strong>paddingTop</strong></li>
<li><em>node.style</em>.<strong>paddingBottom</strong></li>
</ul>
Each of these properties emit a "style.&lt;property name&gt;" event when changed. The <em>event</em>
payload includes: <em>node</em>, previous, current.
<h3>Box</h3>
<p>
Provider of a CSS box model. This feature is meant to be stacked on the <em>Style</em> feature,
and in fact, it requires it. Currently supports:
<ul>
<li><em>node.style</em>.<strong>innerHeight</strong> (readonly)</li>
<li><em>node.style</em>.<strong>innerWidth</strong> (readonly)</li>
</ul>
<h2>Demos</h2>
<ul>
<li><a href="demos/window-with-button.html">Draggable window with a button</a></li>
<li><a href="demos/drag-and-drop.html">Draggable and drop between containers</a></li>
<ul>
</body>
</html>
Jump to Line
Something went wrong with that request. Please try again.