[Javascript]BPMN流程設計器

  • 400
  • 0

還記得剛進入資訊業沒多久就被指派要寫一個流程塑模器,當時為了實作它還需要拼命的尋找第三方現成的套件,評估到最後還要報告到總裁那邊去...經過這幾年的成長沒想到已經可以直接用jQuery+SVG再加上一些邏輯完成流程設計器的基本外框,自己都覺得感動.......

1.引用

jquery.js

jquery-ui.js

2.CSS
.body{
  padding:0px;
  margin:0px;
}

.node{
  position: absolute;
}
.task{
  font-size:12px;
  border : 1px solid black;
  border-radius : 8px;
  width : 100px;
  height: 60px;
  line-height: 60px;
  text-align: center;    
  background-color: #CFF0FB;
}

.event{
  background-color: white;
  width : 30px;
  height: 30px;
  border-radius : 30px;
}

.gateway {
  width:60px; 
  height:60px; 
}

.gateway .shape{
  width: 40px;
  height: 40px;
  border : 1px solid black;
  background-color: #CFF0FB;
  margin-left:30px;
  margin-top:17px;
}

.rotate-45{
  -webkit-transform-origin: 0 100%;
  -moz-transform-origin: 0 100%;
  -o-transform-origin: 0 100%;
  -ms-transform-origin: 0 100%;
  transform-origin: 0 100%;
  -webkit-transform:rotate(-45deg);
  -moz-transform:rotate(-45deg);
  -o-transform:rotate(-45deg);
  -ms-transform:rotate(-45deg);
  transform:rotate(-45deg);
}

.gateway .shape .exclusive-gateway{
  font-size : 60px;
  margin-top : -19px;
  margin-left : 28px;
}
.start-event{
  border : 2px solid black;
}

.end-event{
  border : 6px solid black;
}

#diagram{
  position: absolute;
  top: 0px;
  left: 0px;
}

#linkDiagram{
  position: absolute;
  top: 0px;
  left: 0px;
}

 

3.HTML
<body>
  <!--連接線的區塊 --> 
  <svg id="linkDiagram" width="1600" height="1600">   
  </svg>   
   
  <!--所有圖形元件的區塊-->
  <div id="diagram" style="width:1600px; height:1600px;">  
  </div>   
</body>

 

4.JS

$(function() {  
  //建立任務與事件
  createEvent('startEvent_1', 50, 165 , 'start-event');
  createTask('task_1', 200, 150, '發起者');
  createTask('task_2', 350, 150, '直屬主管');
  createGateway('exclusive_gateway_1', 500, 150, 'exclusive');
  createTask('task_3', 650, 50, '總經理');
  createTask('task_4', 850, 150, '財務群組');
  createEvent('endEvent_1', 1050, 165 , 'end-event');
    
  //建立連結線  
  createLink('startEvent_1', 'task_1');
  createLink('task_1', 'task_2');
  createLink('task_2', 'exclusive_gateway_1');
  createLink('exclusive_gateway_1', 'task_3');
  createLink('exclusive_gateway_1', 'task_4');
  createLink('task_3', 'task_4');
  createLink('task_4', 'endEvent_1');

});
  
// 將元件綁定拖曳等事件
function bindDragEvent(pNodeId){
  $("#"+pNodeId).draggable({
    start: function( event, ui ) {
  },
  stop: function( event, ui ) {
    refreshLink();
  },
  drag:function( event, ui ) {
    refreshLink();
  },
  grid: [ 5, 5 ]
  }).mousedown(function(){
    
  });
}

// 重新刷新線段邏輯
function refreshLink(){
  for(var i=0; i<gAllLinks.length; i++){
    var tLink = gAllLinks[i];
    var tFormNodeId = tLink.formId;
    var tToNodeId = tLink.toId;
    drawLink(tFormNodeId, tToNodeId);
  }
}

// 建立事件元件
function createEvent(pNodeId, pLeft, pTop, pType){  
  var tNode =   
    '<div class="event node '+pType+'" id="'+pNodeId+'">'+
    '</div>';
  tNode = $(tNode);  
  tNode.css({
    'top': pTop + 'px',
    'left' : pLeft + 'px'
  });
    
  $("#diagram").append(tNode);
  bindDragEvent(pNodeId);
}

// 建立任務元件
function createTask(pNodeId, pLeft, pTop, pContent){
  var tNode =   
    '<div class="node task" id="'+pNodeId+'">'+
      pContent+
    '</div>';
  tNode = $(tNode);  
  tNode.css({
    'top': pTop + 'px',
    'left' : pLeft + 'px'
  });
    
  $("#diagram").append(tNode);
  bindDragEvent(pNodeId);
}

