6 * Provides a way to visually connect elements on an HTML page, using either SVG or VML.
8 * This file contains the util functions
10 * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
13 * http://github.com/sporritt/jsplumb
14 * http://code.google.com/p/jsplumb
16 * Dual licensed under the MIT and GPL2 licenses.
21 var _isa = function(a) { return Object.prototype.toString.call(a) === "[object Array]"; },
22 _isnum = function(n) { return Object.prototype.toString.call(n) === "[object Number]"; },
23 _iss = function(s) { return typeof s === "string"; },
24 _isb = function(s) { return typeof s === "boolean"; },
25 _isnull = function(s) { return s == null; },
26 _iso = function(o) { return o == null ? false : Object.prototype.toString.call(o) === "[object Object]"; },
27 _isd = function(o) { return Object.prototype.toString.call(o) === "[object Date]"; },
28 _isf = function(o) { return Object.prototype.toString.call(o) === "[object Function]"; },
30 for (var i in o) { if (o.hasOwnProperty(i)) return false; }
33 pointHelper = function(p1, p2, fn) {
34 p1 = _isa(p1) ? p1 : [p1.x, p1.y];
35 p2 = _isa(p2) ? p2 : [p2.x, p2.y];
50 if (_iss(a)) return "" + a;
51 else if (_isb(a)) return !!a;
52 else if (_isd(a)) return new Date(a.getTime());
53 else if (_isf(a)) return a;
56 for (var i = 0; i < a.length; i++)
57 b.push(this.clone(a[i]));
63 c[j] = this.clone(a[j]);
68 merge : function(a, b) {
69 var c = this.clone(a);
71 if (c[i] == null || _iss(b[i]) || _isb(b[i]))
74 if (_isa(b[i])/* && this.isArray(c[i])*/) {
76 // if c's object is also an array we can keep its values.
77 if (_isa(c[i])) ar.push.apply(ar, c[i]);
78 ar.push.apply(ar, b[i]);
82 // overwite c's value with an object if it is not already one.
92 copyValues:function(names, from, to) {
93 for (var i = 0; i < names.length; i++)
94 to[names[i]] = from[names[i]];
97 // chain a list of functions, supplied by [ object, method name, args ], and return on the first
98 // one that returns the failValue. if none return the failValue, return the successValue.
100 functionChain : function(successValue, failValue, fns) {
101 for (var i = 0; i < fns.length; i++) {
102 var o = fns[i][0][fns[i][1]].apply(fns[i][0], fns[i][2]);
103 if (o === failValue) {
109 // take the given model and expand out any parameters.
110 populate : function(model, values) {
111 // for a string, see if it has parameter matches, and if so, try to make the substitutions.
112 var getValue = function(fromString) {
113 var matches = fromString.match(/(\${.*?})/g);
114 if (matches != null) {
115 for (var i = 0; i < matches.length; i++) {
116 var val = values[matches[i].substring(2, matches[i].length - 1)];
118 fromString = fromString.replace(matches[i], val);
124 // process one entry.
132 for (var i = 0; i < d.length; i++)
151 convertStyle : function(s, ignoreAlpha) {
152 // TODO: jsPlumb should support a separate 'opacity' style member.
153 if ("transparent" === s) return s;
155 pad = function(n) { return n.length == 1 ? "0" + n : n; },
156 hex = function(k) { return pad(Number(k).toString(16)); },
157 pattern = /(rgb[a]?\()(.*)(\))/;
158 if (s.match(pattern)) {
159 var parts = s.match(pattern)[2].split(",");
160 o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]);
161 if (!ignoreAlpha && parts.length == 4)
162 o = o + hex(parts[3]);
166 findWithFunction : function(a, f) {
168 for (var i = 0; i < a.length; i++) if (f(a[i])) return i;
171 clampToGrid : function(x, y, grid, dontClampX, dontClampY) {
172 var _gridClamp = function(n, g) {
174 f = Math.floor(n / g),
175 inc = e >= (g / 2) ? 1 : 0;
176 return (f + inc) * g;
179 dontClampX || grid == null ? x : _gridClamp(x, grid[0]),
180 dontClampY || grid == null ? y : _gridClamp(y, grid[1])
183 indexOf : function(l, v) {
184 return jsPlumbUtil.findWithFunction(l, function(_v) { return _v == v; });
186 removeWithFunction : function(a, f) {
187 var idx = jsPlumbUtil.findWithFunction(a, f);
188 if (idx > -1) a.splice(idx, 1);
191 remove : function(l, v) {
192 var idx = jsPlumbUtil.indexOf(l, v);
193 if (idx > -1) l.splice(idx, 1);
196 // TODO support insert index
197 addWithFunction : function(list, item, hashFunction) {
198 if (jsPlumbUtil.findWithFunction(list, hashFunction) == -1) list.push(item);
200 addToList : function(map, key, value, insertAtStart) {
206 l[insertAtStart ? "unshift" : "push"](value);
210 // extends the given obj (which can be an array) with the given constructor function, prototype functions, and
211 // class members, any of which may be null.
213 extend : function(child, parent, _protoFn, _protoAtts) {
214 _protoFn = _protoFn || {};
215 _protoAtts = _protoAtts || {};
216 parent = _isa(parent) ? parent : [ parent ];
218 for (var i = 0; i < parent.length; i++) {
219 for (var j in parent[i].prototype) {
220 if(parent[i].prototype.hasOwnProperty(j)) {
221 child.prototype[j] = parent[i].prototype[j];
226 var _makeFn = function(name) {
228 for (var i = 0; i < parent.length; i++) {
229 if (parent[i].prototype[name])
230 parent[i].prototype[name].apply(this, arguments);
232 return _protoFn[name].apply(this, arguments);
236 for (var k in _protoFn) {
237 child.prototype[k] = _makeFn(k);
243 return ('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
244 var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
245 return v.toString(16);
250 if (jsPlumbUtil.logEnabled && typeof console != "undefined") {
252 var msg = arguments[arguments.length - 1];
258 group : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.group(g); },
259 groupEnd : function(g) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.groupEnd(g); },
260 time : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.time(t); },
261 timeEnd : function(t) { if (jsPlumbUtil.logEnabled && typeof console != "undefined") console.timeEnd(t); },
264 * helper to remove an element from the DOM.
266 removeElement : function(element) {
267 if (element != null && element.parentNode != null) {
268 element.parentNode.removeChild(element);
272 * helper to remove a list of elements from the DOM.
274 removeElements : function(elements) {
275 for ( var i = 0; i < elements.length; i++)
276 jsPlumbUtil.removeElement(elements[i]);
279 * Function: sizeElement
280 * Helper to size and position an element. You would typically use
281 * this when writing your own Connector or Endpoint implementation.
284 * x - [int] x position for the element origin
285 * y - [int] y position for the element origin
286 * w - [int] width of the element
287 * h - [int] height of the element
290 sizeElement : function(el, x, y, w, h) {
292 el.style.height = h + "px";
294 el.style.width = w + "px";
296 el.style.left = x + "px";
297 el.style.top = y + "px";
301 * @name jsPlumbUtil.wrap
302 * @desc Wraps one function with another, creating a placeholder for the
303 * wrapped function if it was null. this is used to wrap the various
304 * drag/drop event functions - to allow jsPlumb to be notified of
305 * important lifecycle events without imposing itself on the user's
306 * drag/drop functionality.
307 * @param {Function} wrappedFunction original function to wrap; may be null.
308 * @param {Function} newFunction function to wrap the original with.
309 * @param {Object} [returnOnThisValue] Optional. Indicates that the wrappedFunction should
310 * not be executed if the newFunction returns a value matching 'returnOnThisValue'.
311 * note that this is a simple comparison and only works for primitives right now.
313 wrap : function(wrappedFunction, newFunction, returnOnThisValue) {
314 wrappedFunction = wrappedFunction || function() { };
315 newFunction = newFunction || function() { };
319 r = newFunction.apply(this, arguments);
321 jsPlumbUtil.log("jsPlumb function failed : " + e);
323 if (returnOnThisValue == null || (r !== returnOnThisValue)) {
325 r = wrappedFunction.apply(this, arguments);
327 jsPlumbUtil.log("wrapped function failed : " + e);
336 jsPlumbUtil.EventGenerator = function() {
337 var _listeners = {}, eventsSuspended = false;
339 // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to
340 // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event
341 // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready"
342 // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting
343 // to hear what other people think.
344 var eventsToDieOn = [ "ready" ];
346 this.bind = function(event, listener, insertAtStart) {
347 jsPlumbUtil.addToList(_listeners, event, listener, insertAtStart);
351 this.fire = function(event, value, originalEvent) {
352 if (!eventsSuspended && _listeners[event]) {
353 // instead of looping through the array we get a counter and a length, because it is possible
354 // that an event fired from here could cause the object to get cleaned up, which would throw
355 // away the listeners. so after each cycle through the loop we check to ensure we haven't
357 var l = _listeners[event].length, i = 0, _gone = false, ret = null;
358 if (!this.shouldFireEvent || this.shouldFireEvent(event, value, originalEvent)) {
359 while (!_gone && i < l && ret !== false) {
361 // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this
362 // method will have the whole call stack available in the debugger.
363 if (jsPlumbUtil.findWithFunction(eventsToDieOn, function(e) { return e === event; }) != -1)
364 _listeners[event][i](value, originalEvent);
366 // for events we don't want to die on, catch and log.
368 ret = _listeners[event][i](value, originalEvent);
370 jsPlumbUtil.log("jsPlumb: fire failed for event " + event + " : " + e);
374 if (_listeners == null || _listeners[event] == null) _gone = true;
381 this.unbind = function(event) {
383 delete _listeners[event];
390 this.getListener = function(forEvent) {
391 return _listeners[forEvent];
393 this.setSuspendEvents = function(val) {
394 eventsSuspended = val;
396 this.isSuspendEvents = function() {
397 return eventsSuspended;
399 this.cleanupListeners = function() {
400 for (var i in _listeners) {
401 _listeners[i].splice(0);
402 delete _listeners[i];
408 jsPlumbUtil.EventGenerator.prototype = {
410 this.cleanupListeners();
416 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FFunction%2Fbind
417 if (!Function.prototype.bind) {
418 Function.prototype.bind = function (oThis) {
419 if (typeof this !== "function") {
420 // closest thing possible to the ECMAScript 5 internal IsCallable function
421 throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
424 var aArgs = Array.prototype.slice.call(arguments, 1),
426 fNOP = function () {},
427 fBound = function () {
428 return fToBind.apply(this instanceof fNOP && oThis ? this : oThis,
429 aArgs.concat(Array.prototype.slice.call(arguments)));
432 fNOP.prototype = this.prototype;
433 fBound.prototype = new fNOP();