more refactoring of topology code
diff --git a/web/ons-demo/js/topologyactions.js b/web/ons-demo/js/topologyactions.js
new file mode 100644
index 0000000..a7ceaaa
--- /dev/null
+++ b/web/ons-demo/js/topologyactions.js
@@ -0,0 +1,229 @@
+function mouseOverSwitch(data) {
+
+	d3.event.preventDefault();
+
+	d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
+
+	if (data.highlighted) {
+		return;
+	}
+
+	// only highlight valid link or flow destination by checking for class of existing highlighted circle
+	var highlighted = svg.selectAll('.highlight')[0];
+	if (highlighted.length == 1) {
+		var s = d3.select(highlighted[0]).select('circle');
+		// only allow links
+		// 	edge->edge (flow)
+		//  aggregation->core
+		//	core->core
+		if (data.className == 'edge' && !s.classed('edge') ||
+			data.className == 'core' && !s.classed('core') && !s.classed('aggregation') ||
+			data.className == 'aggregation' && !s.classed('core')) {
+			return;
+		}
+
+		// the second highlighted switch is the target for a link or flow
+		data.target = true;
+	}
+
+	var node = d3.select(document.getElementById(data.dpid));
+	node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
+	data.highlighted = true;
+	node.moveToFront();
+}
+
+function mouseOutSwitch(data) {
+	d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
+
+	if (data.mouseDown)
+		return;
+
+	var node = d3.select(document.getElementById(data.dpid));
+	node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
+	data.highlighted = false;
+	data.target = false;
+}
+
+function mouseDownSwitch(data) {
+	mouseOverSwitch(data);
+	data.mouseDown = true;
+	d3.select('#topology').classed('linking', true);
+
+	d3.select('svg')
+		.append('svg:path')
+		.attr('id', 'linkVector')
+		.attr('d', function () {
+			var s = d3.select(document.getElementById(data.dpid));
+
+			var pt = document.querySelector('svg').createSVGPoint();
+			pt.x = s.attr('x');
+			pt.y = s.attr('y');
+			pt = pt.matrixTransform(s[0][0].getCTM());
+
+			return line([pt, pt]);
+		});
+
+
+	if (data.className === 'core') {
+		d3.selectAll('.edge').classed('nodrop', true);
+	}
+	if (data.className === 'edge') {
+		d3.selectAll('.core').classed('nodrop', true);
+		d3.selectAll('.aggregation').classed('nodrop', true);
+	}
+	if (data.className === 'aggregation') {
+		d3.selectAll('.edge').classed('nodrop', true);
+		d3.selectAll('.aggregation').classed('nodrop', true);
+	}
+}
+
+function mouseUpSwitch(data) {
+	if (data.mouseDown) {
+		data.mouseDown = false;
+		d3.select('#topology').classed('linking', false);
+		d3.event.stopPropagation();
+		d3.selectAll('.nodrop').classed('nodrop', false);
+	}
+}
+
+function doubleClickSwitch(data) {
+	var circle = d3.select(document.getElementById(data.dpid)).select('circle');
+	if (data.state == 'ACTIVE') {
+		var prompt = 'Deactivate ' + data.dpid + '?';
+		if (confirm(prompt)) {
+			switchDown(data);
+			setPending(circle);
+		}
+	} else {
+		var prompt = 'Activate ' + data.dpid + '?';
+		if (confirm(prompt)) {
+			switchUp(data);
+			setPending(circle);
+		}
+	}
+}
+
+d3.select(document.body).on('mouseup', function () {
+	function clearHighlight() {
+		svg.selectAll('circle').each(function (data) {
+			data.mouseDown = false;
+			d3.select('#topology').classed('linking', false);
+			mouseOutSwitch(data);
+		});
+		d3.select('#linkVector').remove();
+	};
+
+	d3.selectAll('.nodrop').classed('nodrop', false);
+
+	function removeLink(link) {
+		var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
+		var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
+
+		if (path1) {
+			setPending(d3.select(path1));
+		}
+		if (path2) {
+			setPending(d3.select(path2));
+		}
+
+		linkDown(link);
+	}
+
+
+	var highlighted = svg.selectAll('.highlight')[0];
+	if (highlighted.length == 2) {
+		var s1Data = highlighted[0].__data__;
+		var s2Data = highlighted[1].__data__;
+
+		var srcData, dstData;
+		if (s1Data.target) {
+			dstData = s1Data;
+			srcData = s2Data;
+		} else {
+			dstData = s2Data;
+			srcData = s1Data;
+		}
+
+		if (s1Data.className == 'edge' && s2Data.className == 'edge') {
+			var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
+			if (confirm(prompt)) {
+				addFlow(srcData, dstData);
+
+				var flow = {
+					dataPath: {
+						srcPort: {
+							dpid: {
+								value: srcData.dpid
+							}
+						},
+						dstPort: {
+							dpid: {
+								value: dstData.dpid
+							}
+						}
+					},
+				        srcDpid: srcData.dpid,
+				        dstDpid: dstData.dpid,
+					createPending: true
+				};
+
+				selectFlow(flow);
+
+				setTimeout(function () {
+					deselectFlowIfCreatePending(flow);
+				}, pendingTimeout);
+			}
+		} else {
+			var map = linkMap[srcData.dpid];
+			if (map && map[dstData.dpid]) {
+				var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
+				if (confirm(prompt)) {
+					removeLink(map[dstData.dpid]);
+				}
+			} else {
+				map = linkMap[dstData.dpid];
+				if (map && map[srcData.dpid]) {
+					var prompt = 'Remove link between ' + dstData.dpid + ' and ' + srcData.dpid + '?';
+					if (confirm(prompt)) {
+						removeLink(map[srcData.dpid]);
+					}
+				} else {
+					var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
+					if (confirm(prompt)) {
+						var link1 = {
+							'src-switch': srcData.dpid,
+							'src-port': 1,
+							'dst-switch': dstData.dpid,
+							'dst-port': 1,
+							pending: true
+						};
+						pendingLinks[makeLinkKey(link1)] = link1;
+						var link2 = {
+							'src-switch': dstData.dpid,
+							'src-port': 1,
+							'dst-switch': srcData.dpid,
+							'dst-port': 1,
+							pending: true
+						};
+						pendingLinks[makeLinkKey(link2)] = link2;
+						updateTopology();
+
+						linkUp(link1);
+
+						// remove the pending links after 10s
+						setTimeout(function () {
+							delete pendingLinks[makeLinkKey(link1)];
+							delete pendingLinks[makeLinkKey(link2)];
+
+							updateTopology();
+						}, pendingTimeout);
+					}
+				}
+			}
+		}
+
+		clearHighlight();
+	} else {
+		clearHighlight();
+	}
+});
\ No newline at end of file