// 建立閘道元件
function createGateway(pNodeId, pLeft, pTop, pType){  
  var tHtml = '';

  if(pType === 'exclusive'){
    tHtml = '<div class="exclusive-gateway rotate-45">+</div>';
  }
  var tNode =
    '<div id="'+pNodeId+'" class="node gateway">'+
      '<div class="shape rotate-45">'+
        tHtml+
      '</div>'+
    '</div>';
    tNode = $(tNode);  
    tNode.css({
      'top': pTop + 'px',
      'left' : pLeft + 'px'
    });
    
    $("#diagram").append(tNode);
    bindDragEvent(pNodeId);
}

var gAllLinks = [];

// 建立連接線
function createLink(pFormNodeId, pToNodeId){
  var tLineId = pFormNodeId+'_'+pToNodeId;
  var tLine = document.createElementNS("http://www.w3.org/2000/svg", "polyline");
  tLine = $(tLine);
  tLine.attr({
    id : tLineId
  }).css({
    'fill' : 'none',
    'stroke' : 'black', 
    'stroke-width' : 1
  });
  $("#linkDiagram").append(tLine);
  
  // 產生連接線的箭頭
  var tArrowId = "arrow_"+tLineId;
  var tArrow = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
  tArrow = $(tArrow);
  tArrow.attr({
    id : tArrowId
  }).css({
    'fill' : 'black'
  });
  $("#linkDiagram").append(tArrow);
  
  drawLink(pFormNodeId, pToNodeId);
  
  gAllLinks.push({
    'formId' : pFormNodeId,
    'toId' : pToNodeId
  });
}

// 判斷連接線的模式
function findLineMode(pFormNode, pToNode){
  var tFormNodeWidth = parseInt(pFormNode.css('width').replace('px',''));
  var tFormNodeHeight = parseInt(pFormNode.css('height').replace('px',''));
  var tFormNodeTop = parseInt(pFormNode.css('top').replace('px',''));
  var tFormNodeLeft = parseInt(pFormNode.css('left').replace('px',''));
  
  var tToNodeWidth = parseInt(pToNode.css('width').replace('px',''));
  var tToNodeHeight = parseInt(pToNode.css('height').replace('px',''));
  var tToNodeTop = parseInt(pToNode.css('top').replace('px',''));
  var tToNodeLeft = parseInt(pToNode.css('left').replace('px',''));
  
  var tMode = '';
  if(tFormNodeLeft+tFormNodeWidth <= tToNodeLeft){
    tMode = 'RIGHT';  //目標在右側
  }else if (tToNodeLeft+tToNodeWidth <= tFormNodeLeft){
    tMode = 'LEFT';  //目標在左側
  }else if (tFormNodeLeft >= tToNodeLeft && tFormNodeLeft <= (tToNodeLeft+tToNodeWidth)){
    if (tToNodeTop >= tFormNodeTop+tToNodeHeight){
      tMode = 'BOTTOM';  //目標在下方
    } else {
      tMode = 'TOP';  //目標在上方
    }    
  }else{    
    if (tToNodeTop + tToNodeHeight >= tFormNodeTop){
      tMode = 'BOTTOM';  //目標在下方
    } else {
      tMode = 'TOP';  //目標在上方
    }    
  }
  return tMode;
}

