8月
28
2007
分類:
最近更新:
2007-08-28
WebFlow UserInterface
流程編輯器。使用 JavaScript 實作的使用者介面,未附伺服端儲存與載入功能源碼。
使用 wz_jsgraphics.js 繪製線條。當時曾試過 SVG ,但效果與瀏覽器相容性皆不理想,所以還是用 wz_jsgraphics.js 。它是以 1px 大小的 div node 為畫素,構成圖形。
操作圖例:
FlowNode.js
Flow object。資料結構。
/*
Copyright (c) 2006 Shih Yuncheng. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License (LGPL) as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
*/
function NodeIndex(l, n) {
this.level = l;
this.node = n;
return this;
}
NodeIndex.prototype.toString = function() {
return "(" + this.level + "," + this.node + ")";
}
function FlowNode(name) {
this.name = name;
this.items = new Array();
//this.items.push(null); //每一新節點的會辦者為 END
//this.backs = new Array();
return this;
}
FlowNode.prototype.toString = function() {
return this.name;
}
//每一層級至少有一節點。
function FlowLevel(name) {
this.nodes = new Array();
this.nodes.push(new FlowNode(name));
return this;
}
FlowLevel.prototype.addNode = function(name) {
var nodes = this.nodes;
//nodes[nodes.length] = new FlowNode(name);
for(var i = 0; i < nodes.length; i++) {
if(nodes[i].name == name)
return nodes[i];
}
var node = new FlowNode(name);
nodes.push(node);
return node;
}
/*
Flow
*/
function Flow() {
this.levels = new Array();
this.levels.push(new FlowLevel("")); // Node END, Flow.levels[0]
return this;
}
Flow.prototype.getNodeIndex = function(node) {
var levels = this.levels;
if(node == null || node.name == "")
return new NodeIndex(0,0);
var nodes;
for(var i=1; i < levels.length; i++) {
nodes = levels[i].nodes;
var j = this.arrayHasItem(nodes, node);
if( j >=0 ) {
return new NodeIndex(i,j);
}
}
return null;
}
Flow.prototype.getNodeByName = function(nodeName) {
var levels = this.levels;
if(nodeName == null || nodeName == "")
return levels[0].nodes[0]/*null*/;
var nodes;
for(var i=0; i < levels.length; i++) {
nodes = levels[i].nodes;
for(var j = 0; j < nodes.length; j++) {
if(nodes[j].name == nodeName) {
return nodes[j];
}
}
}
return null;
}
/*
Flow.prototype.removeNodeFromItem = function(node) {
if(node == null)
return;
var levels = this.levels;
var nodes;
for(var i=1; i < levels.length; i++) {
nodes = levels[i].nodes;
for(var j = 0; j < nodes.length; j++) {
this.removeItem(i, j, node);
}
}
}
*/
Flow.prototype.replaceNodeInItems = function(node, replaceWithItems) {
if(node == null)
return;
var levels = this.levels;
var nodes;
for(var i=1; i < levels.length; i++) {
nodes = levels[i].nodes;
for(var j = 0; j < nodes.length; j++) {
for(var k = 0; k < replaceWithItems.length; k++) {
if(this.hasItem(i,j,node) >= 0) {
this.addItem(i, j, replaceWithItems[k]);
}
}
this.removeItem(i, j, node);
}
}
}
Flow.prototype.insertLevel = function(indexOfLevel, name, fromNode) {
var levels = this.levels;
if(indexOfLevel > levels.length)
return null;
if(this.getNodeByName(name) != null)
return null; //已存在 (可能在不同層級)
levels.splice(indexOfLevel, 0, new FlowLevel(name));
var node = levels[indexOfLevel].nodes[0];
if(fromNode != null) {
//node.items.pop();
// remove the Node END; 新層級會包含一個新節點,而新節點的會辦者預設為 END
while(fromNode.items.length >0) {
node.items.push(fromNode.items.pop());
//新層級之新節點之會辦者,為處理者的會辦者。
}
fromNode.items.push(node);
//將新節點加入處理者的會辦者中
}
return levels[indexOfLevel];
}
Flow.prototype.removeLevel = function(indexOfLevel) {
var levels = this.levels;
if(indexOfLevel != levels.length - 1)
return false;
// NOTE: 層級移除策略未定。暫定只能移除最後一個層級。
if(indexOfLevel > levels.length)
return false;
var level = levels[indexOfLevel];
if(level.nodes.length > 1)
return false;
//只有一個參與者時,才可移除層級
var node = level.nodes[0];
this.replaceNodeInItems(node, node.items);
this.levels.splice(indexOfLevel, 1);
return true;
}
Flow.prototype.addNode = function(indexOfLevel, name, fromNode) {
var levels = this.levels;
if(indexOfLevel > levels.length)
return null;
if(this.getNodeByName(name) != null) {
return null; //已存在 (可能在不同層級)
}
else {
var node = this.levels[indexOfLevel].addNode(name);
if(fromNode) {
fromNode.items.push(node);
}
//node.items.push(new NodeIndex(0,0));
return node;
}
}
Flow.prototype.removeNode = function(indexOfLevel, indexOfNode) {
if(indexOfLevel > this.levels.length || indexOfNode > this.levels[indexOfLevel].nodes.length)
return false;
if(this.levels[indexOfLevel].nodes.length <= 1)
return false; //一個層級至少要有一個節點。
var node = this.levels[indexOfLevel].nodes[indexOfNode];
//只有一個會辦者,且其為 END ,才可移除
if(node.items.length > 1) {
return false;
}
else {//if(node.items.length <= 1 /*&& node.items[0] == null*/) {
this.replaceNodeInItems(node, node.items);
this.levels[indexOfLevel].nodes.splice(indexOfNode, 1);
}
return true;
}
Flow.prototype.updateNode = function(indexOfLevel, indexOfNode, nodeData) {
if(indexOfLevel > this.levels.length || indexOfNode > this.levels[indexOfLevel].nodes.length)
return false;
if(this.getNodeByName(name) == null) {
return false; //不存在
}
var node = this.levels[indexOfLevel].nodes[indexOfNode];
var oldNode = new FlowNode(node.name);
if(nodeData['name']) {
node.name = nodeData['name'];
}
this.replaceNodeInItems(oldNode, [node]);
return true;
}
Flow.prototype.arrayHasItem = function(ar, item) {
for(var i = 0; i < ar.length; i++) {
if(ar[i] == null && (item == null || item.name == ""))
return i;
else if(ar[i] != null && item != null) {
if(ar[i].name == item.name)
return i;
}
}
return -1;
}
Flow.prototype.hasItem = function(indexOfLevel, indexOfNode, item) {
if(indexOfLevel > this.levels.length || indexOfNode > this.levels[indexOfLevel].nodes.length)
return -1;
return this.arrayHasItem(this.levels[indexOfLevel].nodes[indexOfNode].items, item);
}
Flow.prototype.addItem = function(indexOfLevel, indexOfNode, item) {
if(indexOfLevel > this.levels.length || indexOfNode > this.levels[indexOfLevel].nodes.length)
return false;
if(this.levels[indexOfLevel].nodes[indexOfNode] == null)
return false;
var items = this.levels[indexOfLevel].nodes[indexOfNode].items;
if(this.hasItem(indexOfLevel, indexOfNode, item) <= -1) {
items.push(item);
}
return true;
}
Flow.prototype.removeItem = function(indexOfLevel, indexOfNode, item) {
if(indexOfLevel > this.levels.length || indexOfNode > this.levels[indexOfLevel].nodes.length)
return false;
var items = this.levels[indexOfLevel].nodes[indexOfNode].items;
if(items.length <= 1)
return false;
if(items.length <= 1 && items[0] && items[0].name == "")
return false;
var i = this.hasItem(indexOfLevel, indexOfNode, item);
if(i >= 0) {
items.splice(i, 1);
}
return true;
}
/*
Flow.prototype.nodeEnd = function() {
//return this.levels[0].nodes[0];
return null;
}
*/
WebFlow.js
流程編輯功能。源碼不展開。
/*
Copyright (c) 2006 Shih Yuncheng. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License (LGPL) as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
*/
function hiddenFlowMenu1() {
document.getElementById("FlowMenu1").style.visibility = "hidden";
}
function mouseOverMenuItem(menuItem) {
menuItem.className = "MenuItemMouseOver";
}
function mouseOutMenuItem(menuItem) {
menuItem.className = "MenuItem";
}
function showFlowMenu1(obj, l, n) {
var menu = document.getElementById("FlowMenu1");
if(currentNodeIndex.level != l || currentNodeIndex.node != n || menu.style.visibility == 'hidden') {
setCurrentNode(l,n);
menu.innerHTML = "關卡: " + flow.levels[l].nodes[n]; //+ "(" + l + "," + n + ")";
menu.innerHTML += "<ul>";
menu.innerHTML += "<li onClick='showAddItem();' onMouseOver='mouseOverMenuItem(this);' onMouseOut='mouseOutMenuItem(this);'>新增關卡</li>";
menu.innerHTML += "<li onClick='showUpdateNode();' onMouseOver='mouseOverMenuItem(this);' onMouseOut='mouseOutMenuItem(this);'>修改關卡</li>";
menu.innerHTML += "<li onClick='showRemoveNode();' onMouseOver='mouseOverMenuItem(this);' onMouseOut='mouseOutMenuItem(this);'>刪除關卡</li>";
menu.innerHTML += "<li onClick='showRemoveItem();' onMouseOver='mouseOverMenuItem(this);' onMouseOut='mouseOutMenuItem(this);'>移除會辦者</li>";
menu.innerHTML += "</ul>";
layoutToElement("FlowMenu1", obj);
menu.style.visibility = 'visible';
}
else {
menu.style.visibility = 'hidden';
}
}
function layoutToElement(objId, layoutToObj) {
var obj = document.getElementById(objId);
if(obj) {
var cs = obj.style;
//if(cs.visibility == 'hidden') {
var o = layoutToObj;
var x=0;
var y=0;
while(o.offsetParent) {
x+=o.offsetLeft;
y+=o.offsetTop;
o=o.offsetParent;
}
//cs.top=(y+layoutToObj.offsetHeight)+'px';
//cs.left=x+'px';
cs.top=y+'px';
cs.left=(x+layoutToObj.offsetWidth)+'px';
// cs.visibility='visible';
//}
//else {
// cs.visibility='hidden';
//}
}
}
function setCurrentNode(l, n) {
currentNodeIndex.level = l;
currentNodeIndex.node = n;
currentNode = flow.levels[l].nodes[n];
}
function getNewLevelName() {
var textbox = document.getElementById("FlowMenuAddNewLevel").getElementsByName("nodeName")[0];
return textbox.value;
}
function cancleAddNewLevel() {
var obj = document.getElementById("FlowMenuAddNewLevel");
var textbox = obj.getElementsByTagName("input")[0];
textbox.value = "";
hiddenFlowMenu1();
obj.style.visibility = "hidden";
}
function submitAddNewLevel() {
var obj = document.getElementById("FlowMenuAddNewLevel");
var textbox = obj.getElementsByTagName("input")[0];
//textbox.value = "";
//layoutToElement("FlowMenuAddNewLevel", document.getElementById("FlowMenu1"));
hiddenFlowMenu1();
obj.style.visibility = "hidden";
if(textbox.value != "") {
flow.insertLevel(currentNodeIndex.level+1, textbox.value, currentNode);
renderFlowTable();
}
}
function showAddNewLevel() {
var obj = document.getElementById("FlowMenuAddNewLevel");
var textbox = obj.getElementsByTagName("input")[0];
textbox.value = "";
layoutToElement("FlowMenuAddNewLevel", document.getElementById("FlowMenu1"));
obj.style.visibility = "visible";
textbox.focus();
}
function getNewNodeName() {
var textbox = document.getElementById("FlowMenuAddNewNode").getElementsByName("nodeName")[0];
return textbox.value;
}
function cancleAddNewNode() {
var obj = document.getElementById("FlowMenuAddNewNode");
var textbox = obj.getElementsByTagName("input")[0];
textbox.value = "";
hiddenFlowMenu1();
obj.style.visibility = "hidden";
}
function submitAddNewNode() {
var obj = document.getElementById("FlowMenuAddNewNode");
var textbox = obj.getElementsByTagName("input")[0];
//textbox.value = "";
//layoutToElement("FlowMenuAddNewLevel", document.getElementById("FlowMenu1"));
hiddenFlowMenu1();
obj.style.visibility = "hidden";
if(textbox.value != "") {
var rc;
if(currentNodeIndex.level >= flow.levels.length -1 ) {
rc = flow.insertLevel(currentNodeIndex.level+1, textbox.value, currentNode);
}
else {
rc = flow.addNode(currentNodeIndex.level+1, textbox.value, currentNode);
}
if(rc == null) {
alert("不允許新增,可能已存在。");
}
else {
renderFlowTable();
}
}
}
function hideAddNewNode() {
var obj = document.getElementById("FlowMenuAddNewNode");
obj.style.visibility = "hidden";
bodyControler.removeClickHandler(hideAddNewNode);
}
function showAddNewNode() {
var obj = document.getElementById("FlowMenuAddNewNode");
var textbox = obj.getElementsByTagName("input")[0];
textbox.value = "";
layoutToElement("FlowMenuAddNewNode", document.getElementById("FlowMenu1"));
obj.style.visibility = "visible";
textbox.focus();
}
function cancleUpdateNode() {
var obj = document.getElementById("FlowMenuUpdateNode");
var textbox = obj.getElementsByTagName("input")[0];
textbox.value = "";
hiddenFlowMenu1();
obj.style.visibility = "hidden";
}
function submitUpdateNode() {
var obj = document.getElementById("FlowMenuUpdateNode");
var textbox = obj.getElementsByTagName("input")[0];
//textbox.value = "";
//layoutToElement("FlowMenuAddNewLevel", document.getElementById("FlowMenu1"));
hiddenFlowMenu1();
obj.style.visibility = "hidden";
if(textbox.value != "" && textbox.value != currentNode.name) {
var rc;
rc = flow.updateNode(currentNodeIndex.level, currentNodeIndex.node, {name:textbox.value});
renderFlowTable();
}
}
function showUpdateNode() {
var obj = document.getElementById("FlowMenuUpdateNode");
var textbox = obj.getElementsByTagName("input")[0];
textbox.value = currentNode.name;
layoutToElement("FlowMenuUpdateNode", document.getElementById("FlowMenu1"));
obj.style.visibility = "visible";
textbox.focus();
}
function getItemName() {
var textbox = document.getElementById("FlowMenuAddItem").getElementsByName("nodeName")[0];
return textbox.value;
}
function cancleAddItem() {
var obj = document.getElementById("FlowMenuAddItem");
var textbox = obj.getElementsByTagName("input")[0];
textbox.value = "";
hiddenFlowMenu1();
obj.style.visibility = "hidden";
}
function submitAddItem() {
var obj = document.getElementById("FlowMenuAddItem");
var textbox = obj.getElementsByTagName("input")[0];
//textbox.value = "";
//layoutToElement("FlowMenuAddNewLevel", document.getElementById("FlowMenu1"));
hiddenFlowMenu1();
obj.style.visibility = "hidden";
var rc;
if(flow.getNodeByName(textbox.value) == null) {
if(currentNodeIndex.level >= flow.levels.length -1 ) {
rc = flow.insertLevel(currentNodeIndex.level+1, textbox.value, currentNode);
}
else {
rc = flow.addNode(currentNodeIndex.level+1, textbox.value, currentNode);
}
}
else {
flow.addItem(currentNodeIndex.level, currentNodeIndex.node, flow.getNodeByName(textbox.value));
}
renderFlowTable();
}
function showAddItem() {
var obj = document.getElementById("FlowMenuAddItem");
var textbox = obj.getElementsByTagName("input")[0];
textbox.value = "";
var nodeList = obj.getElementsByTagName("select")[0];
while(nodeList.options.length > 0) {
nodeList.remove(0);
}
nodeList.options[0] = new Option("請選擇", "0");
for(var i = 1; i < flow.levels.length; i++) {
for(var j = 0; j < flow.levels[i].nodes.length; j++) {
if(flow.levels[currentNodeIndex.level].nodes[currentNodeIndex.node].name
!= flow.levels[i].nodes[j].name
&& flow.hasItem(currentNodeIndex.level,currentNodeIndex.node,flow.levels[i].nodes[j]) <= -1) {
nodeList.options[nodeList.options.length] = new Option(flow.levels[i].nodes[j].name, flow.levels[i].nodes[j].name);
}
}
}
nodeList.options[nodeList.options.length] = new Option("結案", "");
layoutToElement("FlowMenuAddItem", document.getElementById("FlowMenu1"));
obj.style.visibility = "visible";
textbox.focus();
}
function cancleRemoveItem() {
var obj = document.getElementById("FlowMenuRemoveItem");
var textbox = obj.getElementsByTagName("input")[0];
textbox.value = "";
hiddenFlowMenu1();
obj.style.visibility = "hidden";
}
function submitRemoveItem() {
var obj = document.getElementById("FlowMenuRemoveItem");
var textbox = obj.getElementsByTagName("input")[0];
//textbox.value = "";
//layoutToElement("FlowMenuAddNewLevel", document.getElementById("FlowMenu1"));
hiddenFlowMenu1();
obj.style.visibility = "hidden";
//if(textbox.value != "") {
//window.alert(flow.getNodeByName(textbox.value));
flow.removeItem(currentNodeIndex.level, currentNodeIndex.node, flow.getNodeByName(textbox.value));
renderFlowTable();
//}
}
function showRemoveItem() {
var obj = document.getElementById("FlowMenuRemoveItem");
var textbox = obj.getElementsByTagName("input")[0];
textbox.value = "";
var nodeList = obj.getElementsByTagName("select")[0];
while(nodeList.options.length > 0) {
nodeList.remove(0);
}
nodeList.options[0] = new Option("請選擇", "0");
for(var i = 0; i < currentNode.items.length; i++) {
if(currentNode.items[i] == null || currentNode.items[i].name == "") {
nodeList.options[nodeList.options.length] = new Option("結案", "");
}
else {
nodeList.options[nodeList.options.length] = new Option(currentNode.items[i].name, currentNode.items[i].name);
}
}
layoutToElement("FlowMenuRemoveItem", document.getElementById("FlowMenu1"));
obj.style.visibility = "visible";
textbox.focus();
}
function cancleRemoveNode() {
var obj = document.getElementById("FlowMenuRemoveNode");
//var textbox = obj.getElementsByTagName("input")[0];
//textbox.value = "";
hiddenFlowMenu1();
obj.style.visibility = "hidden";
}
function submitRemoveNode() {
var obj = document.getElementById("FlowMenuRemoveNode");
var textbox = obj.getElementsByTagName("input")[0];
//textbox.value = "";
//layoutToElement("FlowMenuAddNewLevel", document.getElementById("FlowMenu1"));
obj.style.visibility = "hidden";
hiddenFlowMenu1();
if(currentNodeIndex.level <= 1)
return;
if(flow.levels[currentNodeIndex.level].nodes.length <= 1) {
flow.removeLevel(currentNodeIndex.level);
}
else {
flow.removeNode(currentNodeIndex.level, currentNodeIndex.node);
}
renderFlowTable();
}
function showRemoveNode() {
var obj = document.getElementById("FlowMenuRemoveNode");
//var textbox = obj.getElementsByTagName("input")[0];
//textbox.value = "";
layoutToElement("FlowMenuRemoveNode", document.getElementById("FlowMenu1"));
obj.style.visibility = "visible";
}
function renderFlowTable() {
var div = document.getElementById("div1");
var offsetX=0;
var offsetY=0;
var o = div;
while(o.offsetParent) {
offsetX += o.offsetLeft;
offsetY += o.offsetTop;
o = o.offsetParent;
}
var nodes = new Array( flow.levels.length);
var maxNodes = 1;
var maxWidth = (20+80) * maxNodes - 20;
for(var i=0; i < flow.levels.length; i++) {
nodes[i] = new Array(flow.levels[i].nodes.length);
for(var j=0; j < flow.levels[i].nodes.length; j++) {
nodes[i][j] = document.createElement("div");
nodes[i][j].className = "FlowNode";
}
if(maxNodes < flow.levels[i].nodes.length) {
maxNodes = flow.levels[i].nodes.length;
maxWidth = (20+80) * maxNodes - 20;
}
}
for(var i=0; i < flow.levels.length; i++) {
nodes[i].offsetLeft = offsetX +((maxWidth - ((20+80)*flow.levels[i].nodes.length - 20)) / 2);
}
jg_doc.clear();
/*for(var i=0; i < div.childNodes.length; i++) {
div.removeChild(div.childNodes[i]);
}*/
nodes[0].offsetLeft = offsetX + ((maxWidth - ((20+80)*flow.levels[0].nodes.length - 20)) / 2);
nodes[0][0].innerHTML = "<input type='button' value='結案'/>";
nodes[0][0].style.top = offsetY + ((flow.levels.length-1) * (50 + 40)) + 'px';
nodes[0][0].style.left = offsetX + (nodes[0].offsetLeft + (20 + 80)) + 'px';
nodes[0][0].getElementsByTagName("input")[0].style['width'] = 80 + 'px';
nodes[0][0].getElementsByTagName("input")[0].style['height'] = 40 + 'px';
div.appendChild(nodes[0][0]);
for(var i = 1; i < flow.levels.length; i++) {
for(var j = 0; j < flow.levels[i].nodes.length; j++) {
nodes[i][j].innerHTML = "<input type='button' value='"
+ flow.levels[i].nodes[j] + "' onClick='showFlowMenu1(this,"+i+","+j+");'/>";//+ flow.levels[i].nodes[j].items.join(",");
if(nodes[i][j].addEventListener) {
//nodes[i][j].addEventListener("mouseover", function(event){this.className = 'FlowMenuItemMouseOver';}, false);
//nodes[i][j].addEventListener("mouseout", function(event){this.className = 'FlowMenuItem';}, false);
}
else { // M$IE
//nodes[i][j].onmouseover = function(){this.className = 'FlowMenuItemMouseOver';};
//nodes[i][j].onmouseout = function(){this.className = 'FlowMenuItem';};
}
nodes[i][j].style.top = offsetY +((i-1) * (50 + 40)) + 'px';
nodes[i][j].style.left = offsetX +(nodes[i].offsetLeft + (j+1) * (20 + 80)) + 'px';
nodes[i][j].getElementsByTagName("input")[0].style['width'] = 80 + 'px';
nodes[i][j].getElementsByTagName("input")[0].style['height'] = 40 + 'px';
div.appendChild(nodes[i][j]);
}
}
//var EndNodeIndex = new NodeIndex(flow.levels.length,0);
jg_doc.setColor("#0000ff"); // green
//jg_doc.drawLine(100, 50, 200, 400);
for(var i = 1; i < flow.levels.length; i++) {
//var currentLevelLeft = (maxWidth - ((20+80)*flow.levels[i].nodes.length - 20)) / 2;
for(var j = 0; j < flow.levels[i].nodes.length; j++) {
var item;
var nodeIndex;
for(var k = 0; k < flow.levels[i].nodes[j].items.length; k++) {
item = flow.levels[i].nodes[j].items[k];
if(item == null || item.name=="") {
//nodeIndex = EndNodeIndex;
jg_doc.drawLine(
offsetX+(nodes[i].offsetLeft+(j+1) * (20 + 80) + 40),
offsetY+((i-1)*(50 + 40)+40),
offsetX+(nodes[0].offsetLeft+(0+1)*(20 + 80)+40),
offsetY+((flow.levels.length-1) * (50+40)));
}
else {
nodeIndex = flow.getNodeIndex(item);
if(nodeIndex.level > i) {
jg_doc.drawLine(
offsetX+(nodes[i].offsetLeft+(j+1) * (20 + 80) + 40),
offsetY+((i-1)*(50 + 40)+40),
offsetX+(nodes[nodeIndex.level].offsetLeft+(nodeIndex.node+1)*(20 + 80)+40),
offsetY+((nodeIndex.level-1) * (50+40)));
}
else {
jg_doc.drawLine(
offsetX+(nodes[i].offsetLeft+(j+1) * (20 + 80) + 40),
offsetY+((i-1)*(50 + 40)),
offsetX+(nodes[nodeIndex.level].offsetLeft+(nodeIndex.node+1)*(20 + 80)+40),
offsetY+((nodeIndex.level-1) * (50+40)+40));
}
}
//window.alert(item + nodeIndex);
}
}
}
jg_doc.paint(); // draws, in this case, directly into the document
}
FlowTableSubmit.js
送出編輯內容。此處僅將結果顯示於網頁上,並未送回伺服端。請依實作規格自行補上伺服端的部份。
我當時是把內容轉成 JSON 格式後送回伺服端儲存。伺服端是以 C#/ASP.Net 實作。我並未擁有伺服端的著作權,故不發佈伺服端源碼。
/*
Copyright (c) 2006 Shih Yuncheng. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License (LGPL) as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
*/
function openString() {
document.getElementById("div1").innerHTML = "<ol>";
}
function closeString() {
document.getElementById("div1").innerHTML += "</ol>";
}
function addString(v) {
var div1 = document.getElementById("div1");
div1.innerHTML += "<li>" + v.name + "<br/> Items: ";
for(var i = 0; i < v.items.length; i++) {
div1.innerHTML += v.items[i] + ",";
}
div1.innerHTML += "<br/> Backs: ";
for(var i = 0; i < v.backs.length; i++) {
div1.innerHTML += v.backs[i] + ",";
}
div1.innerHTML += "</li>";
}
function addRow(tableId, level, n, node) {
var table1 = document.getElementById(tableId).getElementsByTagName("tbody")[0];
var tdLevel = document.createElement("td");
tdLevel.innerHTML = level;
var tdName = document.createElement("td");
tdName.innerHTML = node;
var tdItems = document.createElement("td");
for(var i = 0; i < node.items.length; i++) {
if(i>0)
tdItems.innerHTML += ",";
if(node.items[i] != null)
tdItems.innerHTML += node.items[i];
else
tdItems.innerHTML += "結案";
}
var tr = document.createElement("tr");
tr.appendChild(tdLevel);
tr.appendChild(tdName);
tr.appendChild(tdItems);
// tr.appendChild(tdBacks);
table1.appendChild(tr);
}
function X_renderFlowTable() {
document.getElementById("div1").style.visibility = 'hidden';
document.getElementById("table2").style.visibility='visible';
var table1 = document.getElementById("table2").getElementsByTagName("tbody")[0];
var trs = table1.getElementsByTagName("tr");
while(trs.length > 1) {
table1.deleteRow(1); //using Table Object Model;
}
for(var l = 1; l < flow.levels.length; l++) {
for(var n = 0; n < flow.levels[l].nodes.length; n++) {
addRow("table2", l, n, flow.levels[l].nodes[n]);
}
}
}
WebFlow.sql
WebFlow SQL schema.
-- when insert, the column which IDENTITY could be ignored.
-- Note: IDENTITY only in M$ SQL Server, not SQL-92 Standard.
create table WebFlowMaster (
WebFlowID int IDENTITY(1,1) not null primary key,
Description text not null default '',
ModifyEmployee varchar(50) not null,
ModifyMemo text not null default '',
ModifyTime datetime not null
);
create table WebFlowDetail (
WebFlowID int not null,
FlowLevel int not null,
NodeID varchar(50) not null,
ItemIDs varchar(1000) not null
-- text will not allow to insert with select union in SQLServer.
--SQL Sever線上說明:
--在 SQL Server 版本 7.0 上,execute_statement 無法包含會
--傳回 text 或 image 資料行的延伸預存程序。
--此行為是對先前 SQL Server 版本的一項變更。
);
--select WFM.WebFlowID.*, N.FlowLevel, N.NodeID, I.ItemID
--from WebFlowMaster as WFM
--inner join (WebFlowNode as N inner join WebFlowItem as I
-- on N.WebFlowID = I.WebFlowID and N.NodeID = I.NodeID)
--on WFM.WebFlowID = N.WebFlowID
create procedure dt_WebFlowMasterInsert (
@WebFlowID int output,
@Description text,
@ModifyEmployee varchar(50),
@ModifyMemo text,
@ModifyTime datetime
)
AS
BEGIN
Insert Into WebFlowMaster (
Description,
ModifyEmployee,
ModifyMemo,
ModifyTime
)
Values (
@Description,
@ModifyEmployee,
@ModifyMemo,
@ModifyTime
);
Set @WebFlowID = SCOPE_IDENTITY();
END
GO
create procedure dt_WebFlowMasterUpdate (
@WebFlowID int,
@Description text,
@ModifyEmployee varchar(50),
@ModifyMemo text,
@ModifyTime datetime
)
AS
BEGIN
Update WebFlowMaster set
Description = @Description,
ModifyEmployee = @ModifyEmployee,
ModifyMemo = @ModifyMemo,
ModifyTime = @ModifyTime
where WebFlowID = @WebFlowID;
END
GO
WebFlow.css
視覺元件外觀。
.FlowNode {
position: absolute;
width: 80px;
height: 40px;
border: 1px solid black;
z-index: 100;
}
.FlowMenu1 {
position:absolute;
background-color: lightgrey;
border: 1px solid black;
visibility: hidden;
z-index: 500;
padding: 10px;
width: 120px;
}
.FlowSubMenu1 {
position:absolute;
background-color: lightgrey;
border: 1px solid black;
visibility: hidden;
z-index: 500;
padding: 10px;
width: 360px;
text-align: center;
}
.MenuItemMouseOver {
background-color: #fcfcfc;
}
.MenuItem {
background-color: lightgrey;
}
#div1 {
position:relative;
text-align: center;
}
#div2 {
position:absolute;
left: 200px;
top: 20px;
}
WebFlow.html
主程式。
<html>
<head>
<link rel="stylesheet" href="WebFlow.css" type="text/css" />
<script type="text/javascript" src="wz_jsgraphics.js"></script>
<script type="text/javascript" src="FlowNode.js"></script>
<script type="text/javascript" src="WebFlow.js"></script>
<script type="text/javascript" src="FlowTableSubmit.js"></script>
</head>
<body onClick="bodyControler.clickHandler();">
<span id="FlowMenu1" class="FlowMenu1"></span>
<span id="FlowMenuAddNewLevel" class="FlowSubMenu1">
<label>關卡名稱: <input type="text" name="nodeName" /></label><br/>
<input type="button" value="確定" onClick="submitAddNewLevel();"/>
<input type="button" value="取消" onClick="cancleAddNewLevel();" />
</span>
<span id="FlowMenuAddNewNode" class="FlowSubMenu1">
<fieldset>
<legend>新增關卡</legend>
<label>關卡名稱: <input type="text" name="nodeName" /></label><br/>
</fieldset>
<p align="center">
<input type="button" value="確定" onClick="submitAddNewNode();"/>
<input type="button" value="取消" onClick="cancleAddNewNode();" />
</p>
</span>
<span id="FlowMenuUpdateNode" class="FlowSubMenu1">
<fieldset>
<legend>修改關卡</legend>
<label>關卡名稱: <input type="text" name="nodeName" /></label><br/>
</fieldset>
<p align="center">
<input type="button" value="確定" onClick="submitUpdateNode();"/>
<input type="button" value="取消" onClick="cancleUpdateNode();" />
</p>
</span>
<span id="FlowMenuAddItem" class="FlowSubMenu1">
<fieldset>
<legend>新增會辦者</legend>
<label>關卡名稱: <input type="text" name="nodeName" />
<select name="nodeList" onChange="if(this.selectedIndex > 0){document.getElementById('FlowMenuAddItem').getElementsByTagName('input')[0].value = this.options[this.selectedIndex].value};"></select></label><br/>
</fieldset>
<p align="center">
<input type="button" value="確定" onClick="submitAddItem();"/>
<input type="button" value="取消" onClick="cancleAddItem();" />
</p>
</span>
<span id="FlowMenuRemoveItem" class="FlowSubMenu1">
<fieldset>
<legend>移除會辦者</legend>
<label>關卡名稱: <input type="text" name="nodeName" />
<select name="nodeList" onChange="if(this.selectedIndex > 0){document.getElementById('FlowMenuRemoveItem').getElementsByTagName('input')[0].value = this.options[this.selectedIndex].value};"></select></label></label><br/>
</fieldset>
<p align="center">
<input type="button" value="確定" onClick="submitRemoveItem();"/>
<input type="button" value="取消" onClick="cancleRemoveItem();" />
</p>
</span>
<span id="FlowMenuRemoveNode" class="FlowSubMenu1">
<label>確定刪除?</label><br/>
<input type="button" value="確定" onClick="submitRemoveNode();"/>
<input type="button" value="取消" onClick="cancleRemoveNode();" />
</span>
<table id="table2" border="1" style='visibility:hidden'>
<tboby>
<tr>
<td>Level</td>
<td>Node</td>
<td>Items</td>
</tr>
</tbody>
</table>
<div id="div1" class="div1">
</div>
<canvas id="div2">
</canvas>
<form>
<input type='button' value='送出表格' onClick='X_renderFlowTable();'/>
</form>
<script type="text/javascript">
var currentNode;
var currentNodeIndex = new NodeIndex(0,0);
var jg_doc = new jsGraphics("div1");
var canvas = document.getElementById('div2');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
}
var bodyControler = {
currentNode: null,
currentNodeIndex: new NodeIndex(0,0),
jg: new jsGraphics("div1"),
currentObject: null,
_clickHandlers: new Array(),
addClickHandler: function(func) {
this._clickHandlers.push(func);
},
removeClickHandler: function(func) {
for(var i = 0; i < this._clickHandlers.length; i++) {
if(this._clickHandlers[i] === func) {
this._clickHandlers.splice(i, 1);
break;
}
}
},
clickHandler: function() {
for(var i = 0; i < this._clickHandlers.length; i++) {
this._clickHandlers[i]();
}
}
};
/*=============================================*/
var flow = new Flow();
flow.insertLevel(1, "申請人", null);
flow.addItem(1,0,null);
//flow.insertLevel(2, "A1", flow.levels[1].nodes[0]);
//flow.addNode(2,"A2",flow.levels[1].nodes[0])
renderFlowTable();
</script>
</body>
</html>
這是我一年前在某公司任職時寫的,算是程式雛型。當時最主要的困難點是在瀏覽器頁面上「畫圖/線」。試了包括 SVG 的多種方式後皆不可行,最後還是用 wz_jsgraphics.js 解決。日後待網頁向量圖形規格確認與普及後,再改寫吧。
樂多舊網址: http://blog.roodo.com/rocksaying/archives/4036079.html