reservation plugin - unbound request (unclean
[unfold.git] / portal / static / unbound_reservation_static / src / connector-editors.js
1 /*
2  * jsPlumb
3  * 
4  * Title:jsPlumb 1.5.5
5  * 
6  * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
7  * elements, or VML.  
8  * 
9  * This file contains the jsPlumb connector editors.  It is not deployed wth the released versions of jsPlumb; you need to
10  * include it as an extra script.
11  *
12  * Copyright (c) 2010 - 2013 Simon Porritt (simon.porritt@gmail.com)
13  * 
14  * http://jsplumb.org
15  * http://github.com/sporritt/jsplumb
16  * http://code.google.com/p/jsplumb
17  * 
18  * Dual licensed under the MIT and GPL2 licenses.
19  */
20 ;(function() {
21     
22     var AbstractEditor = function(params) {
23         var self = this;        
24     };
25
26     var isTouchDevice = "ontouchstart" in document.documentElement,
27         downEvent = isTouchDevice ? "touchstart" : "mousedown",
28         upEvent = isTouchDevice ? "touchend" : "mouseup",
29         moveEvent = isTouchDevice ? "touchmove" : "mousemove";
30     
31     // TODO: this is for a Straight segment.it would be better to have these all available somewjere, keyed
32     // by segment type
33     var findClosestPointOnPath = function(seg, x, y, i, bounds) {
34         var m = seg[0] == seg[2] ? Infinity : 0,
35             m2 = -1 / m,
36             out = { s:seg, m:m, i:i, x:-1, y:-1, d:Infinity };
37         
38         if (m == 0) {
39             // a horizontal line. if x is in the range of this line then distance is delta y. otherwise we consider it to be
40             // infinity.
41             if ( (seg[0] <= x && x <= seg[2]) || (seg[2] <= x && x <= seg[0])) {
42                 out.x = x,
43                 out.y = seg[1];
44                 out.d = Math.abs(y - seg[1]);
45             }
46         }
47         else if (m == Infinity || m == -Infinity) {
48             // a vertical line. if y is in the range of this line then distance is delta x. otherwise we consider it to be
49             // infinity.
50             if ((seg[1] <= y && y <= seg[3]) || (seg[3] <= y && y <= seg[1])){
51                 out.x = seg[0];
52                 out.y = y;
53                 out.d = Math.abs(x - seg[0]);
54             }                        
55         }
56         else {
57             // closest point lies on normal from given point to this line.  
58             var b = seg[1] - (m * seg[0]),
59                 b2 = y - (m2 * x),
60             // now we know that
61             // y1 = m.x1 + b   and   y1 = m2.x1 + b2
62             // so:  m.x1 + b = m2.x1 + b2
63             //      x1(m - m2) = b2 - b
64             //      x1 = (b2 - b) / (m - m2)
65                 _x1 = (b2 -b) / (m - m2),
66                 _y1 = (m * _x1) + b,
67                 d = jsPlumbGeom.lineLength([ x, y ], [ _x1, _y1 ]),
68                 fractionInSegment = jsPlumbGeom.lineLength([ _x1, _y1 ], [ seg[0], seg[1] ]);
69             
70             out.d = d;
71             out.x = _x1;
72             out.y = _y1;
73             out.l = fractionInSegment / length;
74         }
75         return out;
76     };
77     
78     /**
79     * @namespace jsPlumb.ConnectorEditors
80     * @desc These are editors for the various connector types. They are not included in the
81     * main jsPlumb release. To use them you have to build a custom version of jsPlumb - see
82     * the Gruntfile for information on how to do that. 
83     *
84     * Currently there is only an editor for the Flowchart connector.
85     */
86     jsPlumb.ConnectorEditors = {
87         /**
88         * @name jsPlumb.ConnectorEditors.FlowchartConnectorEditor
89         * @class
90         * @classdesc Lets you drag the segments of a flowchart connection around. If you subsequently
91         * drag an element, your edits are lost.
92         */
93         "Flowchart":function(params) {
94             AbstractEditor.apply(this, arguments);            
95             
96             var jpcl = jsPlumb.CurrentLibrary,
97                 clickConsumer = function(conn) {                     
98                     conn._jsPlumb.afterEditClick = function() {
99                         console.log("after edit click");
100                         conn.unbind("click", conn._jsPlumb.afterEditClick);
101                         conn._jsPlumb.afterEditClick = null;
102                         return false;
103                     }; 
104                     conn.bind("click", conn._jsPlumb.afterEditClick, true);                    
105                 },
106                 documentMouseUp = function(e) {       
107
108                     // an attempt at consuming the click that occurs after this mouseup
109                     // it's not reliable though, as we dont always get a click fired, for some
110                     // reason.
111                     //if (editing)
112                     //    clickConsumer(params.connection);
113
114                     jpcl.removeClass(document.body, params.connection._jsPlumb.instance.dragSelectClass);
115                     params.connection._jsPlumb.instance.setConnectionBeingDragged(false);
116                     e.stopPropagation();
117                     e.preventDefault();
118                     jpcl.unbind(document, upEvent, documentMouseUp);
119                     jpcl.unbind(document, moveEvent, documentMouseMove);                    
120                     downAt = null;
121                     currentSegments = null;
122                     selectedSegment = null; 
123                     segmentCoords = null;
124                     params.connection.setHover(false);                    
125                     params.connector.setSuspendEvents(false); 
126                     params.connection.endpoints[0].setSuspendEvents(false);                
127                     params.connection.endpoints[1].setSuspendEvents(false);
128                     params.connection.editCompleted();
129                     params.connector.justEdited = editing;
130                     editing = false;            
131                 },
132                 downAt = null,
133                 currentSegments = null,
134                 selectedSegment = null,
135                 segmentCoords = null,
136                 editing = false,
137                 anchorsMoveable = params.params.anchorsMoveable,
138                 sgn = function(p1, p2) {
139                     if (p1[0] == p2[0])
140                         return p1[1] < p2[1]  ? 1 : -1;
141                     else
142                         return p1[0] < p2[0]  ? 1 : -1;
143                 },
144                 // collapses currentSegments by joining subsequent segments that are in the
145                 // same axis. we do this because it doesn't matter about stubs any longer once a user
146                 // is editing a connector. so it is best to reduce the number of segments to the 
147                 // minimum.
148                 _collapseSegments = function() {                       
149                     var _last = null, _lastAxis = null, s = [];
150                     for (var i = 0; i < currentSegments.length; i++) {
151                         var seg = currentSegments[i], axis = seg[4], axisIndex = (axis == "v" ? 3 : 2);
152                         if (_last != null && _lastAxis === axis) {
153                             _last[axisIndex] = seg[axisIndex];                            
154                         }
155                         else {
156                             s.push(seg);
157                             _last = seg;
158                             _lastAxis = seg[4];
159                         }
160                     }
161                     currentSegments = s;                   
162                 },
163                 // attempt to shift anchor
164                 _shiftAnchor = function(endpoint, horizontal, value) {                    
165                     var elementSize = jpcl.getSize(endpoint.element),
166                         sizeValue = elementSize[horizontal ? 1 : 0],
167                         ee = jpcl.getElementObject(endpoint.element),
168                         off = jpcl.getOffset(ee), 
169                         cc = jpcl.getElementObject(params.connector.canvas.parentNode),
170                         co = jpcl.getOffset(cc),
171                         offValue = off[horizontal ? "top" : "left"] - co[horizontal ? "top" : "left"], 
172                         ap = endpoint.anchor.getCurrentLocation({element:endpoint}),
173                         desiredLoc = horizontal ? params.connector.y + value : params.connector.x + value;
174                     
175                     if (anchorsMoveable) {                        
176                         
177                         if (offValue < desiredLoc && desiredLoc < offValue + sizeValue) {
178                             // if still on the element, okay to move.
179                             var udl = [ ap[0], ap[1] ];
180                             ap[horizontal ? 1 : 0] = desiredLoc;
181                             endpoint.anchor.setUserDefinedLocation(ap);
182                             return value;
183                         }
184                         else {                        
185                             // otherwise, clamp to element edge
186                             var edgeVal = desiredLoc < offValue ? offValue : offValue + sizeValue;
187                             return edgeVal - (horizontal ? params.connector.y: params.connector.x);                         
188                         }                    
189                     }
190                     else {
191                         // otherwise, return the current anchor point.
192                         return ap[horizontal ? 1 : 0] - params.connector[horizontal ? "y" : "x"];
193                     }
194                 },
195                 _updateSegmentOrientation = function(seg) {
196                     if (seg[0] != seg[2]) seg[5] = (seg[0] < seg[2]) ? 1 : -1;
197                     if (seg[1] != seg[3]) seg[6] = (seg[1] < seg[3]) ? 1 : -1;
198                 },
199                 documentMouseMove = function(e) {
200                     if (selectedSegment != null) {
201                         // suspend events on first move.
202                         if (!editing) {
203                             params.connection.setHover(true);
204                             params.connector.setSuspendEvents(true);
205                             params.connection.endpoints[0].setSuspendEvents(true);                
206                             params.connection.endpoints[1].setSuspendEvents(true);
207                         }
208                         editing = true;
209                         var m = selectedSegment.m, s = selectedSegment.s,
210                             x = (e.pageX || e.page.x), y = (e.pageY || e.page.y),
211                             dx = m == 0 ? 0 : x - downAt[0], dy = m == 0 ? y - downAt[1] : 0,
212                             newX1 = segmentCoords[0] + dx,
213                             newY1 = segmentCoords[1] + dy,
214                             newX2 = segmentCoords[2] + dx,
215                             newY2 = segmentCoords[3] + dy,
216                             horizontal = s[4] == "h";
217                         
218                         // so here we know the new x,y values we would like to set for the start
219                         // and end of this segment. but we may not be able to set these values: if this
220                         // is the first segment, for example, then we are constrained by how far the anchor
221                         // can move (before it slides off its element). same thing goes if this is the last
222                         // segment. if this is not the first or last segment then there are other considerations.
223                         // we know, from having run collapse segments, that there will never be two
224                         // consecutive segments that are not at right angles to each other, so what we need to
225                         // know is whether we can adjust the endpoint of the previous segment to the values we
226                         // want, and the same question for the start values of the next segment.  the answer to
227                         // that is whether or not the segment in question would be rendered too small by such
228                         // a change. if that is the case (and the same goes for anchors) then we want to know
229                         // what an agreeable value is, and we use that.
230                         
231                         if (selectedSegment.i == 0) {
232                                                         
233                             var anchorLoc = _shiftAnchor(params.connection.endpoints[0], horizontal, horizontal ? newY1 : newX1);                            
234                             if (horizontal) 
235                                 newY1 = newY2 = anchorLoc; 
236                             else
237                                 newX1 = newX2 = anchorLoc;
238                         
239                             currentSegments[1][0] = newX2;
240                             currentSegments[1][1] = newY2;
241                             _updateSegmentOrientation(currentSegments[1]);                                                                                            
242                         }
243                         else if (selectedSegment.i == currentSegments.length - 1) {
244                             var anchorLoc = _shiftAnchor(params.connection.endpoints[1], horizontal, horizontal ? newY1 : newX1);                          
245                             if (horizontal) 
246                                 newY1 = newY2 = anchorLoc; 
247                             else
248                                 newX1 = newX2 = anchorLoc;
249                             
250                             currentSegments[currentSegments.length - 2][2] = newX1;
251                             currentSegments[currentSegments.length - 2][3] = newY1;
252                             _updateSegmentOrientation(currentSegments[currentSegments.length - 2]);
253                         }
254                         else {
255                             if (!horizontal) {
256                                 currentSegments[selectedSegment.i - 1][2] = newX1;
257                                 currentSegments[selectedSegment.i + 1][0] = newX2;                                                                
258                             }
259                             else {
260                                 currentSegments[selectedSegment.i - 1][3] = newY1;                            
261                                 currentSegments[selectedSegment.i + 1][1] = newY2;
262                             }
263                             _updateSegmentOrientation(currentSegments[selectedSegment.i + 1]);
264                             _updateSegmentOrientation(currentSegments[selectedSegment.i - 1]);                            
265                         }
266                                                                                                 
267                         s[0] = newX1;
268                         s[1] = newY1;
269                         s[2] = newX2;
270                         s[3] = newY2;                                              
271                         
272                         params.connector.setSegments(currentSegments);
273                         params.connection.repaint();                        
274                         params.connection.endpoints[0].repaint();
275                         params.connection.endpoints[1].repaint();
276                         params.connector.setEdited(true);                        
277                     }
278                     else
279                         editing = false;
280                 };
281                         
282             //bind to mousedown and mouseup, for editing
283             params.connector.bind(downEvent, function(c, e) {
284                 var x = (e.pageX || e.page.x),
285                     y = (e.pageY || e.page.y),
286                     oe = jpcl.getElementObject(params.connection.getConnector().canvas),
287                     o = jpcl.getOffset(oe),                    
288                     minD = Infinity;
289
290                 // TODO this is really the way we want to go: get the segment from the connector.
291                 // for now it's just here to remind me what to change.
292                 var __seg = params.connector.findSegmentForPoint(x-o.left, y-o.top);
293                 console.log(__seg);
294                 
295                 currentSegments = params.connector.getOriginalSegments();
296                 _collapseSegments();
297                 for (var i = 0; i < currentSegments.length; i++) {                    
298                     var _s = findClosestPointOnPath(currentSegments[i], (x - o.left) , (y - o.top), i, params.connector.bounds);
299                     
300                     //var _s = currentSegments[i].findClosestPointOnPath(x - o.left, y - o.top);
301                     
302                     if (_s.d < minD) {
303                         selectedSegment = _s;
304                         segmentCoords = [ _s.s[0], _s.s[1], _s.s[2], _s.s[3] ]; // copy the coords at mousedown
305                         minD = _s.d;
306                     }
307                 }
308                 
309                 downAt = [ x, y ];
310                 
311                 if (selectedSegment != null) {                    
312                     jpcl.bind(document, upEvent, documentMouseUp);
313                     jpcl.bind(document, moveEvent, documentMouseMove);                                      
314                     jpcl.addClass(document.body, params.connection._jsPlumb.instance.dragSelectClass);
315                     params.connection._jsPlumb.instance.setConnectionBeingDragged(true);
316                     params.connection.editStarted();                
317                     return false;
318                 }
319             }, true);
320         }
321     };
322
323     jsPlumb.Connectors.AbstractConnector.prototype.shouldFireEvent = function(type, value, originalEvent) {
324         var out = !this.justEdited;
325         if (type == "click") {            
326             this.justEdited = false;
327         }
328         return out;
329     };
330
331 // ------------------ augment the Connection prototype with the editing stuff --------------------------
332
333     var EDIT_STARTED = "editStarted", EDIT_COMPLETED = "editCompleted", EDIT_CANCELED = "editCanceled";
334
335     jsPlumb.Connection.prototype.setEditable = function(e) {
336         if (this.connector && this.connector.isEditable())
337             this._jsPlumb.editable = e;
338         
339         return this._jsPlumb.editable;
340     };
341
342     jsPlumb.Connection.prototype.isEditable = function() { return this._jsPlumb.editable; };
343
344     jsPlumb.Connection.prototype.editStarted = function() {  
345         this.setSuspendEvents(true);
346         this.fire(EDIT_STARTED, {
347             path:this.connector.getPath()
348         });            
349         this._jsPlumb.instance.setHoverSuspended(true);
350     };
351
352     jsPlumb.Connection.prototype.editCompleted = function() {            
353         this.fire(EDIT_COMPLETED, {
354             path:this.connector.getPath()
355         });       
356         this.setSuspendEvents(false);        
357         this._jsPlumb.instance.setHoverSuspended(false);
358         this.setHover(false);
359     };
360
361     jsPlumb.Connection.prototype.editCanceled = function() {
362         this.fire(EDIT_CANCELED, {
363             path:this.connector.getPath()
364         });        
365         this._jsPlumb.instance.setHoverSuspended(false);
366         this.setHover(false);
367     };
368         
369 })();