6 * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
9 * This file contains the MooTools adapter.
11 * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
14 * http://github.com/sporritt/jsplumb
15 * http://code.google.com/p/jsplumb
17 * Dual licensed under the MIT and GPL2 licenses.
23 * overrides the FX class to inject 'step' functionality, which MooTools does not
24 * offer, and which makes me sad. they don't seem keen to add it, either, despite
25 * the fact that it could be useful:
27 * https://mootools.lighthouseapp.com/projects/2706/tickets/668
30 var jsPlumbMorph = new Class({
33 initialize : function(el, options) {
34 this.parent(el, options);
36 this.onStep = options.onStep;
39 step : function(now) {
42 try { this.onStep(); }
49 _droppableOptions = {},
50 _draggablesByScope = {},
52 _droppableScopesById = {};
56 var _executeDroppableOption = function(el, dr, event, originalEvent) {
58 var id = dr.get("id");
60 var options = _droppableOptions[id];
63 options[event](el, dr, originalEvent);
70 var _checkHover = function(el, entering) {
72 var id = el.get("id");
74 var options = _droppableOptions[id];
76 if (options.hoverClass) {
77 if (entering) el.addClass(options.hoverClass);
78 else el.removeClass(options.hoverClass);
86 * adds the given value to the given list, with the given scope. creates the scoped list
88 * used by initDraggable and initDroppable.
90 var _add = function(list, _scope, value) {
100 * gets an "element object" from the given input. this means an object that is used by the
101 * underlying library on which jsPlumb is running. 'el' may already be one of these objects,
102 * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup
103 * function is used to find the element, using the given String as the element's id.
105 var _getElementObject = function(el) {
109 jsPlumb.CurrentLibrary = {
112 * adds the given class to the element object.
114 addClass : function(el, clazz) {
115 el = jsPlumb.CurrentLibrary.getElementObject(el);
117 if (el.className.constructor == SVGAnimatedString) {
118 jsPlumbUtil.svg.addClass(el, clazz);
120 else el.addClass(clazz);
123 // SVGAnimatedString not supported; no problem.
128 animate : function(el, properties, options) {
129 var m = new jsPlumbMorph(el, options);
133 appendElement : function(child, parent) {
134 _getElementObject(parent).grab(child);
137 bind : function(el, event, callback) {
138 var els = jsPlumbUtil.isString(el) || typeof el.length == "undefined" ? [ _getElementObject(el) ] : $$(el);
139 //el = _getElementObject(el);
140 for (var i = 0; i < els.length; i++)
141 els[i].addEvent(event, callback);
144 destroyDraggable : function(el) {
146 var id = jsPlumb.getId(el), d = _draggablesById[id];
148 for (var i = 0; i < d.length; i++)
151 delete _draggablesById[id];
155 destroyDroppable : function(el) {
160 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep',
161 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete'
165 * wrapper around the library's 'extend' functionality (which it hopefully has.
166 * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you
167 * instead. it's not like its hard.
169 extend : function(o1, o2) {
170 return $extend(o1, o2);
173 getClientXY : function(eventObject) {
174 return [eventObject.event.clientX, eventObject.event.clientY];
177 getDragObject : function(eventArgs) {
181 getDragScope : function(el) {
182 var id = jsPlumb.getId(el),
183 drags = _draggablesById[id];
184 return drags[0].scope;
187 getDropEvent : function(args) {
191 getDropScope : function(el) {
192 var id = jsPlumb.getId(el);
193 return _droppableScopesById[id];
196 getDOMElement : function(el) {
197 if (el == null) return null;
198 // MooTools just decorates the DOM elements. so we have either an ID or an Element here.
199 return typeof(el) == "string" ? document.getElementById(el) : el;
202 getElementObject : _getElementObject,
205 gets the offset for the element object. this should return a js object like this:
207 { left:xxx, top: xxx}
209 getOffset : function(el) {
210 var p = el.getPosition();
211 return { left:p.x, top:p.y };
214 getOriginalEvent : function(e) {
218 getPageXY : function(eventObject) {
219 return [eventObject.event.pageX, eventObject.event.pageY];
222 getParent : function(el) {
223 return jsPlumb.CurrentLibrary.getElementObject(el).getParent();
226 getScrollLeft : function(el) {
230 getScrollTop : function(el) {
234 getSelector : function(context, spec) {
235 if (arguments.length == 2) {
236 return jsPlumb.CurrentLibrary.getElementObject(context).getElements(spec);
242 getSize : function(el) {
243 var s = el.getSize();
247 getTagName : function(el) {
248 var e = jsPlumb.CurrentLibrary.getElementObject(el);
249 return e != null ? e.tagName : null;
253 * takes the args passed to an event function and returns you an object that gives the
254 * position of the object being moved, as a js object with the same params as the result of
255 * getOffset, ie: { left: xxx, top: xxx }.
257 getUIPosition : function(eventArgs, zoom) {
258 var ui = eventArgs[0],
259 el = jsPlumb.CurrentLibrary.getElementObject(ui),
260 p = el.getPosition();
264 return { left:p.x / zoom, top:p.y / zoom};
267 hasClass : function(el, clazz) {
268 return el.hasClass(clazz);
271 initDraggable : function(el, options, isPlumbedComponent, _jsPlumb) {
272 var id = jsPlumb.getId(el);
273 var drag = _draggablesById[id];
275 var originalZIndex = 0,
276 originalCursor = null,
277 dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000;
279 options.onStart = jsPlumbUtil.wrap(options.onStart, function() {
280 originalZIndex = this.element.getStyle('z-index');
281 this.element.setStyle('z-index', dragZIndex);
282 drag.originalZIndex = originalZIndex;
283 if (jsPlumb.Defaults.DragOptions.cursor) {
284 originalCursor = this.element.getStyle('cursor');
285 this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor);
287 $(document.body).addClass(_jsPlumb.dragSelectClass);
290 options.onComplete = jsPlumbUtil.wrap(options.onComplete, function() {
291 this.element.setStyle('z-index', originalZIndex);
292 if (originalCursor) {
293 this.element.setStyle('cursor', originalCursor);
295 $(document.body).removeClass(_jsPlumb.dragSelectClass);
298 // DROPPABLES - only relevant if this is a plumbed component, ie. not just the result of the user making some DOM element
299 // draggable. this is the only library adapter that has to care about this parameter.
300 var scope = "" + (options.scope || jsPlumb.Defaults.Scope),
301 filterFunc = function(entry) {
302 return entry.get("id") != el.get("id");
304 droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : [];
306 if (isPlumbedComponent) {
308 options.droppables = droppables;
309 options.onLeave = jsPlumbUtil.wrap(options.onLeave, function(el, dr) {
311 _checkHover(dr, false);
312 _executeDroppableOption(el, dr, 'onLeave');
315 options.onEnter = jsPlumbUtil.wrap(options.onEnter, function(el, dr) {
317 _checkHover(dr, true);
318 _executeDroppableOption(el, dr, 'onEnter');
321 options.onDrop = function(el, dr, event) {
323 _checkHover(dr, false);
324 _executeDroppableOption(el, dr, 'onDrop', event);
329 options.droppables = [];
331 drag = new Drag.Move(el, options);
333 drag.originalZIndex = originalZIndex;
334 _add(_draggablesById, el.get("id"), drag);
335 // again, only keep a record of this for scope stuff if this is a plumbed component (an endpoint)
336 if (isPlumbedComponent) {
337 _add(_draggablesByScope, scope, drag);
339 // test for disabled.
340 if (options.disabled) drag.detach();
345 initDroppable : function(el, options, isPlumbedComponent, isPermanent) {
346 var scope = options.scope;
347 _add(_droppables, scope, el);
348 var id = jsPlumb.getId(el);
350 el.setAttribute("_isPermanentDroppable", isPermanent); // we use this to clear out droppables on drag complete.
351 _droppableOptions[id] = options;
352 _droppableScopesById[id] = scope;
353 var filterFunc = function(entry) { return entry.element != el; },
354 draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : [];
355 for (var i = 0; i < draggables.length; i++) {
356 draggables[i].droppables.push(el);
360 isAlreadyDraggable : function(el) {
361 return _draggablesById[jsPlumb.getId(el)] != null;
364 isDragSupported : function(el, options) {
365 return typeof Drag != 'undefined' ;
369 * you need Drag.Move imported to make drop work.
371 isDropSupported : function(el, options) {
372 return (typeof Drag !== undefined && typeof Drag.Move !== undefined);
376 * removes the given class from the element object.
378 removeClass : function(el, clazz) {
379 el = jsPlumb.CurrentLibrary.getElementObject(el);
381 if (el.className.constructor == SVGAnimatedString) {
382 jsPlumbUtil.svg.removeClass(el, clazz);
384 else el.removeClass(clazz);
387 // SVGAnimatedString not supported; no problem.
388 el.removeClass(clazz);
392 removeElement : function(element, parent) {
393 var el = _getElementObject(element);
394 if (el) el.dispose(); // ??
397 setDragFilter : function(el, filter) {
398 jsPlumbUtil.log("NOT IMPLEMENTED: setDragFilter");
401 setDraggable : function(el, draggable) {
402 var draggables = _draggablesById[el.get("id")];
404 draggables.each(function(d) {
405 if (draggable) d.attach(); else d.detach();
410 setDragScope : function(el, scope) {
411 var drag = _draggablesById[el.get("id")];
412 var filterFunc = function(entry) {
413 return entry.get("id") != el.get("id");
415 var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : [];
416 drag[0].droppables = droppables;
419 setOffset : function(el, o) {
420 _getElementObject(el).setPosition({x:o.left, y:o.top});
423 stopDrag : function() {
424 for (var i in _draggablesById) {
425 for (var j = 0; j < _draggablesById[i].length; j++) {
426 var d = _draggablesById[i][j];
428 if (d.originalZIndex !== 0)
429 d.element.setStyle("z-index", d.originalZIndex);
435 * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself.
436 * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff
437 * from the originalEvent to put in an options object for YUI.
440 * @param originalEvent
442 trigger : function(el, event, originalEvent) {
443 originalEvent.stopPropagation();
444 _getElementObject(el).fireEvent(event, originalEvent);
447 unbind : function(el, event, callback) {
448 el = _getElementObject(el);
449 el.removeEvent(event, callback);
453 window.addEvent('domready', jsPlumb.init);