Repository: sketchpunk/NEditorJS Branch: master Commit: 6de5529d3716 Files: 4 Total size: 14.9 KB Directory structure: gitextract_439mgt12/ ├── NEditor.css ├── NEditor.js ├── README.md └── ui.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: NEditor.css ================================================ *{font-family: arial; } body{background:url(bg_grid.png);} svg{position:absolute; z-index:-100; top:0px; left:0px; width:100%; height:100%;} /*---------------------------------------*/ .NodeContainer{ position:absolute; background-color:rgba(63,63,63,.7); display:inline-block; border-radius:5px; box-shadow: 0px 5px 10px #000000;} .NodeContainer > header{ display:block; background-color:#297286; color:white; cursor:pointer; border-radius:5px 5px 0px 0px; text-align:center; padding:4px 12px; -webkit-touch-callout: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .NodeContainer > ul { margin:0px; padding:0px; list-style:none; } .NodeContainer > ul > li { position:relative; margin-top:8px; margin-bottom:8px; } .NodeContainer > ul > li > span{padding-left:1em; padding-right:1em; color:white;} .NodeContainer > ul > li > i{ position:absolute; width:0.6em; height:0.6em; background-color:gray; border-radius:1em; cursor:pointer; } .NodeContainer > ul > li > i:hover{ background-color:red; } .NodeContainer > ul > li.Input > i { left:-0.3em; top:0.4em; } .NodeContainer > ul > li.Output { text-align:right;} .NodeContainer > ul > li.Output > i { right:-0.25em; top:0.4em; } .NodeContainer > ul > li.Active > i { background-color:#72a836;} ================================================ FILE: NEditor.js ================================================ //Copyright 2016 Sketchpunk Labs //########################################################################### //Main Static Object //########################################################################### var NEditor = {}; NEditor.dragMode = 0; NEditor.dragItem = null; //reference to the dragging item NEditor.startPos = null; //Used for starting position of dragging lines NEditor.offsetX = 0; //OffsetX for dragging nodes NEditor.offsetY = 0; //OffsetY for dragging nodes NEditor.svg = null; //SVG where the line paths are drawn. NEditor.pathColor = "#999999"; NEditor.pathColorA = "#86d530"; NEditor.pathWidth = 2; NEditor.pathDashArray = "20,5,5,5,5,5"; NEditor.init = function(){ NEditor.svg = document.getElementById("connsvg"); NEditor.svg.ns = NEditor.svg.namespaceURI; }; /*-------------------------------------------------------- Global Function */ //Trail up the parent nodes to get the X,Y position of an element NEditor.getOffset = function(elm){ var pos = {x:0,y:0}; while(elm){ pos.x += elm.offsetLeft; pos.y += elm.offsetTop; elm = elm.offsetParent; } return pos; }; //Gets the position of one of the connection points NEditor.getConnPos = function(elm){ var pos = NEditor.getOffset(elm); pos.x += (elm.offsetWidth / 2) + 1.5; //Add some offset so its centers on the element pos.y += (elm.offsetHeight / 2) + 0.5; return pos; }; //Used to reset the svg path between two nodes NEditor.updateConnPath = function(o){ var pos1 = o.output.getPos(), pos2 = o.input.getPos(); NEditor.setQCurveD(o.path,pos1.x,pos1.y,pos2.x,pos2.y); }; //Creates an Quadratic Curve path in SVG NEditor.createQCurve = function (x1, y1, x2, y2) { var elm = document.createElementNS(NEditor.svg.ns,"path"); elm.setAttribute("fill", "none"); elm.setAttribute("stroke", NEditor.pathColor); elm.setAttribute("stroke-width", NEditor.pathWidth); elm.setAttribute("stroke-dasharray", NEditor.pathDashArray); NEditor.setQCurveD(elm,x1,y1,x2,y2); return elm; } //This is seperated from the create so it can be reused as a way to update an existing path without duplicating code. NEditor.setQCurveD = function(elm,x1,y1,x2,y2){ var dif = Math.abs(x1-x2) / 1.5, str = "M" + x1 + "," + y1 + " C" + //MoveTo (x1 + dif) + "," + y1 + " " + //First Control Point (x2 - dif) + "," + y2 + " " + //Second Control Point (x2) + "," + y2; //End Point elm.setAttribute('d', str); } NEditor.setCurveColor = function(elm,isActive){ elm.setAttribute('stroke', (isActive)? NEditor.pathColorA : NEditor.pathColor); } /*Unused function at the moment, it creates a straight line NEditor.createline = function (x1, y1, x2, y2, color, w) { var line = document.createElementNS(NEditor.svg.ns, 'line'); line.setAttribute('x1', x1); line.setAttribute('y1', y1); line.setAttribute('x2', x2); line.setAttribute('y2', y2); line.setAttribute('stroke', color); line.setAttribute('stroke-width', w); return line; }*/ /*-------------------------------------------------------- Dragging Nodes */ NEditor.beginNodeDrag = function(n,x,y){ if(NEditor.dragMode != 0) return; NEditor.dragMode = 1; NEditor.dragItem = n; this.offsetX = n.offsetLeft - x; this.offsetY = n.offsetTop - y; window.addEventListener("mousemove",NEditor.onNodeDragMouseMove); window.addEventListener("mouseup",NEditor.onNodeDragMouseUp); }; NEditor.onNodeDragMouseUp = function(e){ e.stopPropagation(); e.preventDefault(); NEditor.dragItem = null; NEditor.dragMode = 0; window.removeEventListener("mousemove",NEditor.onNodeDragMouseMove); window.removeEventListener("mouseup",NEditor.onNodeDragMouseUp); }; NEditor.onNodeDragMouseMove = function(e){ e.stopPropagation(); e.preventDefault(); if(NEditor.dragItem){ NEditor.dragItem.style.left = e.pageX + NEditor.offsetX + "px"; NEditor.dragItem.style.top = e.pageY + NEditor.offsetY + "px"; NEditor.dragItem.ref.updatePaths(); } }; /*-------------------------------------------------------- Dragging Paths */ NEditor.beginConnDrag = function(path){ if(NEditor.dragMode != 0) return; NEditor.dragMode = 2; NEditor.dragItem = path; NEditor.startPos = path.output.getPos(); NEditor.setCurveColor(path.path,false); window.addEventListener("click",NEditor.onConnDragClick); window.addEventListener("mousemove",NEditor.onConnDragMouseMove); }; NEditor.endConnDrag = function(){ NEditor.dragMode = 0; NEditor.dragItem = null; window.removeEventListener("click",NEditor.onConnDragClick); window.removeEventListener("mousemove",NEditor.onConnDragMouseMove); } NEditor.onConnDragClick = function(e){ e.stopPropagation(); e.preventDefault(); NEditor.dragItem.output.removePath(NEditor.dragItem); NEditor.endConnDrag(); }; NEditor.onConnDragMouseMove = function(e){ e.stopPropagation(); e.preventDefault(); if(NEditor.dragItem) NEditor.setQCurveD(NEditor.dragItem.path,NEditor.startPos.x,NEditor.startPos.y,e.pageX,e.pageY); }; /*-------------------------------------------------------- Connection Event Handling */ NEditor.onOutputClick = function(e){ e.stopPropagation(); e.preventDefault(); var path = e.target.parentNode.ref.addPath(); NEditor.beginConnDrag(path); } NEditor.onInputClick = function(e){ e.stopPropagation(); e.preventDefault(); var o = this.parentNode.ref; switch(NEditor.dragMode){ case 2: //Path Drag o.applyPath(NEditor.dragItem); NEditor.endConnDrag(); break; case 0: //Not in drag mode var path = o.clearPath(); if(path != null) NEditor.beginConnDrag(path); break; } } //########################################################################### // Connector Object //########################################################################### //Connector UI Object. Ideally this should be an abstract class as a base for an output and input class, but save time //I wrote this object to handle both types. Its a bit hokey but if it becomes a problem I'll rewrite it in a better OOP way. NEditor.Connector = function(pElm,isInput,name){ this.name = name; this.root = document.createElement("li"); this.dot = document.createElement("i"); this.label = document.createElement("span"); //Input/Output Specific values if(isInput) this.OutputConn = null; //Input can only handle a single connection. else this.paths = []; //Outputs can connect to as many inputs is needed //Create Elements pElm.appendChild(this.root); this.root.appendChild(this.dot); this.root.appendChild(this.label); //Define the Elements this.root.className = (isInput)?"Input":"Output"; this.root.ref = this; this.label.innerHTML = name; this.dot.innerHTML = " "; this.dot.addEventListener("click", (isInput)?NEditor.onInputClick:NEditor.onOutputClick ); }; /*-------------------------------------------------------- Common Methods */ //Get the position of the connection ui element NEditor.Connector.prototype.getPos = function(){ return NEditor.getConnPos(this.dot); } //Just updates the UI if the connection is currently active NEditor.Connector.prototype.resetState = function(){ var isActive = (this.paths && this.paths.length > 0) || (this.OutputConn != null); if(isActive) this.root.classList.add("Active"); else this.root.classList.remove("Active"); } //Used mostly for dragging nodes, so this allows the paths to be redrawn NEditor.Connector.prototype.updatePaths = function(){ if(this.paths && this.paths.length > 0) for(var i=0; i < this.paths.length; i++) NEditor.updateConnPath(this.paths[i]); else if( this.OutputConn ) NEditor.updateConnPath(this.OutputConn); } /*-------------------------------------------------------- Output Methods */ //This creates a new path between nodes NEditor.Connector.prototype.addPath = function(){ var pos = NEditor.getConnPos(this.dot), dat = { path: NEditor.createQCurve(pos.x,pos.y,pos.x,pos.y), input:null, output:this }; NEditor.svg.appendChild(dat.path); this.paths.push(dat); return dat; } //Remove Path NEditor.Connector.prototype.removePath = function(o){ var i = this.paths.indexOf(o); if(i > -1){ NEditor.svg.removeChild(o.path); this.paths.splice(i,1); this.resetState(); } } NEditor.Connector.prototype.connectTo = function(o){ if(o.OutputConn === undefined){ console.log("connectTo - not an input"); return; } var conn = this.addPath(); o.applyPath(conn); } /*-------------------------------------------------------- Input Methods */ //Applying a connection from an output NEditor.Connector.prototype.applyPath = function(o){ //If a connection exists, disconnect it. if(this.OutputConn != null) this.OutputConn.output.removePath(this.OutputConn); //If moving a connection to here, tell previous input to clear itself. if(o.input != null) o.input.clearPath(); o.input = this; //Saving this connection as the input reference this.OutputConn = o; //Saving the path reference to this object this.resetState(); //Update the state on both sides of the connection, TODO some kind of event handling scheme would work better maybe o.output.resetState(); NEditor.updateConnPath(o); NEditor.setCurveColor(o.path,true); } //clearing the connection from an output NEditor.Connector.prototype.clearPath = function(){ if(this.OutputConn != null){ var tmp = this.OutputConn; tmp.input = null; this.OutputConn = null; this.resetState(); return tmp; } } //########################################################################### // Node Object //########################################################################### NEditor.Node = function(sTitle){ this.Title = sTitle; this.Inputs = []; this.Outputs = []; //......................... this.eRoot = document.createElement("div"); document.body.appendChild(this.eRoot); this.eRoot.className = "NodeContainer"; this.eRoot.ref = this; //......................... this.eHeader = document.createElement("header"); this.eRoot.appendChild(this.eHeader); this.eHeader.innerHTML = this.Title; this.eHeader.addEventListener("mousedown",this.onHeaderDown); //......................... this.eList = document.createElement("ul"); this.eRoot.appendChild(this.eList); }; NEditor.Node.prototype.addInput = function(name){ var o = new NEditor.Connector(this.eList,true,name) ; this.Inputs.push(o); return o; } NEditor.Node.prototype.addOutput = function(name){ var o = new NEditor.Connector(this.eList,false,name); this.Outputs.push(o); return o; } NEditor.Node.prototype.getInputPos = function(i){ return NEditor.getConnPos(this.Inputs[i].dot); } NEditor.Node.prototype.getOutputPos = function(i){ return NEditor.getConnPos(this.Outputs[i].dot); } NEditor.Node.prototype.updatePaths = function(){ var i; for(i=0; i < this.Inputs.length; i++) this.Inputs[i].updatePaths(); for(i=0; i < this.Outputs.length; i++) this.Outputs[i].updatePaths(); } //Handle the start node dragging functionality NEditor.Node.prototype.onHeaderDown = function(e){ e.stopPropagation(); NEditor.beginNodeDrag(e.target.parentNode,e.pageX,e.pageY); }; NEditor.Node.prototype.setPosition = function(x,y){ this.eRoot.style.left = x + "px"; this.eRoot.style.top = y + "px"; }; NEditor.Node.prototype.setWidth = function(w){ this.eRoot.style.width = w+"px"; } //########################################################################### // SETUP //########################################################################### window.addEventListener("load",function(e){ NEditor.init(); }); ================================================ FILE: README.md ================================================ # NEditorJS **Youtube Demo** : https://www.youtube.com/watch?v=HpyD2hrySd0 **Live Demo** : http://sketchpunk.com/NEditorJS/ui.html **Purpose**: These are one of those projects you do just for the experience and fun of it, I really have no need for it but who knows, maybe I'll make my own automation software and this will make for a good UI for creating the process flow. I'll probably add on to this from time to time. Note, if you have a need for this sort of interface for some application but you need it to do more, give me a ring maybe we can work together. I'm always open to working on interesting and worthwhile projects. ### Links * [SketchPunk Labs Blog](http://sketchpunklabs.tumblr.com/) * [Support SketchPunk Labs @ Patreon](https://www.patreon.com/sketchpunk) ### License This application is released as in open source application, following the Creative Common - Attribution-NonCommercial Licence. https://creativecommons.org/licenses/by-nc/3.0/us/ ================================================ FILE: ui.html ================================================
Instructions
- Click on an output connecting dots to start a new connection.
- When new connection is made, click on an input connecting dot to complete connection.
- Click and hold node title bars to drag them around.
© 2016 Sketchpunk Labs;