// 劃出連接線
function drawLink(pFormNodeId, pToNodeId){
	
  // 將元件與元件的關係區分四種情境
  // 1.預連接的元件在此元件的右方 (RIGHT模式)
  // 2.預連接的元件在此元件的左方 (LEFT模式)
  // 3.預連接的元件在此元件的上方 (TOP模式)
  // 4.預連接的元件在此元件的下方 (BOTTOM模式)
  
  var tFormNode = $("#"+pFormNodeId);
  var tToNode = $("#"+pToNodeId);
  
  var tMode = findLineMode(tFormNode, tToNode);
  var tStartPointObj;
  var tEndPointObj;

  //線的四個節點
  var tStartPoint;
  var tBreakPoint_1;
  var tBreakPoint_2;
  var tEndPoint;
  
  //畫箭頭的三個節點
  var tArrowPoint_1;
  var tArrowPoint_2;
  var tArrowPoint_3;
    
  var tArrowSize = 10;
  if(tMode === 'RIGHT' || tMode === 'LEFT'){
    if(tMode === 'RIGHT'){
      tStartPointObj = getRightPoint(tFormNode);
      tEndPointObj = getLeftPoint(tToNode);
      
      tArrowPoint_1 = tEndPointObj.left + ',' + tEndPointObj.top;
      tArrowPoint_2 = (tEndPointObj.left-10) + ',' + (tEndPointObj.top+10);
      tArrowPoint_3 = (tEndPointObj.left-10) + ',' + (tEndPointObj.top-10);
    }else if(tMode === 'LEFT'){
      tStartPointObj = getLeftPoint(tFormNode);
      tEndPointObj = getRightPoint(tToNode);
      
      tArrowPoint_1 = tEndPointObj.left + ',' + tEndPointObj.top;
      tArrowPoint_2 = (tEndPointObj.left+10) + ',' + (tEndPointObj.top+10);
      tArrowPoint_3 = (tEndPointObj.left+10) + ',' + (tEndPointObj.top-10);
    }    
    tStartPoint = tStartPointObj.left + ',' + tStartPointObj.top;
    tBreakPoint_1 = (tStartPointObj.left + (tEndPointObj.left - tStartPointObj.left)/2) + ',' + tStartPointObj.top;
    tBreakPoint_2 = (tStartPointObj.left + (tEndPointObj.left - tStartPointObj.left)/2) + ',' + tEndPointObj.top;
    tEndPoint = tEndPointObj.left + ',' + tEndPointObj.top;
  } else if (tMode === 'TOP' || tMode === 'BOTTOM'){
    if(tMode === 'TOP'){
      tStartPointObj = getTopPoint(tFormNode);    
      tEndPointObj = getBottomPoint(tToNode);
      
      tArrowPoint_1 = tEndPointObj.left + ',' + tEndPointObj.top;
      tArrowPoint_2 = (tEndPointObj.left+10) + ',' + (tEndPointObj.top+10);
      tArrowPoint_3 = (tEndPointObj.left-10) + ',' + (tEndPointObj.top+10);
    }else if(tMode === 'BOTTOM'){
      tStartPointObj = getBottomPoint(tFormNode);
      tEndPointObj = getTopPoint(tToNode);
      
      tArrowPoint_1 = tEndPointObj.left + ',' + tEndPointObj.top;
      tArrowPoint_2 = (tEndPointObj.left+10) + ',' + (tEndPointObj.top-10);
      tArrowPoint_3 = (tEndPointObj.left-10) + ',' + (tEndPointObj.top-10);
    }
    tStartPoint = tStartPointObj.left + ',' + tStartPointObj.top;
    tBreakPoint_1 = tStartPointObj.left+ ',' + ( tEndPointObj.top + (tStartPointObj.top - tEndPointObj.top)/2 );
    tBreakPoint_2 = tEndPointObj.left + ',' + ( tEndPointObj.top + (tStartPointObj.top - tEndPointObj.top)/2 );
    tEndPoint = tEndPointObj.left + ',' + tEndPointObj.top;
  } 

  var tLineId = pFormNodeId+'_'+pToNodeId;
  var tLine = $("#"+tLineId);
  tLine.attr({
    points :  tStartPoint + ' ' +tBreakPoint_1 + ' ' + tBreakPoint_2 + ' ' + tEndPoint
  });
  
  var tArrow = $("#arrow_"+tLineId);
  tArrow.attr({
    points :  tArrowPoint_1 + ' ' +tArrowPoint_2 + ' ' + tArrowPoint_3
  });
}

// 找出元件右側的中心點座標
function getRightPoint(pNode){
  var tNodeWidth = parseInt(pNode.css('width').replace('px',''));
  var tNodeHeight = parseInt(pNode.css('height').replace('px',''));
  var tNodeTop = parseInt(pNode.css('top').replace('px',''));
  var tNodeLeft = parseInt(pNode.css('left').replace('px',''));
  
  return {
    left : tNodeLeft + tNodeWidth,
    top : tNodeTop + (tNodeHeight/2)    
  };
}

// 找出元件左側的中心點座標
function getLeftPoint(pNode){
  var tNodeWidth = parseInt(pNode.css('width').replace('px',''));
  var tNodeHeight = parseInt(pNode.css('height').replace('px',''));
  var tNodeTop = parseInt(pNode.css('top').replace('px',''));
  var tNodeLeft = parseInt(pNode.css('left').replace('px',''));
  
  return {
    left : tNodeLeft,
    top : tNodeTop + (tNodeHeight/2)    
  };
}

// 找出元件上方的中心點座標
function getTopPoint(pNode){
  var tNodeWidth = parseInt(pNode.css('width').replace('px',''));
  var tNodeHeight = parseInt(pNode.css('height').replace('px',''));
  var tNodeTop = parseInt(pNode.css('top').replace('px',''));
  var tNodeLeft = parseInt(pNode.css('left').replace('px',''));
  
  return {
    left : tNodeLeft + (tNodeWidth/2),
    top : tNodeTop    
  };
}

// 找出元件下方的中心點座標
function getBottomPoint(pNode){
  var tNodeWidth = parseInt(pNode.css('width').replace('px',''));
  var tNodeHeight = parseInt(pNode.css('height').replace('px',''));
  var tNodeTop = parseInt(pNode.css('top').replace('px',''));
  var tNodeLeft = parseInt(pNode.css('left').replace('px',''));
  
  return {
    left : tNodeLeft + (tNodeWidth/2),
    top : tNodeTop + tNodeHeight
  };
}