--- /dev/null
+/*
+ * jsPlumb
+ *
+ * Title:jsPlumb 1.5.5
+ *
+ * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
+ * elements, or VML.
+ *
+ * This file contains the jsPlumb connector editors. It is not deployed wth the released versions of jsPlumb; you need to
+ * include it as an extra script.
+ *
+ * Copyright (c) 2010 - 2013 Simon Porritt (simon.porritt@gmail.com)
+ *
+ * http://jsplumb.org
+ * http://github.com/sporritt/jsplumb
+ * http://code.google.com/p/jsplumb
+ *
+ * Dual licensed under the MIT and GPL2 licenses.
+ */
+;(function() {
+
+ var AbstractEditor = function(params) {
+ var self = this;
+ };
+
+ var isTouchDevice = "ontouchstart" in document.documentElement,
+ downEvent = isTouchDevice ? "touchstart" : "mousedown",
+ upEvent = isTouchDevice ? "touchend" : "mouseup",
+ moveEvent = isTouchDevice ? "touchmove" : "mousemove";
+
+ // TODO: this is for a Straight segment.it would be better to have these all available somewjere, keyed
+ // by segment type
+ var findClosestPointOnPath = function(seg, x, y, i, bounds) {
+ var m = seg[0] == seg[2] ? Infinity : 0,
+ m2 = -1 / m,
+ out = { s:seg, m:m, i:i, x:-1, y:-1, d:Infinity };
+
+ if (m == 0) {
+ // a horizontal line. if x is in the range of this line then distance is delta y. otherwise we consider it to be
+ // infinity.
+ if ( (seg[0] <= x && x <= seg[2]) || (seg[2] <= x && x <= seg[0])) {
+ out.x = x,
+ out.y = seg[1];
+ out.d = Math.abs(y - seg[1]);
+ }
+ }
+ else if (m == Infinity || m == -Infinity) {
+ // a vertical line. if y is in the range of this line then distance is delta x. otherwise we consider it to be
+ // infinity.
+ if ((seg[1] <= y && y <= seg[3]) || (seg[3] <= y && y <= seg[1])){
+ out.x = seg[0];
+ out.y = y;
+ out.d = Math.abs(x - seg[0]);
+ }
+ }
+ else {
+ // closest point lies on normal from given point to this line.
+ var b = seg[1] - (m * seg[0]),
+ b2 = y - (m2 * x),
+ // now we know that
+ // y1 = m.x1 + b and y1 = m2.x1 + b2
+ // so: m.x1 + b = m2.x1 + b2
+ // x1(m - m2) = b2 - b
+ // x1 = (b2 - b) / (m - m2)
+ _x1 = (b2 -b) / (m - m2),
+ _y1 = (m * _x1) + b,
+ d = jsPlumbGeom.lineLength([ x, y ], [ _x1, _y1 ]),
+ fractionInSegment = jsPlumbGeom.lineLength([ _x1, _y1 ], [ seg[0], seg[1] ]);
+
+ out.d = d;
+ out.x = _x1;
+ out.y = _y1;
+ out.l = fractionInSegment / length;
+ }
+ return out;
+ };
+
+ /**
+ * @namespace jsPlumb.ConnectorEditors
+ * @desc These are editors for the various connector types. They are not included in the
+ * main jsPlumb release. To use them you have to build a custom version of jsPlumb - see
+ * the Gruntfile for information on how to do that.
+ *
+ * Currently there is only an editor for the Flowchart connector.
+ */
+ jsPlumb.ConnectorEditors = {
+ /**
+ * @name jsPlumb.ConnectorEditors.FlowchartConnectorEditor
+ * @class
+ * @classdesc Lets you drag the segments of a flowchart connection around. If you subsequently
+ * drag an element, your edits are lost.
+ */
+ "Flowchart":function(params) {
+ AbstractEditor.apply(this, arguments);
+
+ var jpcl = jsPlumb.CurrentLibrary,
+ clickConsumer = function(conn) {
+ conn._jsPlumb.afterEditClick = function() {
+ console.log("after edit click");
+ conn.unbind("click", conn._jsPlumb.afterEditClick);
+ conn._jsPlumb.afterEditClick = null;
+ return false;
+ };
+ conn.bind("click", conn._jsPlumb.afterEditClick, true);
+ },
+ documentMouseUp = function(e) {
+
+ // an attempt at consuming the click that occurs after this mouseup
+ // it's not reliable though, as we dont always get a click fired, for some
+ // reason.
+ //if (editing)
+ // clickConsumer(params.connection);
+
+ jpcl.removeClass(document.body, params.connection._jsPlumb.instance.dragSelectClass);
+ params.connection._jsPlumb.instance.setConnectionBeingDragged(false);
+ e.stopPropagation();
+ e.preventDefault();
+ jpcl.unbind(document, upEvent, documentMouseUp);
+ jpcl.unbind(document, moveEvent, documentMouseMove);
+ downAt = null;
+ currentSegments = null;
+ selectedSegment = null;
+ segmentCoords = null;
+ params.connection.setHover(false);
+ params.connector.setSuspendEvents(false);
+ params.connection.endpoints[0].setSuspendEvents(false);
+ params.connection.endpoints[1].setSuspendEvents(false);
+ params.connection.editCompleted();
+ params.connector.justEdited = editing;
+ editing = false;
+ },
+ downAt = null,
+ currentSegments = null,
+ selectedSegment = null,
+ segmentCoords = null,
+ editing = false,
+ anchorsMoveable = params.params.anchorsMoveable,
+ sgn = function(p1, p2) {
+ if (p1[0] == p2[0])
+ return p1[1] < p2[1] ? 1 : -1;
+ else
+ return p1[0] < p2[0] ? 1 : -1;
+ },
+ // collapses currentSegments by joining subsequent segments that are in the
+ // same axis. we do this because it doesn't matter about stubs any longer once a user
+ // is editing a connector. so it is best to reduce the number of segments to the
+ // minimum.
+ _collapseSegments = function() {
+ var _last = null, _lastAxis = null, s = [];
+ for (var i = 0; i < currentSegments.length; i++) {
+ var seg = currentSegments[i], axis = seg[4], axisIndex = (axis == "v" ? 3 : 2);
+ if (_last != null && _lastAxis === axis) {
+ _last[axisIndex] = seg[axisIndex];
+ }
+ else {
+ s.push(seg);
+ _last = seg;
+ _lastAxis = seg[4];
+ }
+ }
+ currentSegments = s;
+ },
+ // attempt to shift anchor
+ _shiftAnchor = function(endpoint, horizontal, value) {
+ var elementSize = jpcl.getSize(endpoint.element),
+ sizeValue = elementSize[horizontal ? 1 : 0],
+ ee = jpcl.getElementObject(endpoint.element),
+ off = jpcl.getOffset(ee),
+ cc = jpcl.getElementObject(params.connector.canvas.parentNode),
+ co = jpcl.getOffset(cc),
+ offValue = off[horizontal ? "top" : "left"] - co[horizontal ? "top" : "left"],
+ ap = endpoint.anchor.getCurrentLocation({element:endpoint}),
+ desiredLoc = horizontal ? params.connector.y + value : params.connector.x + value;
+
+ if (anchorsMoveable) {
+
+ if (offValue < desiredLoc && desiredLoc < offValue + sizeValue) {
+ // if still on the element, okay to move.
+ var udl = [ ap[0], ap[1] ];
+ ap[horizontal ? 1 : 0] = desiredLoc;
+ endpoint.anchor.setUserDefinedLocation(ap);
+ return value;
+ }
+ else {
+ // otherwise, clamp to element edge
+ var edgeVal = desiredLoc < offValue ? offValue : offValue + sizeValue;
+ return edgeVal - (horizontal ? params.connector.y: params.connector.x);
+ }
+ }
+ else {
+ // otherwise, return the current anchor point.
+ return ap[horizontal ? 1 : 0] - params.connector[horizontal ? "y" : "x"];
+ }
+ },
+ _updateSegmentOrientation = function(seg) {
+ if (seg[0] != seg[2]) seg[5] = (seg[0] < seg[2]) ? 1 : -1;
+ if (seg[1] != seg[3]) seg[6] = (seg[1] < seg[3]) ? 1 : -1;
+ },
+ documentMouseMove = function(e) {
+ if (selectedSegment != null) {
+ // suspend events on first move.
+ if (!editing) {
+ params.connection.setHover(true);
+ params.connector.setSuspendEvents(true);
+ params.connection.endpoints[0].setSuspendEvents(true);
+ params.connection.endpoints[1].setSuspendEvents(true);
+ }
+ editing = true;
+ var m = selectedSegment.m, s = selectedSegment.s,
+ x = (e.pageX || e.page.x), y = (e.pageY || e.page.y),
+ dx = m == 0 ? 0 : x - downAt[0], dy = m == 0 ? y - downAt[1] : 0,
+ newX1 = segmentCoords[0] + dx,
+ newY1 = segmentCoords[1] + dy,
+ newX2 = segmentCoords[2] + dx,
+ newY2 = segmentCoords[3] + dy,
+ horizontal = s[4] == "h";
+
+ // so here we know the new x,y values we would like to set for the start
+ // and end of this segment. but we may not be able to set these values: if this
+ // is the first segment, for example, then we are constrained by how far the anchor
+ // can move (before it slides off its element). same thing goes if this is the last
+ // segment. if this is not the first or last segment then there are other considerations.
+ // we know, from having run collapse segments, that there will never be two
+ // consecutive segments that are not at right angles to each other, so what we need to
+ // know is whether we can adjust the endpoint of the previous segment to the values we
+ // want, and the same question for the start values of the next segment. the answer to
+ // that is whether or not the segment in question would be rendered too small by such
+ // a change. if that is the case (and the same goes for anchors) then we want to know
+ // what an agreeable value is, and we use that.
+
+ if (selectedSegment.i == 0) {
+
+ var anchorLoc = _shiftAnchor(params.connection.endpoints[0], horizontal, horizontal ? newY1 : newX1);
+ if (horizontal)
+ newY1 = newY2 = anchorLoc;
+ else
+ newX1 = newX2 = anchorLoc;
+
+ currentSegments[1][0] = newX2;
+ currentSegments[1][1] = newY2;
+ _updateSegmentOrientation(currentSegments[1]);
+ }
+ else if (selectedSegment.i == currentSegments.length - 1) {
+ var anchorLoc = _shiftAnchor(params.connection.endpoints[1], horizontal, horizontal ? newY1 : newX1);
+ if (horizontal)
+ newY1 = newY2 = anchorLoc;
+ else
+ newX1 = newX2 = anchorLoc;
+
+ currentSegments[currentSegments.length - 2][2] = newX1;
+ currentSegments[currentSegments.length - 2][3] = newY1;
+ _updateSegmentOrientation(currentSegments[currentSegments.length - 2]);
+ }
+ else {
+ if (!horizontal) {
+ currentSegments[selectedSegment.i - 1][2] = newX1;
+ currentSegments[selectedSegment.i + 1][0] = newX2;
+ }
+ else {
+ currentSegments[selectedSegment.i - 1][3] = newY1;
+ currentSegments[selectedSegment.i + 1][1] = newY2;
+ }
+ _updateSegmentOrientation(currentSegments[selectedSegment.i + 1]);
+ _updateSegmentOrientation(currentSegments[selectedSegment.i - 1]);
+ }
+
+ s[0] = newX1;
+ s[1] = newY1;
+ s[2] = newX2;
+ s[3] = newY2;
+
+ params.connector.setSegments(currentSegments);
+ params.connection.repaint();
+ params.connection.endpoints[0].repaint();
+ params.connection.endpoints[1].repaint();
+ params.connector.setEdited(true);
+ }
+ else
+ editing = false;
+ };
+
+ //bind to mousedown and mouseup, for editing
+ params.connector.bind(downEvent, function(c, e) {
+ var x = (e.pageX || e.page.x),
+ y = (e.pageY || e.page.y),
+ oe = jpcl.getElementObject(params.connection.getConnector().canvas),
+ o = jpcl.getOffset(oe),
+ minD = Infinity;
+
+ // TODO this is really the way we want to go: get the segment from the connector.
+ // for now it's just here to remind me what to change.
+ var __seg = params.connector.findSegmentForPoint(x-o.left, y-o.top);
+ console.log(__seg);
+
+ currentSegments = params.connector.getOriginalSegments();
+ _collapseSegments();
+ for (var i = 0; i < currentSegments.length; i++) {
+ var _s = findClosestPointOnPath(currentSegments[i], (x - o.left) , (y - o.top), i, params.connector.bounds);
+
+ //var _s = currentSegments[i].findClosestPointOnPath(x - o.left, y - o.top);
+
+ if (_s.d < minD) {
+ selectedSegment = _s;
+ segmentCoords = [ _s.s[0], _s.s[1], _s.s[2], _s.s[3] ]; // copy the coords at mousedown
+ minD = _s.d;
+ }
+ }
+
+ downAt = [ x, y ];
+
+ if (selectedSegment != null) {
+ jpcl.bind(document, upEvent, documentMouseUp);
+ jpcl.bind(document, moveEvent, documentMouseMove);
+ jpcl.addClass(document.body, params.connection._jsPlumb.instance.dragSelectClass);
+ params.connection._jsPlumb.instance.setConnectionBeingDragged(true);
+ params.connection.editStarted();
+ return false;
+ }
+ }, true);
+ }
+ };
+
+ jsPlumb.Connectors.AbstractConnector.prototype.shouldFireEvent = function(type, value, originalEvent) {
+ var out = !this.justEdited;
+ if (type == "click") {
+ this.justEdited = false;
+ }
+ return out;
+ };
+
+// ------------------ augment the Connection prototype with the editing stuff --------------------------
+
+ var EDIT_STARTED = "editStarted", EDIT_COMPLETED = "editCompleted", EDIT_CANCELED = "editCanceled";
+
+ jsPlumb.Connection.prototype.setEditable = function(e) {
+ if (this.connector && this.connector.isEditable())
+ this._jsPlumb.editable = e;
+
+ return this._jsPlumb.editable;
+ };
+
+ jsPlumb.Connection.prototype.isEditable = function() { return this._jsPlumb.editable; };
+
+ jsPlumb.Connection.prototype.editStarted = function() {
+ this.setSuspendEvents(true);
+ this.fire(EDIT_STARTED, {
+ path:this.connector.getPath()
+ });
+ this._jsPlumb.instance.setHoverSuspended(true);
+ };
+
+ jsPlumb.Connection.prototype.editCompleted = function() {
+ this.fire(EDIT_COMPLETED, {
+ path:this.connector.getPath()
+ });
+ this.setSuspendEvents(false);
+ this._jsPlumb.instance.setHoverSuspended(false);
+ this.setHover(false);
+ };
+
+ jsPlumb.Connection.prototype.editCanceled = function() {
+ this.fire(EDIT_CANCELED, {
+ path:this.connector.getPath()
+ });
+ this._jsPlumb.instance.setHoverSuspended(false);
+ this.setHover(false);
+ };
+
+})();
\ No newline at end of file