summaryrefslogtreecommitdiffstats
path: root/sca-cpp/trunk/modules/edit/htdocs/graph/graph.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--sca-cpp/trunk/modules/edit/htdocs/graph/graph.js400
1 files changed, 344 insertions, 56 deletions
diff --git a/sca-cpp/trunk/modules/edit/htdocs/graph/graph.js b/sca-cpp/trunk/modules/edit/htdocs/graph/graph.js
index c391afaf23..dc5b48e76a 100644
--- a/sca-cpp/trunk/modules/edit/htdocs/graph/graph.js
+++ b/sca-cpp/trunk/modules/edit/htdocs/graph/graph.js
@@ -21,12 +21,12 @@
* SVG and VML composite rendering functions.
*/
-var graph = new Object();
+var graph = {};
/**
* Basic colors
*/
-graph.colors = new Object();
+graph.colors = {};
graph.colors.black = '#000000';
graph.colors.blue = '#0000ff';
graph.colors.cyan = '#00ffff';
@@ -40,6 +40,13 @@ graph.colors.red = '#ff0000';
graph.colors.white = '#ffffff';
graph.colors.yellow = '#ffff00';
+graph.colors.orange1 = '#ffbb00';
+graph.colors.green1 = '#96d333';
+graph.colors.blue1 = '#00c3c9';
+graph.colors.red1 = '#d03f41';
+graph.colors.yellow1 = '#fcee21';
+graph.colors.magenta1 = '#c0688a';
+
/**
* Base path class.
*/
@@ -92,21 +99,31 @@ if (ui.isIE()) {
document.write('<xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v" />');
/**
- * Make a graph.
+ * Make a VML graph.
*/
- graph.mkgraph = function() {
+ graph.mkgraph = function(pos) {
+
+ // Create div element to host the graph
var div = document.createElement('div');
div.id = 'vmldiv';
+ div.style.position = 'absolute';
+ div.style.left = pos.xpos();
+ div.style.top = pos.ypos();
document.body.appendChild(div);
+ // Create a VML group
var vmlg = document.createElement('v:group');
vmlg.style.width = 5000;
vmlg.style.height = 5000;
vmlg.coordsize = '5000,5000';
div.appendChild(vmlg);
+ // Keep track of the current dragged element
graph.dragging = null;
+ /**
+ * Find the first draggable element in a hierarchy of elements.
+ */
function draggable(n) {
if (n == vmlg)
return null;
@@ -115,53 +132,99 @@ if (ui.isIE()) {
return draggable(n.parentNode);
}
- function bringtotop(n) {
- if (n == vmlg)
- return null;
- n.parentNode.appendChild(n);
- return bringtotop(n.parentNode);
- }
-
+ /**
+ * Handle a mousedown event.
+ */
vmlg.onmousedown = function() {
window.event.returnValue = false;
+
+ // Find draggable element
graph.dragging = draggable(window.event.srcElement);
if (graph.dragging == null)
return false;
- bringtotop(graph.dragging);
+
+ // Bring it to the top
+ graph.bringtotop(graph.dragging, vmlg);
+
+ // Remember mouse position
graph.dragX = window.event.clientX;
graph.dragY = window.event.clientY;
vmlg.setCapture();
return false;
};
+ /**
+ * Handle a mouseup event.
+ */
vmlg.onmouseup = function() {
if (graph.dragging == null)
return false;
+
+ if (graph.dragging.parentNode == vmlg && graph.dragging.id.substring(0, 8) != 'palette:') {
+ if (ui.csspos(graph.dragging.style.left) >= 350) {
+
+ // Add dragged component to the edited composite
+ if (!isNil(graph.dragging.comp) && isNil(graph.dragging.compos)) {
+ var compos = scdl.composite(vmlg.compos);
+ setlist(compos, graph.addcomp(graph.dragging.comp, compos));
+ graph.dragging.compos = vmlg.compos;
+ }
+ } else {
+
+ // Discard top level element dragged out of composite area
+ vmlg.removeChild(graph.dragging);
+ if (!isNil(graph.dragging.comp) && !isNil(graph.dragging.compos)) {
+ var compos = scdl.composite(vmlg.compos);
+ setlist(compos, graph.removecomp(graph.dragging.comp, compos));
+ graph.dragging.compos = vmlg.compos;
+ }
+ }
+ }
+
+ // Forget current dragged element
graph.dragging = null;
vmlg.releaseCapture();
return false;
};
+ /**
+ * Handle a mousemove event.
+ */
vmlg.onmousemove = function() {
if (graph.dragging == null)
return false;
- var origX = graph.dragging.coordorigin.X;
- var origY = graph.dragging.coordorigin.Y;
- var newX = origX - (window.event.clientX - graph.dragX);
- var newY = origY - (window.event.clientY - graph.dragY);
- graph.dragX = window.event.clientX;
- graph.dragY = window.event.clientY;
- if (graph.dragging.id.substring(0, 8) == 'palette:') {
- // Clone an element dragged from the palette
- var clone = graph.compshape(graph.dragging.comp, mklist(), graph.mkpath().move(ui.posn(graph.dragging.style.left), ui.posn(graph.dragging.style.top)));
- graph.dragging.parentNode.appendChild(clone);
- graph.dragging = clone;
- }
- graph.dragging.setAttribute('coordorigin', newX + ' ' + newY);
+ // Calculate new position of dragged element
+ var origX = ui.csspos(graph.dragging.style.left);
+ var origY = ui.csspos(graph.dragging.style.top);
+ var newX = origX + (window.event.clientX - graph.dragX);
+ var newY = origY + (window.event.clientY - graph.dragY);
+ if (newX >= 0)
+ graph.dragX = window.event.clientX;
+ else
+ newX = 0;
+ if (newY >= 0)
+ graph.dragY = window.event.clientY;
+ else
+ newY = 0;
+
+ // Clone an element dragged from the palette
+ if (graph.dragging.id.substring(0, 8) == 'palette:')
+ graph.dragging = graph.clonepalette(graph.dragging);
+
+ // Move the dragged element
+ graph.dragging.style.left = newX;
+ graph.dragging.style.top = newY;
+
+ // Update dragged component position
+ if (!isNil(graph.dragging.comp))
+ setlist(graph.dragging.comp, graph.movecomp(graph.dragging.comp, graph.mkpath().move(newX, newY)));
+
return false;
};
+ // Create hidden spans to help compute the width of
+ // component and reference titles
graph.comptitlewidthdiv = document.createElement('span');
graph.comptitlewidthdiv.style.visibility = 'hidden'
graph.comptitlewidthdiv.style.fontWeight = 'bold'
@@ -268,10 +331,14 @@ if (ui.isIE()) {
* Return a shape representing a component.
*/
graph.compshape = function(comp, cassoc, pos) {
+
+ // Make the component title element
var title = graph.comptitle(comp);
+ // Compute the component shape path
var d = graph.comppath(comp, cassoc).str();
+ // Create the main component shape
var shape = document.createElement('v:shape');
shape.style.width = 5000;
shape.style.height = 5000;
@@ -280,6 +347,7 @@ if (ui.isIE()) {
shape.fillcolor = graph.color(comp);
shape.stroked = 'false';
+ // Create an overlay contour shape
var contour = document.createElement('v:shape');
contour.style.width = 5000;
contour.style.height = 5000;
@@ -294,6 +362,7 @@ if (ui.isIE()) {
stroke.opacity = '20%';
contour.appendChild(stroke);
+ // Create a group and add the component and contour shapes to it
var g = document.createElement('v:group');
g.id = scdl.name(comp);
g.style.width = 5000;
@@ -325,17 +394,23 @@ if (ui.isIE()) {
* Return a shape representing a button.
*/
graph.mkbutton = function(t, pos) {
+
+ // Make the title element
var title = graph.mktitle(t, true, pos);
+
+ // Compute the path of the button shape
var d = graph.buttonpath().str();
+ // Create the main button shape
var shape = document.createElement('v:shape');
shape.style.width = 5000;
shape.style.height = 5000;
shape.coordsize = '5000,5000';
shape.path = d;
- shape.fillcolor = graph.colors.blue;
+ shape.fillcolor = graph.colors.blue1;
shape.stroked = 'false';
+ // Create an overlay contour shape
var contour = document.createElement('v:shape');
contour.style.width = 5000;
contour.style.height = 5000;
@@ -350,6 +425,7 @@ if (ui.isIE()) {
stroke.opacity = '20%';
contour.appendChild(stroke);
+ // Create a group and add the button and contour shapes to it
var g = document.createElement('v:group');
g.style.width = 5000;
g.style.height = 5000;
@@ -370,9 +446,11 @@ if (ui.isIE()) {
graph.svgns='http://www.w3.org/2000/svg';
/**
- * Make a graph.
+ * Make an SVG graph.
*/
graph.mkgraph = function(pos) {
+
+ // Create a div element to host the graph
var div = document.createElement('div');
div.id = 'svgdiv';
div.style.position = 'absolute';
@@ -381,13 +459,18 @@ if (ui.isIE()) {
// -webkit-user-select: none;
document.body.appendChild(div);
+ // Create SVG element
var svg = document.createElementNS(graph.svgns, 'svg');
svg.style.height = 5000;
svg.style.width = 5000;
div.appendChild(svg);
+ // Keep track of current dragged element
graph.dragging = null;
+ /**
+ * Find the first draggable element in a hierarchy of elements.
+ */
function draggable(n) {
if (n == svg)
return null;
@@ -396,37 +479,70 @@ if (ui.isIE()) {
return draggable(n.parentNode);
}
- function bringtotop(n) {
- if (n == svg)
- return null;
- n.parentNode.appendChild(n);
- return bringtotop(n.parentNode);
- }
-
+ /**
+ * Handle a mouse down event.
+ */
svg.onmousedown = function(e) {
if (e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
+
+ // Find draggable element
graph.dragging = draggable(e.target);
if (graph.dragging == null)
return false;
- bringtotop(graph.dragging);
+
+ // Bring it to the top
+ graph.bringtotop(graph.dragging, svg);
+
+ // Remember the mouse position
var pos = typeof e.touches != "undefined" ? e.touches[0] : e;
graph.dragX = pos.screenX;
graph.dragY = pos.screenY;
return false;
};
+ // Support touch devices
svg.ontouchstart = svg.onmousedown;
+ /**
+ * Handle a mouse up event.
+ */
window.onmouseup = function(e) {
if (graph.dragging == null)
return false;
+
+ if (graph.dragging.parentNode == svg && graph.dragging.id.substring(0, 8) != 'palette:') {
+ var pmatrix = graph.dragging.parentNode.getCTM();
+ var matrix = graph.dragging.getCTM();
+ var curX = pmatrix != null? (Number(matrix.e) - Number(pmatrix.e)): Number(matrix.e);
+ if (curX >= 350) {
+
+ // Add dragged component to the edited composite
+ if (!isNil(graph.dragging.comp) && isNil(graph.dragging.compos)) {
+ var compos = scdl.composite(svg.compos);
+ setlist(compos, graph.addcomp(graph.dragging.comp, compos));
+ graph.dragging.compos = svg.compos;
+ }
+ } else {
+
+ // Discard top level element dragged out of composite area
+ svg.removeChild(graph.dragging);
+ if (!isNil(graph.dragging.comp) && !isNil(graph.dragging.compos)) {
+ var compos = scdl.composite(svg.compos);
+ setlist(compos, graph.removecomp(graph.dragging.comp, compos));
+ graph.dragging.compos = svg.compos;
+ }
+ }
+ }
+
+ // Forget current dragged element
graph.dragging = null;
return false;
};
+ // Support touch devices
window.top.onmouseup = window.onmouseup;
window.ontouchend = window.onmouseup;
window.gestureend = window.onmouseup;
@@ -435,6 +551,9 @@ if (ui.isIE()) {
window.ontouchcancel = window.onmouseup;
window.top.ontouchcancel = window.onmouseup;
+ /**
+ * Handle a mouse move event.
+ */
window.onmousemove = function(e) {
if (graph.dragging == null)
return false;
@@ -442,6 +561,8 @@ if (ui.isIE()) {
e.preventDefault();
else
e.returnValue = false;
+
+ // Calculate new position of dragged element
var pmatrix = graph.dragging.parentNode.getCTM();
var matrix = graph.dragging.getCTM();
var curX = pmatrix != null? (Number(matrix.e) - Number(pmatrix.e)): Number(matrix.e);
@@ -458,20 +579,27 @@ if (ui.isIE()) {
else
newY = 0;
- if (graph.dragging.id.substring(0, 8) == 'palette:') {
- // Clone an element dragged from the palette
- var clone = graph.compshape(graph.dragging.comp, mklist(), graph.mkpath());
- graph.dragging.parentNode.appendChild(clone);
- graph.dragging = clone;
- }
+ // Clone an element dragged from the palette
+ if (graph.dragging.id.substring(0, 8) == 'palette:')
+ graph.dragging = graph.clonepalette(graph.dragging);
+
+ // Move the dragged element
graph.dragging.setAttribute('transform', 'translate(' + newX + ',' + newY + ')');
+
+ // Update dragged component position
+ if (!isNil(graph.dragging.comp))
+ setlist(graph.dragging.comp, graph.movecomp(graph.dragging.comp, graph.mkpath().move(newX, newY)));
+
return false;
};
+ // Support touch devices
window.top.onmousemove = window.onmousemove;
window.ontouchmove = window.onmousemove;
window.top.ontouchmove = window.onmousemove;
+ // Create a hidden SVG element to help compute the width
+ // of component and reference titles
graph.titlewidthsvg = document.createElementNS(graph.svgns, 'svg');
graph.titlewidthsvg.style.visibility = 'hidden';
graph.titlewidthsvg.style.height = 0;
@@ -571,14 +699,19 @@ if (ui.isIE()) {
* Return a shape representing a component.
*/
graph.compshape = function(comp, cassoc, pos) {
+
+ // Make the component title
var title = graph.comptitle(comp);
+ // Compute the path of the component shape
var d = graph.comppath(comp, cassoc).str();
+ // Create the main component shape
var shape = document.createElementNS(graph.svgns, 'path');
shape.setAttribute('d', d);
shape.setAttribute('fill', graph.color(comp));
+ // Create an overlay contour shape
var contour = document.createElementNS(graph.svgns, 'path');
contour.setAttribute('d', d);
contour.setAttribute('fill', 'none');
@@ -587,6 +720,7 @@ if (ui.isIE()) {
contour.setAttribute('stroke-opacity', '0.20');
contour.setAttribute('transform', 'translate(1,1)');
+ // Create a group and add the component and contour shapes to it.
var g = document.createElementNS(graph.svgns, 'g');
g.id = scdl.name(comp);
g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
@@ -613,13 +747,19 @@ if (ui.isIE()) {
* Return a shape representing a button.
*/
graph.mkbutton = function(t, pos) {
+
+ // Make the button title
var title = graph.mktitle(t, true);
+
+ // Compute the path of the button shape
var d = graph.buttonpath().str();
+ // Create the main button shape
var shape = document.createElementNS(graph.svgns, 'path');
shape.setAttribute('d', d);
- shape.setAttribute('fill', graph.colors.blue);
+ shape.setAttribute('fill', graph.colors.blue1);
+ // Create an overlay contour shape
var contour = document.createElementNS(graph.svgns, 'path');
contour.setAttribute('d', d);
contour.setAttribute('fill', 'none');
@@ -628,6 +768,7 @@ if (ui.isIE()) {
contour.setAttribute('stroke-opacity', '0.20');
contour.setAttribute('transform', 'translate(1,1)');
+ // Create a group and add the button and contour shapes to it
var g = document.createElementNS(graph.svgns, 'g');
g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
g.appendChild(shape);
@@ -638,6 +779,16 @@ if (ui.isIE()) {
}
/**
+ * Bring an element and its parents to the top.
+ */
+graph.bringtotop = function(n, g) {
+ if (n == g)
+ return null;
+ n.parentNode.appendChild(n);
+ return graph.bringtotop(n.parentNode, g);
+}
+
+/**
* Return the title of a SCDL element.
*/
graph.title = function(e) {
@@ -646,7 +797,17 @@ graph.title = function(e) {
};
/**
- * Return the services and references of a component.
+ * Return the color of a component.
+ */
+graph.color = function(comp) {
+ return memo(comp, 'color', function() {
+ var c = scdl.color(comp);
+ return c == null? graph.colors.blue1 : graph.colors[c];
+ });
+};
+
+/**
+ * Return the services on the top side of a component.
*/
graph.tsvcs = function(comp) {
return memo(comp, 'tsvcs', function() {
@@ -658,6 +819,9 @@ graph.tsvcs = function(comp) {
});
};
+/**
+ * Return the services on the left side of a component.
+ */
graph.lsvcs = function(comp) {
return memo(comp, 'lsvcs', function() {
var svcs = scdl.services(comp);
@@ -676,12 +840,18 @@ graph.lsvcs = function(comp) {
});
};
+/**
+ * Return the references on the bottom side of a component.
+ */
graph.brefs = function(comp) {
return memo(comp, 'brefs', function() {
return filter(function(r) { return scdl.align(r) == 'bottom'; }, scdl.references(comp));
});
};
+/**
+ * Return the references on the right side of a component.
+ */
graph.rrefs = function(comp) {
return memo(comp, 'rrefs', function() {
return filter(function(r) { var a = scdl.align(r); return a == null || a == 'right'; }, scdl.references(comp));
@@ -689,16 +859,6 @@ graph.rrefs = function(comp) {
};
/**
- * Return the color of a component.
- */
-graph.color = function(comp) {
- return memo(comp, 'color', function() {
- var c = scdl.color(comp);
- return c == null? graph.colors.blue : graph.colors[c];
- });
-};
-
-/**
* Return the height of a reference on the right side of a component.
*/
graph.rrefheight = function(ref, cassoc) {
@@ -861,33 +1021,43 @@ graph.tsvcpath = function(svc, cassoc, path) {
* Return a path representing a component.
*/
graph.comppath = function(comp, cassoc) {
- var height = graph.compheight(comp, cassoc);
+
+ // Calculate the width and height of the component shape
var width = graph.compwidth(comp, cassoc);
+ var height = graph.compheight(comp, cassoc);
+ /**
+ * Apply a path rendering function to a list of services or references.
+ */
function renderpath(x, f, cassoc, path) {
if (isNil(x))
return path;
return renderpath(cdr(x), f, cassoc, f(car(x), cassoc, path));
}
+ // Render the services on the top side of the component
var tsvcs = graph.tsvcs(comp);
var path = graph.mkpath().move(10,0);
path = renderpath(tsvcs, graph.tsvcpath, cassoc, path);
+ // Render the references on the right side of the component
var rrefs = graph.rrefs(comp);
path = path.line(width - 10,path.ypos()).rcurve(10,0,0,10);
path = renderpath(rrefs, graph.rrefpath, cassoc, path);
+ // Render the references on the bottom side of the component
var brefs = reverse(graph.brefs(comp));
var boffset = 10 + graph.brefswidth(brefs, cassoc);
path = path.line(path.xpos(),height - 10).rcurve(0,10,-10,0).line(boffset, path.ypos());
path = renderpath(brefs, graph.brefpath, cassoc, path);
+ // Render the services on the top side of the component
var lsvcs = graph.lsvcs(comp);
var loffset = 10 + (length(lsvcs) * 60);
path = path.line(10,path.ypos()).rcurve(-10,0,0,-10).line(path.xpos(), loffset);
path = renderpath(lsvcs, graph.lsvcpath, cassoc, path);
+ // Close the component shape path
path = path.line(0,10).rcurve(0,-10,10,0);
return path.end();
};
@@ -915,15 +1085,31 @@ graph.composite = function(compos, pos) {
var cassoc = scdl.nameToElementAssoc(comps);
var proms = scdl.promotions(compos);
+ /**
+ * Render a component.
+ */
function rendercomp(comp, cassoc, pos) {
+
+ /**
+ * Render the references on the right side of a component.
+ */
function renderrrefs(refs, cassoc, pos) {
+
+ /**
+ * Render a reference on the right side of a component.
+ */
function renderrref(ref, cassoc, pos) {
var target = assoc(scdl.target(ref), cassoc);
if (isNil(target))
return mklist();
+
+ // Render the component target of the reference
return rendercomp(cadr(target), cassoc, pos);
}
+ /**
+ * Move the rendering cursor down below a reference.
+ */
function rendermove(ref, cassoc, pos) {
return pos.clone().rmove(0, graph.rrefheight(ref, cassoc));
}
@@ -933,14 +1119,26 @@ graph.composite = function(compos, pos) {
return append(renderrref(car(refs), cassoc, pos), renderrrefs(cdr(refs), cassoc, rendermove(car(refs), cassoc, pos)));
}
+ /**
+ * Render the references on the bottom side of a component.
+ */
function renderbrefs(refs, cassoc, pos) {
+
+ /**
+ * Render a reference on the bottom side of a component.
+ */
function renderbref(ref, cassoc, pos) {
var target = assoc(scdl.target(ref), cassoc);
if (isNil(target))
return mklist();
+
+ // Render the component target of the reference
return rendercomp(cadr(target), cassoc, pos);
}
+ /**
+ * Move the rendering cursor to the right of a reference.
+ */
function rendermove(ref, cassoc, pos) {
return pos.clone().rmove(graph.brefwidth(ref, cassoc), 0);
}
@@ -950,8 +1148,11 @@ graph.composite = function(compos, pos) {
return append(renderbref(car(refs), cassoc, pos), renderbrefs(cdr(refs), cassoc, rendermove(car(refs), cassoc, pos)));
}
+ // Compute the component shape
var gcomp = graph.compshape(comp, cassoc, pos);
+ // Add the shapes of the components wired to its references
+ // as children elements
var rrefs = graph.rrefs(comp);
var rpos = graph.mkpath().rmove(graph.compwidth(comp, cassoc), 0);
appendNodes(renderrrefs(rrefs, cassoc, rpos), gcomp);
@@ -963,7 +1164,14 @@ graph.composite = function(compos, pos) {
return mklist(gcomp);
}
+ /**
+ * Render a list of promoted service components.
+ */
function renderproms(svcs, cassoc, pos) {
+
+ /**
+ * Return the component promoted by a service.
+ */
function promcomp(svc, cassoc) {
var c = assoc(scdl.promote(svc), cassoc);
if (isNil(c))
@@ -971,12 +1179,18 @@ graph.composite = function(compos, pos) {
return cadr(c);
}
+ /**
+ * Return the position of a component.
+ */
function comppos(comp, pos) {
var x = scdl.x(comp);
var y = scdl.y(comp);
- return graph.mkpath().move(x != null? x : pos.xpos(), y != null? y : pos.ypos());
+ return graph.mkpath().move(x != null? Number(x) + 350 : pos.xpos(), y != null? Number(y) : pos.ypos());
}
+ /**
+ * Move the rendering cursor down below a component.
+ */
function rendermove(comp, cassoc, pos) {
return pos.clone().rmove(0, graph.compclosureheight(comp, cassoc) + 40);
}
@@ -984,6 +1198,8 @@ graph.composite = function(compos, pos) {
if (isNil(svcs))
return mklist();
+ // Render the first promoted component in the list
+ // then recurse to render the rest of the list
var comp = promcomp(car(svcs), cassoc);
if (isNil(comp))
return renderproms(cdr(svcs), cassoc, rendermove(car(svcs), cassoc, pos));
@@ -992,13 +1208,85 @@ graph.composite = function(compos, pos) {
return append(rendercomp(comp, cassoc, cpos), renderproms(cdr(svcs), cassoc, rendermove(comp, cassoc, cpos)));
}
+ // Render the promoted service components
var rproms = renderproms(proms, cassoc, pos.clone().rmove(20,20));
- if (name == 'palette')
+ if (name == 'palette') {
+
+ // Prefix ids of palette component elements with 'palette:'
return map(function(r) {
r.id = 'palette:' + r.id;
return r;
}, rproms);
+ } else {
+
+ // Link app component elements to the containing composite
+ return map(function(r) {
+ r.compos = compos;
+ return r;
+ }, rproms);
+ }
return rproms;
};
+/**
+ * Clone a palette element and the component associated with it.
+ */
+graph.clones = 0;
+graph.clonepalette = function(e) {
+
+ // Clone the component and give it a unique name
+ var comp = append(mklist(element, "'component", mklist(attribute, "'name", scdl.name(e.comp) + (++graph.clones))),
+ filter(function(c) { return !(isAttribute(c) && attributeName(c) == "'name")}, elementChildren(e.comp)));
+
+ // Make the component shape element
+ var clone = graph.compshape(comp, mklist(), graph.mkpath());
+ e.parentNode.appendChild(clone);
+ return clone;
+};
+
+/**
+ * Move a component to the given position.
+ */
+graph.movecomp = function(comp, pos) {
+
+ // Add or set the x and y attributes of the component
+ return append(mklist(element, "'component", mklist(attribute, "'t:x", '' + (pos.xpos() - 350)), mklist(attribute, "'t:y", '' + pos.ypos())),
+ filter(function(e) { return !(isAttribute(e) && (attributeName(e) == "'t:x" || attributeName(e) == "'t:y")); }, elementChildren(comp)));
+};
+
+/**
+ * Add a component to a composite.
+ */
+graph.addcomp = function(comp, compos) {
+ var name = scdl.name(comp);
+ var prom = mklist(element, "'service", mklist(attribute, "'name", name), mklist(attribute, "'promote", name));
+ return append(mklist(element, "'composite"), append(elementChildren(compos), mklist(prom, graph.dragging.comp)));
+}
+
+/**
+ * Remove a component from a composite.
+ */
+graph.removecomp = function(comp, compos) {
+ var name = scdl.name(comp);
+ return append(mklist(element, "'composite"),
+ filter(function(c) { return !(isElement(c) && scdl.name(c) == name); }, elementChildren(compos)));
+}
+
+/**
+ * Display a list of graphical nodes.
+ */
+graph.display = function(nodes, g) {
+ appendNodes(nodes, g);
+ return nodes;
+};
+
+/**
+ * Display and enable editing of a composite and the graphical
+ * nodes that represent it.
+ */
+graph.edit = function(compos, nodes, g) {
+ g.compos = compos;
+ return graph.display(nodes, g);
+};
+