GooFlow.js 107 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641
  1. // 定义一个区域图类:
  2. function GooFlow (bgDiv, property) {
  3. if (navigator.userAgent.indexOf('MSIE 8.0') > 0 || navigator.userAgent.indexOf('MSIE 7.0') > 0 || navigator.userAgent.indexOf('MSIE 6.0') > 0) {
  4. GooFlow.prototype.useSVG = '';
  5. } else GooFlow.prototype.useSVG = '1';
  6. // 初始化区域图的对象
  7. this.$id = bgDiv.attr('id');
  8. this.$bgDiv = bgDiv; // 最父框架的DIV
  9. this.$bgDiv.addClass('GooFlow');
  10. if (GooFlow.prototype.color.font) {
  11. this.$bgDiv.css('color', GooFlow.prototype.color.font);
  12. }
  13. if (GooFlow.prototype.color.main) {
  14. this.$bgDiv.append('<style>.GooFlow_tool_btndown{background:' + GooFlow.prototype.color.main + '}</style>');
  15. }
  16. var width = (property.width || 800);
  17. var height = (property.height || 500);
  18. this.$bgDiv.css({
  19. width: width + 'px',
  20. height: height + 'px'
  21. });
  22. this.$tool = null; // 左侧工具栏对象
  23. this.$head = null; // 顶部标签及工具栏按钮
  24. this.$title = 'newFlow_1'; // 流程图的名称
  25. this.$id = '';
  26. this.$nodeRemark = {}; // 每一种结点或按钮的说明文字,JSON格式,key为类名,value为用户自定义文字说明
  27. this.$nowType = 'cursor'; // 当前要绘制的对象类型
  28. this.$lineData = {};
  29. this.$lineCount = 0;
  30. this.$nodeData = {};
  31. this.$nodeCount = 0;
  32. this.$areaData = {};
  33. this.$areaCount = 0;
  34. this.$lineDom = {};
  35. this.$nodeDom = {};
  36. this.$areaDom = {};
  37. this.$max = property.initNum || 1; // 计算默认ID值的起始SEQUENCE
  38. this.$focus = ''; // 当前被选定的结点/转换线ID,如果没选中或者工作区被清空,则为""
  39. this.$cursor = 'default'; // 鼠标指针在工作区内的样式
  40. this.$editable = false; // 工作区是否可编辑
  41. this.$deletedItem = {}; // 在流程图的编辑操作中被删除掉的元素ID集合,元素ID为KEY,元素类型(node,line.area)为VALUE
  42. this.$workExtendStep = 200; // 在自动/手动扩展可编辑区时,一次扩展后宽/高增加多少像素
  43. this.$setName = function (id, name, type) {
  44. if (type == 'node') { // 如果是结点
  45. this.$nodeData[id].name = name;
  46. this.$nodeDom[id].children('.span').text(name);
  47. this.$nodeDom[id].find('td:eq(1)').text(name);
  48. } else if (type == 'line') { // 如果是线
  49. this.$lineDom[id].childNodes[2].textContent = name;
  50. this.$lineDom[id].childNodes[1].innerHTML = name;
  51. }
  52. };
  53. var headHeight = 0;
  54. var tmp = '';
  55. if (property.haveHead) {
  56. tmp = "<div class='GooFlow_head' " + (GooFlow.prototype.color.main ? "style='border-bottom-color:" + GooFlow.prototype.color.main + "'" : '') +
  57. '>';
  58. if (property.headLabel) {
  59. tmp += "<label title='" + (property.initLabelText || 'newFlow_1') + "' " +
  60. (GooFlow.prototype.color.main ? "style='background:" + GooFlow.prototype.color.main + "'" : '') + '>' + (property.initLabelText || 'newFlow_1') + '</label>';
  61. }
  62. for (var x = 0; x < property.headBtns.length; ++x) {
  63. tmp += "<a href='javascript:void(0)' class='GooFlow_head_btn'><i class='ico_" + property.headBtns[x] + "'></i></a>"
  64. }
  65. tmp += '</div>';
  66. this.$head = $(tmp);
  67. this.$bgDiv.append(this.$head);
  68. headHeight = 28;
  69. // 以下是当工具栏按钮被点击时触发的事件自定义(虚函数),格式为function(),因为可直接用THIS操作对象本身,不用传参;用户可自行重定义:
  70. this.onBtnNewClick = null; // 新建流程图按钮被点中
  71. this.onBtnOpenClick = null; // 打开流程图按钮定义
  72. this.onBtnSaveClick = null; // 保存流程图按钮定义
  73. this.onFreshClick = null; // 重载流程图按钮定义
  74. this.onPrintClick = null; // 打印流程图按钮定义
  75. if (property.headBtns) {
  76. this.$head.on('click', {
  77. inthis: this
  78. }, function (e) {
  79. if (!e) e = window.event;
  80. var tar = e.target;
  81. if (tar.tagName == 'DIV' || tar.tagName == 'SPAN') return;
  82. else if (tar.tagName == 'A') tar = tar.childNodes[0];
  83. var This = e.data.inthis;
  84. // 定义顶部操作栏按钮的事件
  85. switch ($(tar).attr('class')) {
  86. case 'ico_new':
  87. if (This.onBtnNewClick != null) This.onBtnNewClick();
  88. break;
  89. case 'ico_open':
  90. if (This.onBtnOpenClick != null) This.onBtnOpenClick();
  91. break;
  92. case 'ico_save':
  93. if (This.onBtnSaveClick != null) This.onBtnSaveClick();
  94. break;
  95. case 'ico_undo':
  96. This.undo();
  97. break;
  98. case 'ico_redo':
  99. This.redo();
  100. break;
  101. case 'ico_reload':
  102. if (This.onFreshClick != null) This.onFreshClick();
  103. break;
  104. case 'ico_print':
  105. if (This.onPrintClick != null) This.onPrintClick();
  106. break;
  107. }
  108. });
  109. }
  110. }
  111. var toolWidth = 0;
  112. if (property.haveTool) {
  113. this.$bgDiv.append("<div class='GooFlow_tool'" + (property.haveHead ? '' : " style='margin-top:4px'") + "><div style='height:" + (height - headHeight - (property.haveHead ? 5 : 8)) + "px' class='GooFlow_tool_div'></div></div>");
  114. this.$tool = this.$bgDiv.find('.GooFlow_tool div');
  115. // 未加代码:加入绘图工具按钮
  116. this.$tool.append("<div style='margin-bottom:5px'><span/><span/><span/><span/></div>" +
  117. "<a href='javascript:void(0)' type='cursor' class='GooFlow_tool_btndown' id='" + this.$id + "_btn_cursor'><i class='ico_cursor'/></a>" +
  118. "<a href='javascript:void(0)' type='direct' class='GooFlow_tool_btn' id='" + this.$id + "_btn_direct'><i class='ico_direct'/></a>"
  119. );
  120. if (property.toolBtns && property.toolBtns.length > 0) {
  121. tmp = '<span/>';
  122. for (var i = 0; i < property.toolBtns.length; ++i) {
  123. tmp += "<a href='javascript:void(0)' type='" + property.toolBtns[i] + "' id='" + this.$id + '_btn_' + property.toolBtns[i].split(' ')[0] + "' class='GooFlow_tool_btn'><i class='ico_" + property.toolBtns[i] + "'/></a>"; // 加入自定义按钮
  124. }
  125. this.$tool.append(tmp);
  126. }
  127. // 加入区域划分框工具开关按钮
  128. if (property.haveGroup) {
  129. this.$tool.append("<span/><a href='javascript:void(0)' type='group' class='GooFlow_tool_btn' id='" + this.$id + "_btn_group'><i class='ico_group'/></a>");
  130. }
  131. toolWidth = 31;
  132. this.$nowType = 'cursor';
  133. // 绑定各个按钮的点击事件
  134. this.$tool.on('click', {
  135. inthis: this
  136. }, function (e) {
  137. if (!e) e = window.event;
  138. var tar;
  139. switch (e.target.tagName) {
  140. case 'SPAN':
  141. return false;
  142. case 'DIV':
  143. return false;
  144. case 'I':
  145. tar = e.target.parentNode;
  146. break;
  147. case 'A':
  148. tar = e.target;
  149. };
  150. var type = $(tar).attr('type');
  151. e.data.inthis.switchToolBtn(type);
  152. return false;
  153. });
  154. this.$editable = true; // 只有具有工具栏时可编辑
  155. }
  156. width = width - toolWidth - 9;
  157. height = height - headHeight - (property.haveHead ? 5 : 8);
  158. this.$bgDiv.append("<div class='GooFlow_work' style='width:" + (width) + 'px;height:' + (height) + 'px;' + (property.haveHead ? '' : 'margin-top:3px') + "'></div>");
  159. this.$workArea = $("<div class='GooFlow_work_inner' style='width:" + width + 'px;height:' + height + "px'></div>")
  160. .attr({
  161. 'unselectable': 'on',
  162. 'onselectstart': 'return false',
  163. 'onselect': 'document.selection.empty()'
  164. });
  165. this.$bgDiv.children('.GooFlow_work').append(this.$workArea);
  166. this.$draw = null; // 画矢量线条的容器
  167. this.initDraw('draw_' + this.$id, width, height);
  168. this.$group = null;
  169. if (property.haveGroup) {
  170. this.initGroup(width, height);
  171. }
  172. this.initExpendFunc();
  173. if (this.$editable) {
  174. // 绑定工作区事件
  175. this.$workArea.on('click', {
  176. inthis: this
  177. }, function (e) {
  178. if (!e) e = window.event;
  179. var This = e.data.inthis;
  180. if (!This.$editable) return;
  181. var type = This.$nowType;
  182. if (type == 'cursor') {
  183. var t = $(e.target);
  184. var n = t.prop('tagName');
  185. // alert(n);
  186. if (n == 'svg' || (n == 'DIV' && t.prop('class').indexOf('GooFlow_work') > -1) || n == 'LABEL') {
  187. if (This.$lineOper.data('tid')) {
  188. This.focusItem(This.$lineOper.data('tid'), false);
  189. // This.$mpFrom.removeData("p");
  190. } else {
  191. This.blurItem();
  192. }
  193. }
  194. return;
  195. } else if (type == 'direct' || type == 'group') return;
  196. var X, Y;
  197. var ev = mousePosition(e),
  198. t = getElCoordinate(this);
  199. X = ev.x - t.left + this.parentNode.scrollLeft;
  200. Y = ev.y - t.top + this.parentNode.scrollTop;
  201. var id = This.generateMixed(3) + String(new Date().getTime());
  202. This.addNode(id, {
  203. name: 'node_' + This.$max,
  204. left: X,
  205. top: Y,
  206. type: This.$nowType
  207. });
  208. This.$max++;
  209. });
  210. // 划线或改线时用的绑定
  211. this.$workArea.mousemove({
  212. inthis: this
  213. }, function (e) {
  214. if (e.data.inthis.$nowType != 'direct' && !e.data.inthis.$mpTo.data('p')) return;
  215. var lineStart = $(this).data('lineStart');
  216. var lineEnd = $(this).data('lineEnd');
  217. if (!lineStart && !lineEnd) return;
  218. var ev = mousePosition(e),
  219. t = getElCoordinate(this);
  220. var X, Y;
  221. X = ev.x - t.left + this.parentNode.scrollLeft;
  222. Y = ev.y - t.top + this.parentNode.scrollTop;
  223. var line = document.getElementById('GooFlow_tmp_line');
  224. if (lineStart) {
  225. if (GooFlow.prototype.useSVG != '') {
  226. line.childNodes[0].setAttribute('d', 'M ' + lineStart.x + ' ' + lineStart.y + ' L ' + X + ' ' + Y);
  227. line.childNodes[1].setAttribute('d', 'M ' + lineStart.x + ' ' + lineStart.y + ' L ' + X + ' ' + Y);
  228. if (line.childNodes[1].getAttribute('marker-end') == 'url("#arrow2")') {
  229. line.childNodes[1].setAttribute('marker-end', 'url(#arrow3)');
  230. } else line.childNodes[1].setAttribute('marker-end', 'url(#arrow2)');
  231. } else line.points.value = lineStart.x + ',' + lineStart.y + ' ' + X + ',' + Y;
  232. } else if (lineEnd) {
  233. if (GooFlow.prototype.useSVG != '') {
  234. line.childNodes[0].setAttribute('d', 'M ' + X + ' ' + Y + ' L ' + lineEnd.x + ' ' + lineEnd.y);
  235. line.childNodes[1].setAttribute('d', 'M ' + X + ' ' + Y + ' L ' + lineEnd.x + ' ' + lineEnd.y);
  236. if (line.childNodes[1].getAttribute('marker-end') == 'url("#arrow2")') {
  237. line.childNodes[1].setAttribute('marker-end', 'url(#arrow3)');
  238. } else line.childNodes[1].setAttribute('marker-end', 'url(#arrow2)');
  239. } else line.points.value = X + ',' + Y + ' ' + lineEnd.x + ',' + lineEnd.y;
  240. }
  241. });
  242. this.$workArea.mouseup({
  243. inthis: this
  244. }, function (e) {
  245. var This = e.data.inthis;
  246. if (This.$nowType != 'direct' && !This.$mpTo.data('p')) return;
  247. var tmp = document.getElementById('GooFlow_tmp_line');
  248. if (tmp) {
  249. $(this).css('cursor', 'auto').removeData('lineStart').removeData('lineEnd');
  250. This.$mpTo.hide().removeData('p');
  251. This.$mpFrom.hide().removeData('p');
  252. This.$draw.removeChild(tmp);
  253. This.focusItem(This.$focus, false);
  254. } else {
  255. This.$lineOper.removeData('tid');
  256. }
  257. });
  258. // 为了结点而增加的一些集体delegate绑定
  259. this.initWorkForNode();
  260. // 对结点进行移动或者RESIZE时用来显示的遮罩层
  261. this.$ghost = $("<div class='rs_ghost'></div>").attr({
  262. 'unselectable': 'on',
  263. 'onselectstart': 'return false',
  264. 'onselect': 'document.selection.empty()'
  265. });
  266. this.$bgDiv.append(this.$ghost);
  267. this.$textArea = $('<textarea></textarea>');
  268. this.$bgDiv.append(this.$textArea);
  269. this.$lineMove = $('<div class="GooFlow_linemove" style="display:none"></div>'); // 操作折线时的移动框
  270. this.$workArea.append(this.$lineMove);
  271. this.$lineMove.on('mousedown', {
  272. inthis: this
  273. }, function (e) {
  274. if (e.button == 2) return false;
  275. var lm = $(this);
  276. lm.css({
  277. 'background-color': '#333'
  278. });
  279. var This = e.data.inthis;
  280. var ev = mousePosition(e),
  281. t = getElCoordinate(This.$workArea[0]);
  282. var X, Y;
  283. X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft;
  284. Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop;
  285. var p = This.$lineMove.position();
  286. var vX = X - p.left,
  287. vY = Y - p.top;
  288. var isMove = false;
  289. document.onmousemove = function (e) {
  290. if (!e) e = window.event;
  291. var ev = mousePosition(e);
  292. var ps = This.$lineMove.position();
  293. X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft;
  294. Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop;
  295. if (This.$lineMove.data('type') == 'lr') {
  296. X = X - vX;
  297. if (X < 0) X = 0;
  298. else if (X > This.$workArea.width()) {
  299. X = This.$workArea.width();
  300. }
  301. This.$lineMove.css({
  302. left: X + 'px'
  303. });
  304. } else if (This.$lineMove.data('type') == 'tb') {
  305. Y = Y - vY;
  306. if (Y < 0) Y = 0;
  307. else if (Y > This.$workArea.height()) {
  308. Y = This.$workArea.height();
  309. }
  310. This.$lineMove.css({
  311. top: Y + 'px'
  312. });
  313. }
  314. isMove = true;
  315. }
  316. document.onmouseup = function (e) {
  317. if (isMove) {
  318. var p = This.$lineMove.position();
  319. if (This.$lineMove.data('type') == 'lr') {
  320. This.setLineM(This.$lineMove.data('tid'), p.left + 3);
  321. } else if (This.$lineMove.data('type') == 'tb') {
  322. This.setLineM(This.$lineMove.data('tid'), p.top + 3);
  323. }
  324. }
  325. This.$lineMove.css({
  326. 'background-color': 'transparent'
  327. });
  328. if (This.$focus == This.$lineMove.data('tid')) {
  329. This.focusItem(This.$lineMove.data('tid'));
  330. }
  331. document.onmousemove = null;
  332. document.onmouseup = null;
  333. }
  334. });
  335. var chars = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
  336. this.generateMixed = function (n) {
  337.      var res = '';
  338.      for (var i = 0; i < n; i++) {
  339.          var id = Math.ceil(Math.random() * 24);
  340.          res += chars[id];
  341.      }
  342.      return res;
  343. }
  344. // 选定一条转换线后出现的浮动操作栏,有改变线的样式和删除线等按钮。
  345. this.$lineOper = $("<div class='GooFlow_line_oper' style='display:none'><i class='b_l1'></i><i class='b_l2'></i><i class='b_l3'></i><i class='b_x'></i></div>"); // 选定线时显示的操作框
  346. this.$workArea.parent().append(this.$lineOper);
  347. this.$lineOper.on('click', {
  348. inthis: this
  349. }, function (e) {
  350. if (!e) e = window.event;
  351. if (e.target.tagName != 'I') return;
  352. var This = e.data.inthis;
  353. var id = $(this).data('tid');
  354. switch ($(e.target).attr('class')) {
  355. case 'b_x':
  356. This.delLine(id);
  357. this.style.display = 'none';
  358. break;
  359. case 'b_l1':
  360. This.setLineType(id, 'lr');
  361. break;
  362. case 'b_l2':
  363. This.setLineType(id, 'tb');
  364. break;
  365. case 'b_l3':
  366. This.setLineType(id, 'sl');
  367. break;
  368. }
  369. });
  370. // 新增移动线两个端点至新的结点功能移动功能,这里要提供移动用的DOM
  371. this.$mpFrom = $("<div class='GooFlow_line_mp' style='display:none'></div>");
  372. this.$mpTo = $("<div class='GooFlow_line_mp' style='display:none'></div>");
  373. this.$workArea.append(this.$mpFrom).append(this.$mpTo);
  374. this.initLinePointsChg();
  375. // 下面绑定当结点/线/分组块的一些操作事件,这些事件可直接通过this访问对象本身
  376. // 当操作某个单元(结点/线/分组块)被添加时,触发的方法,返回FALSE可阻止添加事件的发生
  377. // 格式function(id,type,json):id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值,json即addNode,addLine或addArea方法的第二个传参json.
  378. this.onItemAdd = null;
  379. // 当操作某个单元(结点/线/分组块)被删除时,触发的方法,返回FALSE可阻止删除事件的发生
  380. // 格式function(id,type):id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值
  381. this.onItemDel = null;
  382. // 当操作某个单元(结点/分组块)被移动时,触发的方法,返回FALSE可阻止移动事件的发生
  383. // 格式function(id,type,left,top):id是单元的唯一标识ID,type是单元的种类,有"node","area"两种取值,线line不支持移动,left是新的左边距坐标,top是新的顶边距坐标
  384. this.onItemMove = null;
  385. // 当操作某个单元(结点/线/分组块)被重命名时,触发的方法,返回FALSE可阻止重命名事件的发生
  386. // 格式function(id,name,type):id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值,name是新的名称
  387. this.onItemRename = null;
  388. // 当操作某个单元(结点/线)被由不选中变成选中时,触发的方法,返回FALSE可阻止选中事件的发生
  389. // 格式function(id,type):id是单元的唯一标识ID,type是单元的种类,有"node","line"两种取值,"area"不支持被选中
  390. this.onItemFocus = null;
  391. // 当操作某个单元(结点/线)被由选中变成不选中时,触发的方法,返回FALSE可阻止取消选中事件的发生
  392. // 格式function(id,type):id是单元的唯一标识ID,type是单元的种类,有"node","line"两种取值,"area"不支持被取消选中
  393. this.onItemBlur = null;
  394. // 当操作某个单元(结点/分组块)被重定义大小或造型时,触发的方法,返回FALSE可阻止重定大小/造型事件的发生
  395. // 格式function(id,type,width,height):id是单元的唯一标识ID,type是单元的种类,有"node","line","area"三种取值;width是新的宽度,height是新的高度
  396. this.onItemResize = null;
  397. // 当移动某条折线中段的位置,触发的方法,返回FALSE可阻止重定大小/造型事件的发生
  398. // 格式function(id,M):id是单元的唯一标识ID,M是中段的新X(或Y)的坐标
  399. this.onLineMove = null;
  400. // 当变换某条连接线的类型,触发的方法,返回FALSE可阻止重定大小/造型事件的发生
  401. // 格式function(id,type):id是单元的唯一标识ID,type是连接线的新类型,"sl":直线,"lr":中段可左右移动的折线,"tb":中段可上下移动的折线
  402. this.onLineSetType = null;
  403. // 当变换某条连接线的端点变更连接的结点时,触发的方法,返回FALSE可阻止重定大小/造型事件的发生
  404. // 格式function(id,newStart,newEnd):id是连线单元的唯一标识ID,newStart,newEnd分别是起始结点的ID和到达结点的ID
  405. this.onLinePointMove = null;
  406. // 当用重色标注某个结点/转换线时触发的方法,返回FALSE可阻止重定大小/造型事件的发生
  407. // 格式function(id,type,mark):id是单元的唯一标识ID,type是单元类型("node"结点,"line"转换线),mark为布尔值,表示是要标注TRUE还是取消标注FALSE
  408. this.onItemMark = null;
  409. if (property.useOperStack && this.$editable) { // 如果要使用堆栈记录操作并提供“撤销/重做”的功能,只在编辑状态下有效
  410. this.$undoStack = [];
  411. this.$redoStack = [];
  412. this.$isUndo = 0;
  413. /// ////////////以下是构造撤销操作/重做操作的方法
  414. // 为了节省浏览器内存空间,undo/redo中的操作缓存栈,最多只可放40步操作;超过40步时,将自动删掉最旧的一个缓存
  415. this.pushOper = function (funcName, paras) {
  416. var len = this.$undoStack.length;
  417. if (this.$isUndo == 1) {
  418. this.$redoStack.push([funcName, paras]);
  419. this.$isUndo = false;
  420. if (this.$redoStack.length > 40) this.$redoStack.shift();
  421. } else {
  422. this.$undoStack.push([funcName, paras]);
  423. if (this.$undoStack.length > 40) this.$undoStack.shift();
  424. if (this.$isUndo == 0) {
  425. this.$redoStack.splice(0, this.$redoStack.length);
  426. }
  427. this.$isUndo = 0;
  428. }
  429. };
  430. // 将外部的方法加入到GooFlow对象的事务操作堆栈中,在过后的undo/redo操作中可以进行控制,一般用于对流程图以外的附加信息进行编辑的事务撤销/重做控制;
  431. // 传参func为要执行方法对象,jsonPara为外部方法仅有的一个面向字面的JSON传参,由JSON对象带入所有要传的信息;
  432. // 提示:为了让外部方法能够被UNDO/REDO,需要在编写这些外部方法实现时,加入对该方法执行后效果回退的另一个执行方法的pushExternalOper
  433. this.pushExternalOper = function (func, jsonPara) {
  434. this.pushOper('externalFunc', [func, jsonPara]);
  435. };
  436. // 撤销上一步操作
  437. this.undo = function () {
  438. if (this.$undoStack.length == 0) return;
  439. this.blurItem();
  440. var tmp = this.$undoStack.pop();
  441. this.$isUndo = 1;
  442. if (tmp[0] == 'externalFunc') {
  443. tmp[1][0](tmp[1][1]);
  444. } else {
  445. // 传参的数量,最多支持6个.
  446. switch (tmp[1].length) {
  447. case 0:
  448. this[tmp[0]]();
  449. break;
  450. case 1:
  451. this[tmp[0]](tmp[1][0]);
  452. break;
  453. case 2:
  454. this[tmp[0]](tmp[1][0], tmp[1][1]);
  455. break;
  456. case 3:
  457. this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2]);
  458. break;
  459. case 4:
  460. this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3]);
  461. break;
  462. case 5:
  463. this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3], tmp[1][4]);
  464. break;
  465. case 6:
  466. this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3], tmp[1][4], tmp[1][5]);
  467. break;
  468. }
  469. }
  470. };
  471. // 重做最近一次被撤销的操作
  472. this.redo = function () {
  473. if (this.$redoStack.length == 0) return;
  474. this.blurItem();
  475. var tmp = this.$redoStack.pop();
  476. this.$isUndo = 2;
  477. if (tmp[0] == 'externalFunc') {
  478. tmp[1][0](tmp[1][1]);
  479. } else {
  480. // 传参的数量,最多支持6个.
  481. switch (tmp[1].length) {
  482. case 0:
  483. this[tmp[0]]();
  484. break;
  485. case 1:
  486. this[tmp[0]](tmp[1][0]);
  487. break;
  488. case 2:
  489. this[tmp[0]](tmp[1][0], tmp[1][1]);
  490. break;
  491. case 3:
  492. this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2]);
  493. break;
  494. case 4:
  495. this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3]);
  496. break;
  497. case 5:
  498. this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3], tmp[1][4]);
  499. break;
  500. case 6:
  501. this[tmp[0]](tmp[1][0], tmp[1][1], tmp[1][2], tmp[1][3], tmp[1][4], tmp[1][5]);
  502. break;
  503. }
  504. }
  505. };
  506. }
  507. $(document).keydown({
  508. inthis: this
  509. }, function (e) {
  510. // 绑定键盘操作
  511. var This = e.data.inthis;
  512. if (This.$focus == '') return;
  513. switch (e.keyCode) {
  514. case 46: // 删除
  515. This.delNode(This.$focus, true);
  516. This.delLine(This.$focus);
  517. break;
  518. }
  519. });
  520. }
  521. }
  522. GooFlow.prototype = {
  523. useSVG: '',
  524. getSvgMarker: function (id, color) {
  525. var m = document.createElementNS('http://www.w3.org/2000/svg', 'marker');
  526. m.setAttribute('id', id);
  527. m.setAttribute('viewBox', '0 0 6 6');
  528. m.setAttribute('refX', 5);
  529. m.setAttribute('refY', 3);
  530. m.setAttribute('markerUnits', 'strokeWidth');
  531. m.setAttribute('markerWidth', 6);
  532. m.setAttribute('markerHeight', 6);
  533. m.setAttribute('orient', 'auto');
  534. var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  535. path.setAttribute('d', 'M 0 0 L 6 3 L 0 6 z');
  536. path.setAttribute('fill', color);
  537. path.setAttribute('stroke-width', 0);
  538. m.appendChild(path);
  539. return m;
  540. },
  541. initDraw: function (id, width, height) {
  542. var elem;
  543. if (GooFlow.prototype.useSVG != '') {
  544. this.$draw = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); // 可创建带有指定命名空间的元素节点
  545. this.$workArea.prepend(this.$draw);
  546. var defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
  547. this.$draw.appendChild(defs);
  548. defs.appendChild(GooFlow.prototype.getSvgMarker('arrow1', GooFlow.prototype.color.line || '#3892D3'));
  549. defs.appendChild(GooFlow.prototype.getSvgMarker('arrow2', GooFlow.prototype.color.mark || '#ff8800'));
  550. defs.appendChild(GooFlow.prototype.getSvgMarker('arrow3', GooFlow.prototype.color.mark || '#ff8800'));
  551. } else {
  552. this.$draw = document.createElement('v:group');
  553. this.$draw.coordsize = width + ',' + height;
  554. this.$workArea.prepend("<div class='GooFlow_work_vml' style='position:relative;width:" + width + 'px;height:' + height + "px'></div>");
  555. this.$workArea.children('div')[0].insertBefore(this.$draw, null);
  556. }
  557. this.$draw.id = id;
  558. this.$draw.style.width = width + 'px';
  559. this.$draw.style.height = +height + 'px';
  560. // 绑定连线的点击选中以及双击编辑事件
  561. var tmpClk = null;
  562. if (GooFlow.prototype.useSVG != '') tmpClk = 'g';
  563. else tmpClk = 'PolyLine';
  564. if (!this.$editable) return;
  565. $(this.$draw).delegate(tmpClk, 'click', {
  566. inthis: this
  567. }, function (e) {
  568. e.data.inthis.focusItem(this.id, true);
  569. });
  570. // $(this.$draw).delegate(tmpClk, 'dblclick', {
  571. // inthis: this
  572. // }, function (e) {
  573. // var oldTxt, x, y, from, to;
  574. // var This = e.data.inthis;
  575. // if (GooFlow.prototype.useSVG != '') {
  576. // oldTxt = this.childNodes[2].textContent;
  577. // from = this.getAttribute('from').split(',');
  578. // to = this.getAttribute('to').split(',');
  579. // } else {
  580. // oldTxt = this.childNodes[1].innerHTML;
  581. // var n = this.getAttribute('fromTo').split(',');
  582. // from = [n[0], n[1]];
  583. // to = [n[2], n[3]];
  584. // }
  585. // if (This.$lineData[this.id].type == 'lr') {
  586. // from[0] = This.$lineData[this.id].M;
  587. // to[0] = from[0];
  588. // } else if (This.$lineData[this.id].type == 'tb') {
  589. // from[1] = This.$lineData[this.id].M;
  590. // to[1] = from[1];
  591. // }
  592. // x = (parseInt(from[0], 10) + parseInt(to[0], 10)) / 2 - 64;
  593. // y = (parseInt(from[1], 10) + parseInt(to[1], 10)) / 2 - 18;
  594. // var t = getElCoordinate(This.$workArea[0]);
  595. // This.$textArea.val(oldTxt).css({
  596. // display: 'block',
  597. // width: 130,
  598. // height: 26,
  599. // left: t.left + x - This.$workArea[0].parentNode.scrollLeft,
  600. // top: t.top + y - This.$workArea[0].parentNode.scrollTop
  601. // }).data('id', This.$focus).focus();
  602. // This.$workArea.parent().one('mousedown', function (e) {
  603. // if (e.button == 2) return false;
  604. // This.setName(This.$textArea.data('id'), This.$textArea.val(), 'line');
  605. // This.$textArea.val('').removeData('id').hide();
  606. // });
  607. // });
  608. },
  609. initGroup: function (width, height) {
  610. this.$group = $("<div class='GooFlow_work_group' style='width:" + width + 'px;height:' + height + "px'></div>"); // 存放背景区域的容器
  611. this.$workArea.prepend(this.$group);
  612. if (!this.$editable) return;
  613. // 区域划分框操作区的事件绑定
  614. this.$group.on('mousedown', {
  615. inthis: this
  616. }, function (e) { // 绑定RESIZE功能以及移动功能
  617. if (e.button == 2) return false;
  618. var This = e.data.inthis;
  619. if (This.$nowType != 'group') return;
  620. if (!e) e = window.event;
  621. var cursor = $(e.target).css('cursor');
  622. var id = e.target.parentNode;
  623. switch (cursor) {
  624. case 'nw-resize':
  625. id = id.parentNode;
  626. break;
  627. case 'w-resize':
  628. id = id.parentNode;
  629. break;
  630. case 'n-resize':
  631. id = id.parentNode;
  632. break;
  633. case 'move':
  634. break;
  635. default:
  636. return;
  637. }
  638. id = id.id;
  639. var ev = mousePosition(e),
  640. t = getElCoordinate(This.$workArea[0]);
  641. var X, Y;
  642. X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft;
  643. Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop;
  644. if (cursor != 'move') {
  645. This.$ghost.css({
  646. display: 'block',
  647. width: This.$areaData[id].width + 'px',
  648. height: This.$areaData[id].height + 'px',
  649. top: This.$areaData[id].top + t.top - This.$workArea[0].parentNode.scrollTop + 'px',
  650. left: This.$areaData[id].left + t.left - This.$workArea[0].parentNode.scrollLeft + 'px',
  651. cursor: cursor
  652. });
  653. var vX = (This.$areaData[id].left + This.$areaData[id].width) - X;
  654. var vY = (This.$areaData[id].top + This.$areaData[id].height) - Y;
  655. } else {
  656. var vX = X - This.$areaData[id].left;
  657. var vY = Y - This.$areaData[id].top;
  658. }
  659. var isMove = false;
  660. This.$ghost.css('cursor', cursor);
  661. document.onmousemove = function (e) {
  662. if (!e) e = window.event;
  663. var ev = mousePosition(e);
  664. if (cursor != 'move') {
  665. X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft - This.$areaData[id].left + vX;
  666. Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop - This.$areaData[id].top + vY;
  667. if (X < 200) X = 200;
  668. if (Y < 100) Y = 100;
  669. switch (cursor) {
  670. case 'nw-resize':
  671. This.$ghost.css({
  672. width: X + 'px',
  673. height: Y + 'px'
  674. });
  675. break;
  676. case 'w-resize':
  677. This.$ghost.css({
  678. width: X + 'px'
  679. });
  680. break;
  681. case 'n-resize':
  682. This.$ghost.css({
  683. height: Y + 'px'
  684. });
  685. break;
  686. }
  687. } else {
  688. if (This.$ghost.css('display') == 'none') {
  689. This.$ghost.css({
  690. display: 'block',
  691. width: This.$areaData[id].width + 'px',
  692. height: This.$areaData[id].height + 'px',
  693. top: This.$areaData[id].top + t.top - This.$workArea[0].parentNode.scrollTop + 'px',
  694. left: This.$areaData[id].left + t.left - This.$workArea[0].parentNode.scrollLeft + 'px',
  695. cursor: cursor
  696. });
  697. }
  698. X = ev.x - vX;
  699. Y = ev.y - vY;
  700. if (X < t.left - This.$workArea[0].parentNode.scrollLeft) {
  701. X = t.left - This.$workArea[0].parentNode.scrollLeft;
  702. } else if (X + This.$workArea[0].parentNode.scrollLeft + This.$areaData[id].width > t.left + This.$workArea.width()) {
  703. X = t.left + This.$workArea.width() - This.$workArea[0].parentNode.scrollLeft - This.$areaData[id].width;
  704. }
  705. if (Y < t.top - This.$workArea[0].parentNode.scrollTop) {
  706. Y = t.top - This.$workArea[0].parentNode.scrollTop;
  707. } else if (Y + This.$workArea[0].parentNode.scrollTop + This.$areaData[id].height > t.top + This.$workArea.height()) {
  708. Y = t.top + This.$workArea.height() - This.$workArea[0].parentNode.scrollTop - This.$areaData[id].height;
  709. }
  710. This.$ghost.css({
  711. left: X + 'px',
  712. top: Y + 'px'
  713. });
  714. }
  715. isMove = true;
  716. }
  717. document.onmouseup = function (e) {
  718. This.$ghost.empty().hide();
  719. document.onmousemove = null;
  720. document.onmouseup = null;
  721. if (!isMove) return;
  722. if (cursor != 'move') {
  723. This.resizeArea(id, This.$ghost.outerWidth(), This.$ghost.outerHeight());
  724. } else {
  725. This.moveArea(id, X + This.$workArea[0].parentNode.scrollLeft - t.left, Y + This.$workArea[0].parentNode.scrollTop - t.top);
  726. }
  727. return false;
  728. }
  729. });
  730. // 绑定修改文字说明功能
  731. // this.$group.on('dblclick', {
  732. // inthis: this
  733. // }, function (e) {
  734. // var This = e.data.inthis;
  735. // if (This.$nowType != 'group') return;
  736. // if (!e) e = window.event;
  737. // if (e.target.tagName != 'LABEL') return false;
  738. // var oldTxt = e.target.innerHTML;
  739. // var p = e.target.parentNode;
  740. // var x = parseInt(p.style.left, 10) + 18,
  741. // y = parseInt(p.style.top, 10) + 1;
  742. // var t = getElCoordinate(This.$workArea[0]);
  743. // This.$textArea.val(oldTxt).css({
  744. // display: 'block',
  745. // width: 120,
  746. // height: 26,
  747. // left: t.left + x - This.$workArea[0].parentNode.scrollLeft,
  748. // top: t.top + y - This.$workArea[0].parentNode.scrollTop
  749. // }).data('id', p.id).focus();
  750. // This.$workArea.parent().one('mouseup', function (e) {
  751. // if (e.button == 2) return false;
  752. // if (This.$textArea.css('display') == 'block') {
  753. // This.setName(This.$textArea.data('id'), This.$textArea.val(), 'area');
  754. // This.$textArea.val('').removeData('id').hide();
  755. // }
  756. // return false;
  757. // });
  758. // return false;
  759. // });
  760. // 绑定点击事件
  761. this.$group.mouseup({
  762. inthis: this
  763. }, function (e) {
  764. var This = e.data.inthis;
  765. if (This.$textArea.css('display') == 'block') {
  766. This.setName(This.$textArea.data('id'), This.$textArea.val(), 'area');
  767. This.$textArea.val('').removeData('id').hide();
  768. return false;
  769. };
  770. if (This.$nowType != 'group') return;
  771. if (!e) e = window.event;
  772. switch ($(e.target).attr('class')) {
  773. case 'rs_close':
  774. This.delArea(e.target.parentNode.parentNode.id);
  775. return false; // 删除该分组区域
  776. case 'bg':
  777. return;
  778. }
  779. switch (e.target.tagName) {
  780. case 'LABEL':
  781. return false;
  782. case 'I': // 绑定变色功能
  783. var id = e.target.parentNode.id;
  784. switch (This.$areaData[id].color) {
  785. case 'red':
  786. This.setAreaColor(id, 'yellow');
  787. break;
  788. case 'yellow':
  789. This.setAreaColor(id, 'blue');
  790. break;
  791. case 'blue':
  792. This.setAreaColor(id, 'green');
  793. break;
  794. case 'green':
  795. This.setAreaColor(id, 'red');
  796. break;
  797. }
  798. return false;
  799. }
  800. if (e.data.inthis.$ghost.css('display') == 'none') {
  801. var X, Y;
  802. var ev = mousePosition(e),
  803. t = getElCoordinate(this);
  804. X = ev.x - t.left + this.parentNode.parentNode.scrollLeft;
  805. Y = ev.y - t.top + this.parentNode.parentNode.scrollTop;
  806. var color = ['red', 'yellow', 'blue', 'green'];
  807. e.data.inthis.addArea(new Date().getTime(), {
  808. name: 'area_' + e.data.inthis.$max,
  809. left: X,
  810. top: Y,
  811. color: color[e.data.inthis.$max % 4],
  812. width: 200,
  813. height: 100
  814. });
  815. e.data.inthis.$max++;
  816. return false;
  817. }
  818. });
  819. },
  820. // 加入手动扩展编辑区功能,一次扩展200px
  821. initExpendFunc: function () {
  822. this.$workArea.append('<div class="Gooflow_extend_right"></div><div class="Gooflow_extend_bottom"></div>');
  823. this.$workArea.children('.Gooflow_extend_right').on('click', {
  824. inthis: this
  825. }, function (e) {
  826. var This = e.data.inthis;
  827. var w = This.$workArea.width() + This.$workExtendStep;
  828. var h = This.$workArea.height();
  829. This.$workArea.css({
  830. width: w + 'px'
  831. });
  832. if (GooFlow.prototype.useSVG == '') {
  833. This.$draw.coordsize = w + ',' + h;
  834. }
  835. This.$draw.style.width = w + 'px';
  836. if (This.$group != null) {
  837. This.$group.css({
  838. width: w + 'px'
  839. });
  840. }
  841. var parentDiv = This.$workArea.parent()[0];
  842. parentDiv.scrollLeft = parentDiv.scrollWidth;
  843. return false;
  844. });
  845. this.$workArea.children('.Gooflow_extend_bottom').on('click', {
  846. inthis: this
  847. }, function (e) {
  848. var This = e.data.inthis
  849. var w = This.$workArea.width();
  850. var h = This.$workArea.height() + This.$workExtendStep;
  851. This.$workArea.css({
  852. height: h + 'px'
  853. });
  854. if (GooFlow.prototype.useSVG == '') {
  855. This.$draw.coordsize = w + ',' + h;
  856. }
  857. This.$draw.style.height = h + 'px';
  858. if (This.$group != null) {
  859. This.$group.css({
  860. height: h + 'px'
  861. });
  862. }
  863. var parentDiv = This.$workArea.parent()[0];
  864. parentDiv.scrollTop = parentDiv.scrollHeight;
  865. return false;
  866. });
  867. },
  868. // 初始化用来改变连线的连接端点的两个小方块的操作事件
  869. initLinePointsChg: function () {
  870. this.$mpFrom.on('mousedown', {
  871. inthis: this
  872. }, function (e) {
  873. var This = e.data.inthis;
  874. This.switchToolBtn('cursor');
  875. var ps = This.$mpFrom.data('p').split(',');
  876. var pe = This.$mpTo.data('p').split(',');
  877. $(this).hide();
  878. This.$workArea.data('lineEnd', {
  879. 'x': pe[0],
  880. 'y': pe[1],
  881. 'id': This.$lineData[This.$lineOper.data('tid')].to
  882. }).css('cursor', 'crosshair');
  883. var line = GooFlow.prototype.drawLine('GooFlow_tmp_line', [ps[0], ps[1]], [pe[0], pe[1]], true, true);
  884. This.$draw.appendChild(line);
  885. return false;
  886. });
  887. this.$mpTo.on('mousedown', {
  888. inthis: this
  889. }, function (e) {
  890. var This = e.data.inthis;
  891. This.switchToolBtn('cursor');
  892. var ps = This.$mpFrom.data('p').split(',');
  893. var pe = This.$mpTo.data('p').split(',');
  894. $(this).hide();
  895. This.$workArea.data('lineStart', {
  896. 'x': ps[0],
  897. 'y': ps[1],
  898. 'id': This.$lineData[This.$lineOper.data('tid')].from
  899. }).css('cursor', 'crosshair');
  900. var line = GooFlow.prototype.drawLine('GooFlow_tmp_line', [ps[0], ps[1]], [pe[0], pe[1]], true, true);
  901. This.$draw.appendChild(line);
  902. return false;
  903. });
  904. },
  905. // 每一种类型结点及其按钮的说明文字
  906. setNodeRemarks: function (remark) {
  907. if (this.$tool == null) return;
  908. this.$tool.children('a').each(function () {
  909. this.title = remark[$(this).attr('id').split('btn_')[1]];
  910. });
  911. this.$nodeRemark = remark;
  912. },
  913. // 切换左边工具栏按钮,传参TYPE表示切换成哪种类型的按钮
  914. switchToolBtn: function (type) {
  915. this.$tool.children('#' + this.$id + '_btn_' + this.$nowType.split(' ')[0]).attr('class', 'GooFlow_tool_btn');
  916. if (this.$nowType == 'group') {
  917. this.$workArea.prepend(this.$group);
  918. for (var key in this.$areaDom) this.$areaDom[key].addClass('lock').children('div:eq(1)').css('display', 'none');
  919. }
  920. this.$nowType = type;
  921. this.$tool.children('#' + this.$id + '_btn_' + type.split(' ')[0]).attr('class', 'GooFlow_tool_btndown');
  922. if (this.$nowType == 'group') {
  923. this.blurItem();
  924. this.$workArea.append(this.$group);
  925. for (var key in this.$areaDom) this.$areaDom[key].removeClass('lock').children('div:eq(1)').css('display', '');
  926. } else if (this.$nowType == 'direct') {
  927. this.blurItem();
  928. }
  929. if (this.$textArea.css('display') == 'none') this.$textArea.removeData('id').val('').hide();
  930. },
  931. // 增加一个流程结点,传参为一个JSON,有id,name,top,left,width,height,type(结点类型)等属性
  932. addNode: function (id, json) {
  933. if (this.onItemAdd != null && !this.onItemAdd(id, 'node', json)) return;
  934. if (this.$undoStack && this.$editable) {
  935. this.pushOper('delNode', [id]);
  936. }
  937. var mark = json.marked ? ' item_mark' : '';
  938. var mark2 = mark + json.type === 'plug' ? 'plugShape' : '';
  939. if (json.type.indexOf(' round') < 0 && json.type !== 'plug') {
  940. if (!json.width || json.width < 104) json.width = 104;
  941. if (!json.height || json.height < 55) json.height = 55;
  942. if (!json.top || json.top < 0) json.top = 0;
  943. if (!json.left || json.left < 0) json.left = 0;
  944. if (json.type === 'plug') {
  945. json.width = 60;
  946. json.height = 60;
  947. }
  948. this.$nodeDom[id] = $("<div class='GooFlow_item " + mark + "' id='" + id + "' style='top:" + json.top + 'px;left:' + json.left + "px'><table cellspacing='1' style='width:" + (json.width - 2) + 'px;height:' + (json.height - 2) + "px;'><tr><td class='icon ico'><i class='ico_" + json.type + "'></i></td><td class='icon'>" + json.name + "</td></tr></table><div style='display:none'><div class='rs_bottom'></div><div class='rs_right'></div><div class='rs_rb'></div><div class='rs_close'></div></div></div>");
  949. } else if (json.type === 'plug') {
  950. json.width = 60;
  951. json.height = 60;
  952. if (!json.top || json.top < 0) json.top = 0;
  953. if (!json.left || json.left < 0) json.left = 0;
  954. this.$nodeDom[id] = $("<div class='GooFlow_item " + mark2 + "' id='" + id + "' style='top:" + json.top + 'px;left:' + json.left + "px;'><table cellspacing='0' style='width:" + (json.width - 2) + 'px;height:' + (json.height - 2) + "px;'><tr><td class='icon ico'><i class='ico_" + json.type + "'></i></td></tr></table><div style='display:none'><div class='rs_close'></div></div><div class='span'>" + json.name + '</div></div>');
  955. } else {
  956. json.width = 26;
  957. json.height = 26;
  958. json.top += 16;
  959. this.$nodeDom[id] = $("<div class='GooFlow_item item_round " + mark + "' id='" + id + "' style='top:" + json.top + 'px;left:' + json.left + "px;'><table cellspacing='0'><tr><td class='icon ico'><i class='ico_" + json.type + "'></i></td></tr></table><div style='display:none'><div class='rs_close'></div></div><div class='span'>" + json.name + '</div></div>');
  960. }
  961. if (GooFlow.prototype.color.node) {
  962. if (json.type.indexOf(' mix') > -1) {
  963. this.$nodeDom[id].css({
  964. 'background-color': GooFlow.prototype.color.mix,
  965. 'border-color': GooFlow.prototype.color.mix
  966. });
  967. if (GooFlow.prototype.color.mixFont) {
  968. this.$nodeDom[id].find('td:eq(1)').css('color', GooFlow.prototype.color.mixFont);
  969. this.$nodeDom[id].find('.span').css('color', GooFlow.prototype.color.mixFont);
  970. }
  971. } else {
  972. this.$nodeDom[id].css({
  973. 'background-color': GooFlow.prototype.color.node,
  974. 'border-color': GooFlow.prototype.color.node
  975. });
  976. }
  977. if (mark && GooFlow.prototype.color.mark) {
  978. this.$nodeDom[id].css({
  979. 'border-color': GooFlow.prototype.color.mark
  980. });
  981. }
  982. }
  983. if (json.type.indexOf(' mix') > -1) {
  984. this.$nodeDom[id].addClass('item_mix');
  985. }
  986. var ua = navigator.userAgent.toLowerCase();
  987. if (ua.indexOf('msie') != -1 && ua.indexOf('8.0') != -1) {
  988. this.$nodeDom[id].css('filter', 'progid:DXImageTransform.Microsoft.Shadow(color=#94AAC2,direction=135,strength=2)');
  989. }
  990. this.$workArea.append(this.$nodeDom[id]);
  991. json.group = '';
  992. this.$nodeData[id] = json;
  993. ++this.$nodeCount;
  994. if (this.$editable) {
  995. this.$nodeData[id].alt = true;
  996. if (this.$deletedItem[id]) delete this.$deletedItem[id]; // 在回退删除操作时,去掉该元素的删除记录
  997. }
  998. },
  999. initWorkForNode: function () {
  1000. // 绑定点击事件
  1001. this.$workArea.delegate('.GooFlow_item', 'click', {
  1002. inthis: this
  1003. }, function (e) {
  1004. e.data.inthis.focusItem(this.id, true);
  1005. $(this).removeClass('item_mark');
  1006. });
  1007. // 绑定用鼠标移动事件
  1008. this.$workArea.delegate('.icon', 'mousedown', {
  1009. inthis: this
  1010. }, function (e) {
  1011. if (!e) e = window.event;
  1012. if (e.button == 2) return false;
  1013. var This = e.data.inthis;
  1014. if (This.$nowType == 'direct') return;
  1015. var Dom = $(this).parents('.GooFlow_item');
  1016. var id = Dom.attr('id');
  1017. This.focusItem(id, true);
  1018. var ev = mousePosition(e),
  1019. t = getElCoordinate(This.$workArea[0]);
  1020. Dom.children('table').clone().prependTo(This.$ghost);
  1021. var X, Y;
  1022. X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft;
  1023. Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop;
  1024. var vX = X - This.$nodeData[id].left,
  1025. vY = Y - This.$nodeData[id].top;
  1026. var isMove = false;
  1027. document.onmousemove = function (e) {
  1028. if (!e) e = window.event;
  1029. var ev = mousePosition(e);
  1030. if (X == ev.x - vX && Y == ev.y - vY) return false;
  1031. X = ev.x - vX;
  1032. Y = ev.y - vY;
  1033. if (isMove && This.$ghost.css('display') == 'none') {
  1034. This.$ghost.css({
  1035. display: 'block',
  1036. width: This.$nodeData[id].width + 'px',
  1037. height: This.$nodeData[id].height + 'px',
  1038. top: This.$nodeData[id].top + t.top - This.$workArea[0].parentNode.scrollTop + 'px',
  1039. left: This.$nodeData[id].left + t.left - This.$workArea[0].parentNode.scrollLeft + 'px',
  1040. cursor: 'move'
  1041. });
  1042. }
  1043. if (X < t.left - This.$workArea[0].parentNode.scrollLeft) {
  1044. X = t.left - This.$workArea[0].parentNode.scrollLeft;
  1045. } else if (X + This.$workArea[0].parentNode.scrollLeft + This.$nodeData[id].width > t.left + This.$workArea.width()) {
  1046. X = t.left + This.$workArea.width() - This.$workArea[0].parentNode.scrollLeft - This.$nodeData[id].width;
  1047. }
  1048. if (Y < t.top - This.$workArea[0].parentNode.scrollTop) {
  1049. Y = t.top - This.$workArea[0].parentNode.scrollTop;
  1050. } else if (Y + This.$workArea[0].parentNode.scrollTop + This.$nodeData[id].height > t.top + This.$workArea.height()) {
  1051. Y = t.top + This.$workArea.height() - This.$workArea[0].parentNode.scrollTop - This.$nodeData[id].height;
  1052. }
  1053. This.$ghost.css({
  1054. left: X + 'px',
  1055. top: Y + 'px'
  1056. });
  1057. isMove = true;
  1058. }
  1059. document.onmouseup = function (e) {
  1060. if (isMove) This.moveNode(id, X + This.$workArea[0].parentNode.scrollLeft - t.left, Y + This.$workArea[0].parentNode.scrollTop - t.top);
  1061. This.$ghost.empty().hide();
  1062. document.onmousemove = null;
  1063. document.onmouseup = null;
  1064. }
  1065. });
  1066. if (!this.$editable) return;
  1067. // 绑定鼠标覆盖/移出事件
  1068. this.$workArea.delegate('.GooFlow_item', 'mouseenter', {
  1069. inthis: this
  1070. }, function (e) {
  1071. if (e.data.inthis.$nowType != 'direct' && !document.getElementById('GooFlow_tmp_line')) return;
  1072. $(this).addClass('item_mark').addClass('crosshair').css('border-color', GooFlow.prototype.color.mark || '#ff8800');
  1073. });
  1074. this.$workArea.delegate('.GooFlow_item', 'mouseleave', {
  1075. inthis: this
  1076. }, function (e) {
  1077. if (e.data.inthis.$nowType != 'direct' && !document.getElementById('GooFlow_tmp_line')) return;
  1078. $(this).removeClass('item_mark').removeClass('crosshair');
  1079. if (this.id == e.data.inthis.$focus) {
  1080. $(this).css('border-color', GooFlow.prototype.color.line || '#3892D3');
  1081. } else {
  1082. $(this).css('border-color', GooFlow.prototype.color.node || '#A1DCEB');
  1083. }
  1084. });
  1085. // 绑定连线时确定初始点
  1086. this.$workArea.delegate('.GooFlow_item', 'mousedown', {
  1087. inthis: this
  1088. }, function (e) {
  1089. if (e.button == 2) return false;
  1090. var This = e.data.inthis;
  1091. if (This.$nowType != 'direct') return;
  1092. var ev = mousePosition(e),
  1093. t = getElCoordinate(This.$workArea[0]);
  1094. var X, Y;
  1095. X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft;
  1096. Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop;
  1097. This.$workArea.data('lineStart', {
  1098. 'x': X,
  1099. 'y': Y,
  1100. 'id': this.id
  1101. }).css('cursor', 'crosshair');
  1102. var line = GooFlow.prototype.drawLine('GooFlow_tmp_line', [X, Y], [X, Y], true, true);
  1103. This.$draw.appendChild(line);
  1104. });
  1105. // 绑定连线时确定结束点
  1106. this.$workArea.delegate('.GooFlow_item', 'mouseup', {
  1107. inthis: this
  1108. }, function (e) {
  1109. var This = e.data.inthis;
  1110. if (This.$nowType != 'direct' && !This.$mpTo.data('p')) return;
  1111. var lineStart = This.$workArea.data('lineStart');
  1112. var lineEnd = This.$workArea.data('lineEnd');
  1113. if (lineStart && !This.$mpTo.data('p')) {
  1114. var id = This.generateMixed(4) + String(new Date().getTime());
  1115. This.addLine(id, {
  1116. from: lineStart.id,
  1117. to: this.id,
  1118. name: ''
  1119. });
  1120. This.$max++;
  1121. } else {
  1122. if (lineStart) {
  1123. This.moveLinePoints(This.$focus, lineStart.id, this.id);
  1124. } else if (lineEnd) {
  1125. This.moveLinePoints(This.$focus, this.id, lineEnd.id);
  1126. }
  1127. if (!This.$nodeData[this.id].marked) {
  1128. $(this).removeClass('item_mark');
  1129. if (this.id != This.$focus) {
  1130. $(this).css('border-color', GooFlow.prototype.color.node);
  1131. } else {
  1132. $(this).css('border-color', GooFlow.prototype.color.line);
  1133. }
  1134. }
  1135. }
  1136. });
  1137. // 绑定双击编辑事件
  1138. // this.$workArea.delegate('.GooFlow_item > .span', 'dblclick', {
  1139. // inthis: this
  1140. // }, function (e) {
  1141. // var oldTxt = this.innerHTML;
  1142. // var This = e.data.inthis;
  1143. // var id = this.parentNode.id;
  1144. // var t = getElCoordinate(This.$workArea[0]);
  1145. // This.$textArea.val(oldTxt).css({
  1146. // display: 'block',
  1147. // height: $(this).height() + 6,
  1148. // width: 100,
  1149. // left: t.left + This.$nodeData[id].left - This.$workArea[0].parentNode.scrollLeft - 26,
  1150. // top: t.top + This.$nodeData[id].top - This.$workArea[0].parentNode.scrollTop + 26
  1151. // })
  1152. // .data('id', This.$focus).focus();
  1153. // This.$workArea.parent().one('mousedown', function (e) {
  1154. // if (e.button == 2) return false;
  1155. // This.setName(This.$textArea.data('id'), This.$textArea.val(), 'node');
  1156. // This.$textArea.val('').removeData('id').hide();
  1157. // });
  1158. // });
  1159. // this.$workArea.delegate('.ico + td', 'dblclick', {
  1160. // inthis: this
  1161. // }, function (e) {
  1162. // var oldTxt = this.innerHTML;
  1163. // var This = e.data.inthis;
  1164. // var id = $(this).parents('.GooFlow_item').attr('id');
  1165. // var t = getElCoordinate(This.$workArea[0]);
  1166. // This.$textArea.val(oldTxt).css({
  1167. // display: 'block',
  1168. // width: $(this).width() + 26,
  1169. // height: $(this).height() + 6,
  1170. // left: t.left + 26 + This.$nodeData[id].left - This.$workArea[0].parentNode.scrollLeft,
  1171. // top: t.top + 2 + This.$nodeData[id].top - This.$workArea[0].parentNode.scrollTop
  1172. // })
  1173. // .data('id', This.$focus).focus();
  1174. // This.$workArea.parent().one('mousedown', function (e) {
  1175. // if (e.button == 2) return false;
  1176. // This.setName(This.$textArea.data('id'), This.$textArea.val(), 'node');
  1177. // This.$textArea.val('').removeData('id').hide();
  1178. // });
  1179. // });
  1180. // 绑定结点的删除功能
  1181. this.$workArea.delegate('.rs_close', 'click', {
  1182. inthis: this
  1183. }, function (e) {
  1184. if (!e) e = window.event;
  1185. e.data.inthis.delNode(e.data.inthis.$focus);
  1186. return false;
  1187. });
  1188. // 绑定结点的RESIZE功能
  1189. this.$workArea.delegate('.GooFlow_item > div > div[class!=rs_close]', 'mousedown', {
  1190. inthis: this
  1191. }, function (e) {
  1192. if (!e) e = window.event;
  1193. if (e.button == 2) return false;
  1194. var cursor = $(this).css('cursor');
  1195. if (cursor == 'pointer') {
  1196. return;
  1197. }
  1198. var This = e.data.inthis;
  1199. var id = This.$focus;
  1200. This.switchToolBtn('cursor');
  1201. e.cancelBubble = true;
  1202. e.stopPropagation();
  1203. var ev = mousePosition(e),
  1204. t = getElCoordinate(This.$workArea[0]);
  1205. This.$ghost.css({
  1206. display: 'block',
  1207. width: This.$nodeData[id].width + 'px',
  1208. height: This.$nodeData[id].height + 'px',
  1209. top: This.$nodeData[id].top + t.top - This.$workArea[0].parentNode.scrollTop + 'px',
  1210. left: This.$nodeData[id].left + t.left - This.$workArea[0].parentNode.scrollLeft + 'px',
  1211. cursor: cursor
  1212. });
  1213. var X, Y;
  1214. X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft;
  1215. Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop;
  1216. var vX = (This.$nodeData[id].left + This.$nodeData[id].width) - X;
  1217. var vY = (This.$nodeData[id].top + This.$nodeData[id].height) - Y;
  1218. var isMove = false;
  1219. This.$ghost.css('cursor', cursor);
  1220. document.onmousemove = function (e) {
  1221. if (!e) e = window.event;
  1222. var ev = mousePosition(e);
  1223. X = ev.x - t.left + This.$workArea[0].parentNode.scrollLeft - This.$nodeData[id].left + vX;
  1224. Y = ev.y - t.top + This.$workArea[0].parentNode.scrollTop - This.$nodeData[id].top + vY;
  1225. if (X < 104) X = 104;
  1226. if (Y < 26) Y = 26;
  1227. isMove = true;
  1228. switch (cursor) {
  1229. case 'nw-resize':
  1230. This.$ghost.css({
  1231. width: X + 'px',
  1232. height: Y + 'px'
  1233. });
  1234. break;
  1235. case 'w-resize':
  1236. This.$ghost.css({
  1237. width: X + 'px'
  1238. });
  1239. break;
  1240. case 'n-resize':
  1241. This.$ghost.css({
  1242. height: Y + 'px'
  1243. });
  1244. break;
  1245. }
  1246. }
  1247. document.onmouseup = function (e) {
  1248. document.onmousemove = null;
  1249. document.onmouseup = null;
  1250. This.$ghost.hide();
  1251. if (!isMove) return;
  1252. if (!e) e = window.event;
  1253. This.resizeNode(id, This.$ghost.outerWidth(), This.$ghost.outerHeight());
  1254. }
  1255. });
  1256. },
  1257. // 获取结点/连线/分组区域的详细信息
  1258. getItemInfo: function (id, type) {
  1259. switch (type) {
  1260. case 'node':
  1261. return this.$nodeData[id] || null;
  1262. case 'line':
  1263. return this.$lineData[id] || null;
  1264. case 'area':
  1265. return this.$areaData[id] || null;
  1266. }
  1267. },
  1268. // 取消所有结点/连线被选定的状态
  1269. blurItem: function () {
  1270. if (this.$focus != '') {
  1271. var jq = $('#' + this.$focus);
  1272. if (jq.prop('tagName') == 'DIV') {
  1273. if (this.onItemBlur != null && !this.onItemBlur(this.$focus, 'node')) return false;
  1274. jq.removeClass('item_focus').children('div:eq(0)').css('display', 'none');
  1275. if (this.$nodeData[this.$focus].marked) {
  1276. jq.addClass('item_mark').css('border-color', GooFlow.prototype.color.mark || '#ff8800');
  1277. }
  1278. } else {
  1279. if (this.onItemBlur != null && !this.onItemBlur(this.$focus, 'line')) return false;
  1280. if (GooFlow.prototype.useSVG != '') {
  1281. if (!this.$lineData[this.$focus].marked) {
  1282. jq[0].childNodes[1].setAttribute('stroke', GooFlow.prototype.color.line || '#3892D3');
  1283. jq[0].childNodes[1].setAttribute('marker-end', 'url(#arrow1)');
  1284. }
  1285. } else {
  1286. if (!this.$lineData[this.$focus].marked) {
  1287. jq[0].strokeColor = GooFlow.prototype.color.line || '#3892D3';
  1288. }
  1289. }
  1290. this.$lineMove.hide().removeData('type').removeData('tid');
  1291. if (this.$editable) {
  1292. this.$lineOper.hide().removeData('tid');
  1293. this.$mpFrom.hide().removeData('p');
  1294. this.$mpTo.hide().removeData('p');
  1295. }
  1296. }
  1297. }
  1298. this.$focus = '';
  1299. return true;
  1300. },
  1301. // 选定某个结点/转换线 bool:TRUE决定了要触发选中事件,FALSE则不触发选中事件,多用在程序内部调用。
  1302. focusItem: function (id, bool) {
  1303. var jq = $('#' + id);
  1304. if (jq.length == 0) return;
  1305. if (!this.blurItem()) return; // 先执行"取消选中",如果返回FLASE,则也会阻止选定事件继续进行.
  1306. if (jq.prop('tagName') == 'DIV') {
  1307. if (bool && this.onItemFocus != null && !this.onItemFocus(id, 'node'));
  1308. jq.addClass('item_focus');
  1309. if (GooFlow.prototype.color.line) {
  1310. jq.css('border-color', GooFlow.prototype.color.line);
  1311. }
  1312. if (this.$editable) jq.children('div:eq(0)').css('display', 'block');
  1313. this.$workArea.append(jq);
  1314. } else { // 如果是连接线
  1315. if (this.onItemFocus != null && !this.onItemFocus(id, 'line'));
  1316. if (GooFlow.prototype.useSVG != '') {
  1317. jq[0].childNodes[1].setAttribute('stroke', GooFlow.prototype.color.mark || '#ff8800');
  1318. jq[0].childNodes[1].setAttribute('marker-end', 'url(#arrow2)');
  1319. } else {
  1320. jq[0].strokeColor = GooFlow.prototype.color.mark || '#ff8800';
  1321. }
  1322. if (!this.$editable) return;
  1323. var x, y, from, to, n;
  1324. if (GooFlow.prototype.useSVG != '') {
  1325. from = jq.attr('from').split(',');
  1326. to = jq.attr('to').split(',');
  1327. n = [from[0], from[1], to[0], to[1]];
  1328. } else {
  1329. n = jq[0].getAttribute('fromTo').split(',');
  1330. from = [n[0], n[1]];
  1331. to = [n[2], n[3]];
  1332. }
  1333. from[0] = parseInt(from[0], 10);
  1334. from[1] = parseInt(from[1], 10);
  1335. to[0] = parseInt(to[0], 10);
  1336. to[1] = parseInt(to[1], 10);
  1337. // var t=getElCoordinate(this.$workArea[0]);
  1338. if (this.$lineData[id].type == 'lr') {
  1339. from[0] = this.$lineData[id].M;
  1340. to[0] = from[0];
  1341. this.$lineMove.css({
  1342. width: '5px',
  1343. height: (to[1] - from[1]) * (to[1] > from[1] ? 1 : -1) + 'px',
  1344. left: from[0] - 3 + 'px',
  1345. top: (to[1] > from[1] ? from[1] : to[1]) + 1 + 'px',
  1346. cursor: 'e-resize',
  1347. display: 'block'
  1348. }).data({
  1349. 'type': 'lr',
  1350. 'tid': id
  1351. });
  1352. } else if (this.$lineData[id].type == 'tb') {
  1353. from[1] = this.$lineData[id].M;
  1354. to[1] = from[1];
  1355. this.$lineMove.css({
  1356. width: (to[0] - from[0]) * (to[0] > from[0] ? 1 : -1) + 'px',
  1357. height: '5px',
  1358. left: (to[0] > from[0] ? from[0] : to[0]) + 1 + 'px',
  1359. top: from[1] - 3 + 'px',
  1360. cursor: 's-resize',
  1361. display: 'block'
  1362. }).data({
  1363. 'type': 'tb',
  1364. 'tid': id
  1365. });
  1366. }
  1367. x = (from[0] + to[0]) / 2 - 40;
  1368. y = (from[1] + to[1]) / 2 + 4;
  1369. this.$lineOper.css({
  1370. display: 'block',
  1371. left: x + 'px',
  1372. top: y + 'px'
  1373. }).data('tid', id);
  1374. if (this.$editable) {
  1375. this.$mpFrom.css({
  1376. display: 'block',
  1377. left: n[0] - 4 + 'px',
  1378. top: n[1] - 4 + 'px'
  1379. }).data('p', n[0] + ',' + n[1]);
  1380. this.$mpTo.css({
  1381. display: 'block',
  1382. left: n[2] - 4 + 'px',
  1383. top: n[3] - 4 + 'px'
  1384. }).data('p', n[2] + ',' + n[3]);
  1385. }
  1386. this.$draw.appendChild(jq[0]);
  1387. }
  1388. this.$focus = id;
  1389. this.switchToolBtn('cursor');
  1390. },
  1391. // 移动结点到一个新的位置
  1392. moveNode: function (id, left, top) {
  1393. if (!this.$nodeData[id]) return;
  1394. if (this.onItemMove != null && !this.onItemMove(id, 'node', left, top)) return;
  1395. if (this.$undoStack) {
  1396. var paras = [id, this.$nodeData[id].left, this.$nodeData[id].top];
  1397. this.pushOper('moveNode', paras);
  1398. }
  1399. if (left < 0) left = 0;
  1400. if (top < 0) top = 0;
  1401. $('#' + id).css({
  1402. left: left + 'px',
  1403. top: top + 'px'
  1404. });
  1405. this.$nodeData[id].left = left;
  1406. this.$nodeData[id].top = top;
  1407. // 重画转换线
  1408. this.resetLines(id, this.$nodeData[id]);
  1409. if (this.$editable) {
  1410. this.$nodeData[id].alt = true;
  1411. }
  1412. },
  1413. // 设置结点/连线/分组区域的文字信息
  1414. setName: function (id, name, type) {
  1415. var oldName;
  1416. if (type == 'node') { // 如果是结点
  1417. if (!this.$nodeData[id]) return;
  1418. if (this.$nodeData[id].name == name) return;
  1419. if (this.onItemRename != null && !this.onItemRename(id, name, 'node'));
  1420. oldName = this.$nodeData[id].name;
  1421. this.$nodeData[id].name = name;
  1422. if (this.$nodeData[id].type.indexOf('round') > 1) {
  1423. this.$nodeDom[id].children('.span').text(name);
  1424. } else {
  1425. this.$nodeDom[id].find('td:eq(1)').text(name);
  1426. var width = this.$nodeDom[id].width();
  1427. var height = this.$nodeDom[id].height();
  1428. this.$nodeDom[id].children('table').css({
  1429. width: width + 'px',
  1430. height: height + 'px'
  1431. });
  1432. this.$nodeData[id].width = width;
  1433. this.$nodeData[id].height = height;
  1434. }
  1435. if (this.$editable) {
  1436. this.$nodeData[id].alt = true;
  1437. }
  1438. // 重画转换线
  1439. this.resetLines(id, this.$nodeData[id]);
  1440. } else if (type == 'line') { // 如果是线
  1441. if (!this.$lineData[id]) return;
  1442. if (this.$lineData[id].name == name) return;
  1443. if (this.onItemRename != null && !this.onItemRename(id, name, 'line'));
  1444. oldName = this.$lineData[id].name;
  1445. this.$lineData[id].name = name;
  1446. if (GooFlow.prototype.useSVG != '') {
  1447. this.$lineDom[id].childNodes[2].textContent = name;
  1448. } else {
  1449. this.$lineDom[id].childNodes[1].innerHTML = name;
  1450. var n = this.$lineDom[id].getAttribute('fromTo').split(',');
  1451. var x;
  1452. if (this.$lineData[id].type != 'lr') {
  1453. x = (n[2] - n[0]) / 2;
  1454. } else {
  1455. var Min = n[2] > n[0] ? n[0] : n[2];
  1456. if (Min > this.$lineData[id].M) Min = this.$lineData[id].M;
  1457. x = this.$lineData[id].M - Min;
  1458. }
  1459. if (x < 0) x = x * -1;
  1460. this.$lineDom[id].childNodes[1].style.left = x - this.$lineDom[id].childNodes[1].offsetWidth / 2 + 4 + 'px';
  1461. }
  1462. if (this.$editable) {
  1463. this.$lineData[id].alt = true;
  1464. }
  1465. } else if (type == 'area') { // 如果是分组区域
  1466. if (!this.$areaData[id]) return;
  1467. if (this.$areaData[id].name == name) return;
  1468. if (this.onItemRename != null && !this.onItemRename(id, name, 'area'));
  1469. oldName = this.$areaData[id].name;
  1470. this.$areaData[id].name = name;
  1471. this.$areaDom[id].children('label').text(name);
  1472. if (this.$editable) {
  1473. this.$areaData[id].alt = true;
  1474. }
  1475. }
  1476. if (this.$undoStack) {
  1477. var paras = [id, oldName, type];
  1478. this.pushOper('setName', paras);
  1479. }
  1480. },
  1481. // 设置结点的尺寸,仅支持非开始/结束结点
  1482. resizeNode: function (id, width, height) {
  1483. if (!this.$nodeData[id]) return;
  1484. if (this.onItemResize != null && !this.onItemResize(id, 'node', width, height)) return;
  1485. if (this.$nodeData[id].type == 'start' || this.$nodeData[id].type == 'end') return;
  1486. if (this.$undoStack) {
  1487. var paras = [id, this.$nodeData[id].width, this.$nodeData[id].height];
  1488. this.pushOper('resizeNode', paras);
  1489. }
  1490. this.$nodeDom[id].children('table').css({
  1491. width: width - 2 + 'px',
  1492. height: height - 2 + 'px'
  1493. });
  1494. // width=this.$nodeDom[id].outerWidth();
  1495. // height=this.$nodeDom[id].outerHeight();
  1496. // this.$nodeDom[id].children("table").css({width:width-2+"px",height:height-2+"px"});
  1497. this.$nodeData[id].width = width;
  1498. this.$nodeData[id].height = height;
  1499. if (this.$editable) {
  1500. this.$nodeData[id].alt = true;
  1501. }
  1502. // 重画转换线
  1503. this.resetLines(id, this.$nodeData[id]);
  1504. },
  1505. // 删除结点
  1506. delNode: function (id, trigger) {
  1507. if (!this.$nodeData[id]) return;
  1508. if (trigger != false && this.onItemDel != null && !this.onItemDel(id, 'node')) return;
  1509. // 先删除可能的连线
  1510. for (var k in this.$lineData) {
  1511. if (this.$lineData[k].from == id || this.$lineData[k].to == id) {
  1512. // this.$draw.removeChild(this.$lineDom[k]);
  1513. // delete this.$lineData[k];
  1514. // delete this.$lineDom[k];
  1515. this.delLine(k, false);
  1516. }
  1517. }
  1518. // 再删除结点本身
  1519. if (this.$undoStack) {
  1520. var paras = [id, this.$nodeData[id]];
  1521. this.pushOper('addNode', paras);
  1522. }
  1523. delete this.$nodeData[id];
  1524. this.$nodeDom[id].remove();
  1525. delete this.$nodeDom[id];
  1526. --this.$nodeCount;
  1527. if (this.$focus == id) this.$focus = '';
  1528. if (this.$editable) {
  1529. // 在回退新增操作时,如果节点ID以this.$id+"_node_"开头,则表示为本次编辑时新加入的节点,这些节点的删除不用加入到$deletedItem中
  1530. if (id.indexOf(this.$id + '_node_') < 0) {
  1531. this.$deletedItem[id] = 'node';
  1532. }
  1533. }
  1534. },
  1535. // 设置流程图的名称
  1536. setTitle: function (text) {
  1537. this.$title = text;
  1538. if (this.$head) this.$head.children('label').attr('title', text).text(text);
  1539. },
  1540. setId: function (id) {
  1541. this.$id = id
  1542. },
  1543. // 载入一组数据
  1544. loadData: function (data) {
  1545. var t = this.$editable;
  1546. this.$editable = false;
  1547. if (data.title) this.setTitle(data.title);
  1548. if (data.initNum) this.$max = data.initNum;
  1549. this.$id = data.id;
  1550. for (var i in data.nodes) {
  1551. this.addNode(i, data.nodes[i]);
  1552. }
  1553. for (var j in data.lines) {
  1554. this.addLine(j, data.lines[j]);
  1555. }
  1556. for (var k in data.areas) {
  1557. this.addArea(k, data.areas[k]);
  1558. }
  1559. this.$editable = t;
  1560. this.$deletedItem = {};
  1561. // 自行重构工作区,使之大小自适应
  1562. var width = this.$workArea.width();
  1563. var height = this.$workArea.height();
  1564. var maxW = 0,
  1565. maxH = 0;
  1566. for (var key in this.$nodeData) {
  1567. var item = this.$nodeData[key];
  1568. if (maxW < item.width + item.left) {
  1569. maxW = item.width + item.left;
  1570. }
  1571. if (maxH < item.height + item.top) {
  1572. maxH = item.height + item.top;
  1573. }
  1574. }
  1575. for (var key in this.$areaData) {
  1576. var item = this.$areaData[key];
  1577. if (maxW < item.width + item.left) {
  1578. maxW = item.width + item.left;
  1579. }
  1580. if (maxH < item.height + item.top) {
  1581. maxH = item.height + item.top;
  1582. }
  1583. }
  1584. M = 0;
  1585. for (var key in this.$lineData) {
  1586. var item = this.$lineData[key];
  1587. if (item.M && item.type == 'lt' && maxW < item.M) {
  1588. maxW = M + 4;
  1589. }
  1590. if (item.M && item.type == 'tb' && maxH < item.M) {
  1591. maxH = M + 4;
  1592. }
  1593. }
  1594. while (maxW > width) {
  1595. width += this.$workExtendStep;
  1596. }
  1597. while (maxH > height) {
  1598. height += this.$workExtendStep;
  1599. }
  1600. this.$workArea.css({
  1601. height: height + 'px',
  1602. width: width + 'px'
  1603. });
  1604. if (GooFlow.prototype.useSVG == '') {
  1605. this.$draw.coordsize = width + ',' + height;
  1606. }
  1607. this.$draw.style.width = width + 'px';
  1608. this.$draw.style.height = +height + 'px';
  1609. if (this.$group != null) {
  1610. this.$group.css({
  1611. height: height + 'px',
  1612. width: width + 'px'
  1613. });
  1614. }
  1615. },
  1616. // 用AJAX方式,远程读取一组数据
  1617. // 参数para为JSON结构,与JQUERY中$.ajax()方法的传参一样
  1618. loadDataAjax: function (para) {
  1619. var This = this;
  1620. $.ajax({
  1621. type: para.type,
  1622. url: para.url,
  1623. dataType: 'json',
  1624. data: para.data,
  1625. success: function (msg) {
  1626. if (para.dataFilter) para.dataFilter(msg, 'json');
  1627. This.loadData(msg);
  1628. if (para.success) para.success(msg);
  1629. },
  1630. error: function (XMLHttpRequest, textStatus, errorThrown) {
  1631. if (para.error) para.error(textStatus, errorThrown);
  1632. }
  1633. })
  1634. },
  1635. // 把画好的整个流程图导出到一个变量中(其实也可以直接访问GooFlow对象的$nodeData,$lineData,$areaData这三个JSON属性)
  1636. exportData: function () {
  1637. var ret = {
  1638. title: this.$title,
  1639. id: this.$id,
  1640. nodes: this.$nodeData,
  1641. lines: this.$lineData,
  1642. areas: this.$areaData,
  1643. initNum: this.$max
  1644. };
  1645. for (var k1 in ret.nodes) {
  1646. if (!ret.nodes[k1].marked) {
  1647. delete ret.nodes[k1]['marked'];
  1648. }
  1649. }
  1650. for (var k2 in ret.lines) {
  1651. if (!ret.lines[k2].marked) {
  1652. delete ret.lines[k2]['marked'];
  1653. }
  1654. }
  1655. return ret;
  1656. },
  1657. exportDataToXML: function (JsonData) {
  1658. let file = `
  1659. <?xml version="1.0" encoding="UTF-8"?>
  1660. <definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:tns="http://www.activiti.org/test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd" id="test2019" exporter="camunda modeler" exporterVersion="2.7.1" name="" targetNamespace="http://www.activiti.org/test">
  1661. <process id="${this.$id}" name="${this.$title}" isExecutable="true">
  1662. `
  1663. let nodeData = '',
  1664. lineData = '',
  1665. detail = '',
  1666. startData = '',
  1667. data = '',
  1668. endData = '';
  1669. // 转换节点
  1670. for (let itemNode in this.$nodeData) {
  1671. if (this.$nodeData[itemNode].type.indexOf('start') >= 0) {
  1672. startData += `
  1673. <startEvent id="${itemNode}" name="${this.$nodeData[itemNode].name}">
  1674. ${this.checkNodeLine(itemNode)}
  1675. </startEvent>
  1676. `
  1677. } else if (this.$nodeData[itemNode].type.indexOf('end') >= 0) {
  1678. endData += `
  1679. <endEvent id="${itemNode}" name="${this.$nodeData[itemNode].name}">
  1680. ${this.checkNodeLine(itemNode)}
  1681. </endEvent>
  1682. `
  1683. } else if (this.$nodeData[itemNode].type === 'plug') {
  1684. data += `
  1685. <exclusiveGateway id="${itemNode}" name="${this.$nodeData[itemNode].name}">
  1686. ${this.checkNodeLine(itemNode)}
  1687. </exclusiveGateway>
  1688. `
  1689. } else {
  1690. data += `
  1691. <userTask id="${itemNode}" activiti:candidateGroups="${this.$nodeData[itemNode].group}" activiti:exclusive="true" name="${this.$nodeData[itemNode].name}">
  1692. ${this.checkNodeLine(itemNode)}
  1693. </userTask>
  1694. `
  1695. }
  1696. }
  1697. nodeData = startData + data + endData;
  1698. // 转换线
  1699. for (let item in this.$lineData) {
  1700. if (this.$lineData[item].rules) {
  1701. lineData += `
  1702. <sequenceFlow id="${item}" sourceRef="${this.$lineData[item].from}" targetRef="${this.$lineData[item].to}">
  1703. <conditionExpression xsi:type="tFormalExpression">
  1704. <![CDATA[ ${this.$lineData[item].rules} ]]>
  1705. </conditionExpression>
  1706. </sequenceFlow>
  1707. `
  1708. } else {
  1709. lineData += `
  1710. <sequenceFlow id="${item}" sourceRef="${this.$lineData[item].from}" targetRef="${this.$lineData[item].to}"/>
  1711. `
  1712. }
  1713. }
  1714. lineData += '</process>'
  1715. // 生成描述
  1716. detail += `
  1717. <bpmndi:BPMNDiagram documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
  1718. <bpmndi:BPMNPlane bpmnElement="${this.$id}">
  1719. `
  1720. for (let item in this.$nodeData) {
  1721. detail += `
  1722. <bpmndi:BPMNShape id="Shape-${item}" bpmnElement="${item}">
  1723. <omgdc:Bounds height="${this.$nodeData[item].height}" width="${this.$nodeData[item].width}" x="${this.$nodeData[item].left}" y="${this.$nodeData[item].top}"/>
  1724. <bpmndi:BPMNLabel>
  1725. <omgdc:Bounds height="${this.$nodeData[item].height}" width="${this.$nodeData[item].width}" x="0.0" y="0.0"/>
  1726. </bpmndi:BPMNLabel>
  1727. </bpmndi:BPMNShape>
  1728. ` // + this.$nodeData[item].width / 2 // + this.$nodeData[item].height / 2
  1729. }
  1730. for (let item in this.$lineData) {
  1731. if (this.$lineData[item].type !== 'sl') {
  1732. let arr = this.calcPolyPoints(this.$nodeData[this.$lineData[item].from], this.$nodeData[this.$lineData[item].to], this.$lineData[item].type, this.$lineData[item].M)
  1733. detail += `
  1734. <bpmndi:BPMNEdge id="BPMNEdge_${item}" bpmnElement="${item}">
  1735. <omgdi:waypoint xsi:type="omgdc:Point" x="${arr.start[0]}" y="${arr.start[1]}"/>
  1736. <omgdi:waypoint xsi:type="omgdc:Point" x="${arr.m1[0]}" y="${arr.m1[1]}"/>
  1737. <omgdi:waypoint xsi:type="omgdc:Point" x="${arr.m2[0]}" y="${arr.m2[1]}"/>
  1738. <omgdi:waypoint xsi:type="omgdc:Point" x="${arr.end[0]}" y="${arr.end[1]}"/>
  1739. <bpmndi:BPMNLabel>
  1740. <omgdc:Bounds height="-1.0" width="-1.0" x="-1.0" y="-1.0"/>
  1741. </bpmndi:BPMNLabel>
  1742. </bpmndi:BPMNEdge>
  1743. `
  1744. } else {
  1745. let arr = this.calcStartEnd(this.$nodeData[this.$lineData[item].from], this.$nodeData[this.$lineData[item].to])
  1746. detail += `
  1747. <bpmndi:BPMNEdge id="BPMNEdge_${item}" bpmnElement="${item}">
  1748. <omgdi:waypoint xsi:type="omgdc:Point" x="${arr.start[0]}" y="${arr.start[1]}"/>
  1749. <omgdi:waypoint xsi:type="omgdc:Point" x="${arr.end[0]}" y="${arr.end[1]}"/>
  1750. <bpmndi:BPMNLabel>
  1751. <omgdc:Bounds height="-1.0" width="-1.0" x="-1.0" y="-1.0"/>
  1752. </bpmndi:BPMNLabel>
  1753. </bpmndi:BPMNEdge>
  1754. `
  1755. }
  1756. }
  1757. detail += `
  1758. </bpmndi:BPMNPlane>
  1759. </bpmndi:BPMNDiagram>
  1760. </definitions>
  1761. `
  1762. return (file + nodeData + lineData + detail)
  1763. },
  1764. // addRule: function (id, rules) {
  1765. // this.$lineData[id].rules = '';
  1766. // this.$lineData[id].rules = rules;
  1767. // },
  1768. checkNodeLine: function (itemName) {
  1769. let goIn = '';
  1770. let goOut = '';
  1771. for (let itemLine in this.$lineData) {
  1772. if (this.$lineData[itemLine].from === itemName) {
  1773. goOut += '<outgoing>' + itemLine + '</outgoing>'
  1774. } else if (this.$lineData[itemLine].to === itemName) {
  1775. goIn += '<incoming>' + itemLine + '</incoming>'
  1776. }
  1777. }
  1778. return (goIn + goOut)
  1779. },
  1780. // 只把本次编辑流程图中作了变更(包括增删改)的元素导出到一个变量中,以方便用户每次编辑载入的流程图后只获取变更过的数据
  1781. exportAlter: function () {
  1782. var ret = {
  1783. nodes: {},
  1784. lines: {},
  1785. areas: {}
  1786. };
  1787. for (var k1 in this.$nodeData) {
  1788. if (this.$nodeData[k1].alt) {
  1789. ret.nodes[k1] = this.$nodeData[k1];
  1790. }
  1791. }
  1792. for (var k2 in this.$lineData) {
  1793. if (this.$lineData[k2].alt) {
  1794. ret.lines[k2] = this.$lineData[k2];
  1795. }
  1796. }
  1797. for (var k3 in this.$areaData) {
  1798. if (this.$areaData[k3].alt) {
  1799. ret.areas[k3] = this.$areaData[k3];
  1800. }
  1801. }
  1802. ret.deletedItem = this.$deletedItem;
  1803. return ret;
  1804. },
  1805. // 变更元素的ID,一般用于快速保存后,将后台返回新元素的ID更新到页面中;type为元素类型(节点,连线,区块)
  1806. transNewId: function (oldId, newId, type) {
  1807. var tmp;
  1808. switch (type) {
  1809. case 'node':
  1810. if (this.$nodeData[oldId]) {
  1811. tmp = this.$nodeData[oldId];
  1812. delete this.$nodeData[oldId];
  1813. this.$nodeData[newId] = tmp;
  1814. tmp = this.$nodeDom[oldId].attr('id', newId);
  1815. delete this.$nodeDom[oldId];
  1816. this.$nodeDom[newId] = tmp;
  1817. }
  1818. break;
  1819. case 'line':
  1820. if (this.$lineData[oldId]) {
  1821. tmp = this.$lineData[oldId];
  1822. delete this.$lineData[oldId];
  1823. this.$lineData[newId] = tmp;
  1824. tmp = this.$lineDom[oldId].attr('id', newId);
  1825. delete this.$lineDom[oldId];
  1826. this.$lineDom[newId] = tmp;
  1827. }
  1828. break;
  1829. case 'area':
  1830. if (this.$areaData[oldId]) {
  1831. tmp = this.$areaData[oldId];
  1832. delete this.$areaData[oldId];
  1833. this.$areaData[newId] = tmp;
  1834. tmp = this.$areaDom[oldId].attr('id', newId);
  1835. delete this.$areaDom[oldId];
  1836. this.$areaDom[newId] = tmp;
  1837. }
  1838. break;
  1839. }
  1840. },
  1841. // 清空工作区及已载入的数据
  1842. clearData: function () {
  1843. for (var key in this.$nodeData) {
  1844. this.delNode(key);
  1845. }
  1846. for (var key in this.$lineData) {
  1847. this.delLine(key);
  1848. }
  1849. for (var key in this.$areaData) {
  1850. this.delArea(key);
  1851. }
  1852. this.$deletedItem = {};
  1853. },
  1854. // 销毁自己
  1855. destrory: function () {
  1856. this.$bgDiv.empty();
  1857. this.$lineData = null;
  1858. this.$nodeData = null;
  1859. this.$lineDom = null;
  1860. this.$nodeDom = null;
  1861. this.$areaDom = null;
  1862. this.$areaData = null;
  1863. this.$nodeCount = 0;
  1864. this.$areaCount = 0;
  1865. this.$areaCount = 0;
  1866. this.$deletedItem = {};
  1867. },
  1868. /// ////////以下为有关画线的方法
  1869. // 绘制一条箭头线,并返回线的DOM
  1870. drawLine: function (id, sp, ep, mark, dash) {
  1871. var line;
  1872. if (GooFlow.prototype.useSVG != '') {
  1873. line = document.createElementNS('http://www.w3.org/2000/svg', 'g');
  1874. var hi = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  1875. var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  1876. if (id != '') line.setAttribute('id', id);
  1877. line.setAttribute('from', sp[0] + ',' + sp[1]);
  1878. line.setAttribute('to', ep[0] + ',' + ep[1]);
  1879. hi.setAttribute('visibility', 'hidden');
  1880. hi.setAttribute('stroke-width', 9);
  1881. hi.setAttribute('fill', 'none');
  1882. hi.setAttribute('stroke', 'white');
  1883. hi.setAttribute('d', 'M ' + sp[0] + ' ' + sp[1] + ' L ' + ep[0] + ' ' + ep[1]);
  1884. hi.setAttribute('pointer-events', 'stroke');
  1885. path.setAttribute('d', 'M ' + sp[0] + ' ' + sp[1] + ' L ' + ep[0] + ' ' + ep[1]);
  1886. path.setAttribute('stroke-width', mark ? 2.4 : 1.4);
  1887. path.setAttribute('stroke-linecap', 'round');
  1888. path.setAttribute('fill', 'none');
  1889. if (dash) path.setAttribute('style', 'stroke-dasharray:6,5');
  1890. if (mark) {
  1891. path.setAttribute('stroke', GooFlow.prototype.color.mark || '#ff8800');
  1892. path.setAttribute('marker-end', 'url(#arrow2)');
  1893. } else {
  1894. path.setAttribute('stroke', GooFlow.prototype.color.line || '#3892D3');
  1895. path.setAttribute('marker-end', 'url(#arrow1)');
  1896. }
  1897. line.appendChild(hi);
  1898. line.appendChild(path);
  1899. line.style.cursor = 'crosshair';
  1900. if (id != '' && id != 'GooFlow_tmp_line') {
  1901. var text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
  1902. text.setAttribute('fill', GooFlow.prototype.color.lineFont || '#333');
  1903. line.appendChild(text);
  1904. var x = (ep[0] + sp[0]) / 2;
  1905. var y = (ep[1] + sp[1]) / 2;
  1906. text.setAttribute('text-anchor', 'middle');
  1907. text.setAttribute('x', x);
  1908. text.setAttribute('y', y);
  1909. line.style.cursor = 'pointer';
  1910. text.style.cursor = 'text';
  1911. }
  1912. } else {
  1913. line = document.createElement('v:polyline');
  1914. if (id != '') line.id = id;
  1915. // line.style.position="absolute";
  1916. line.points.value = sp[0] + ',' + sp[1] + ' ' + ep[0] + ',' + ep[1];
  1917. line.setAttribute('fromTo', sp[0] + ',' + sp[1] + ',' + ep[0] + ',' + ep[1]);
  1918. line.strokeWeight = '1.2';
  1919. line.stroke.EndArrow = 'Block';
  1920. line.style.cursor = 'crosshair';
  1921. if (id != '' && id != 'GooFlow_tmp_line') {
  1922. var text = document.createElement('div');
  1923. // text.innerHTML=id;
  1924. line.appendChild(text);
  1925. var x = (ep[0] - sp[0]) / 2;
  1926. var y = (ep[1] - sp[1]) / 2;
  1927. if (x < 0) x = x * -1;
  1928. if (y < 0) y = y * -1;
  1929. text.style.left = x + 'px';
  1930. text.style.top = y - 6 + 'px';
  1931. line.style.cursor = 'pointer';
  1932. }
  1933. if (dash) line.stroke.dashstyle = 'Dash';
  1934. if (mark) line.strokeColor = GooFlow.prototype.color.mark || '#ff8800';
  1935. else line.strokeColor = GooFlow.prototype.color.line || '#3892D3';
  1936. line.fillColor = GooFlow.prototype.color.line || '#3892D3';
  1937. }
  1938. return line;
  1939. },
  1940. // 画一条只有两个中点的折线
  1941. drawPoly: function (id, sp, m1, m2, ep, mark) {
  1942. var poly, strPath;
  1943. if (GooFlow.prototype.useSVG != '') {
  1944. poly = document.createElementNS('http://www.w3.org/2000/svg', 'g');
  1945. var hi = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  1946. var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  1947. if (id != '') poly.setAttribute('id', id);
  1948. poly.setAttribute('from', sp[0] + ',' + sp[1]);
  1949. poly.setAttribute('to', ep[0] + ',' + ep[1]);
  1950. hi.setAttribute('visibility', 'hidden');
  1951. hi.setAttribute('stroke-width', 9);
  1952. hi.setAttribute('fill', 'none');
  1953. hi.setAttribute('stroke', 'white');
  1954. strPath = 'M ' + sp[0] + ' ' + sp[1];
  1955. if (m1[0] != sp[0] || m1[1] != sp[1]) {
  1956. strPath += ' L ' + m1[0] + ' ' + m1[1];
  1957. }
  1958. if (m2[0] != ep[0] || m2[1] != ep[1]) {
  1959. strPath += ' L ' + m2[0] + ' ' + m2[1];
  1960. }
  1961. strPath += ' L ' + ep[0] + ' ' + ep[1];
  1962. hi.setAttribute('d', strPath);
  1963. hi.setAttribute('pointer-events', 'stroke');
  1964. path.setAttribute('d', strPath);
  1965. path.setAttribute('stroke-width', mark ? 2.4 : 1.4);
  1966. path.setAttribute('stroke-linecap', 'round');
  1967. path.setAttribute('fill', 'none');
  1968. if (mark) {
  1969. path.setAttribute('stroke', GooFlow.prototype.color.mark || '#ff8800');
  1970. path.setAttribute('marker-end', 'url(#arrow2)');
  1971. } else {
  1972. path.setAttribute('stroke', GooFlow.prototype.color.line || '#3892D3');
  1973. path.setAttribute('marker-end', 'url(#arrow1)');
  1974. }
  1975. poly.appendChild(hi);
  1976. poly.appendChild(path);
  1977. var text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
  1978. text.setAttribute('fill', GooFlow.prototype.color.lineFont || '#333');
  1979. poly.appendChild(text);
  1980. var x = (m2[0] + m1[0]) / 2;
  1981. var y = (m2[1] + m1[1]) / 2;
  1982. text.setAttribute('text-anchor', 'middle');
  1983. text.setAttribute('x', x);
  1984. text.setAttribute('y', y);
  1985. text.style.cursor = 'text';
  1986. poly.style.cursor = 'pointer';
  1987. } else {
  1988. poly = document.createElement('v:Polyline');
  1989. if (id != '') poly.id = id;
  1990. poly.filled = 'false';
  1991. strPath = sp[0] + ',' + sp[1];
  1992. if (m1[0] != sp[0] || m1[1] != sp[1]) {
  1993. strPath += ' ' + m1[0] + ',' + m1[1];
  1994. }
  1995. if (m2[0] != ep[0] || m2[1] != ep[1]) {
  1996. strPath += ' ' + m2[0] + ',' + m2[1];
  1997. }
  1998. strPath += ' ' + ep[0] + ',' + ep[1];
  1999. poly.points.value = strPath;
  2000. poly.setAttribute('fromTo', sp[0] + ',' + sp[1] + ',' + ep[0] + ',' + ep[1]);
  2001. poly.strokeWeight = mark ? '2.4' : '1.2';
  2002. poly.stroke.EndArrow = 'Block';
  2003. var text = document.createElement('div');
  2004. // text.innerHTML=id;
  2005. poly.appendChild(text);
  2006. var x = (m2[0] - m1[0]) / 2;
  2007. var y = (m2[1] - m1[1]) / 2;
  2008. if (x < 0) x = x * -1;
  2009. if (y < 0) y = y * -1;
  2010. text.style.left = x + 'px';
  2011. text.style.top = y - 4 + 'px';
  2012. poly.style.cursor = 'pointer';
  2013. if (mark) poly.strokeColor = GooFlow.prototype.color.mark || '#ff8800';
  2014. else poly.strokeColor = GooFlow.prototype.color.line || '#3892D3';
  2015. }
  2016. return poly;
  2017. },
  2018. // 计算两个结点间要连直线的话,连线的开始坐标和结束坐标
  2019. calcStartEnd: function (n1, n2) {
  2020. var X_1, Y_1, X_2, Y_2;
  2021. // X判断:
  2022. var x11 = n1.left,
  2023. x12 = n1.left + n1.width,
  2024. x21 = n2.left,
  2025. x22 = n2.left + n2.width;
  2026. // 结点2在结点1左边
  2027. if (x11 >= x22) {
  2028. X_1 = x11;
  2029. X_2 = x22;
  2030. }
  2031. // 结点2在结点1右边
  2032. else if (x12 <= x21) {
  2033. X_1 = x12;
  2034. X_2 = x21;
  2035. }
  2036. // 结点2在结点1水平部分重合
  2037. else if (x11 <= x21 && x12 >= x21 && x12 <= x22) {
  2038. X_1 = (x12 + x21) / 2;
  2039. X_2 = X_1;
  2040. } else if (x11 >= x21 && x12 <= x22) {
  2041. X_1 = (x11 + x12) / 2;
  2042. X_2 = X_1;
  2043. } else if (x21 >= x11 && x22 <= x12) {
  2044. X_1 = (x21 + x22) / 2;
  2045. X_2 = X_1;
  2046. } else if (x11 <= x22 && x12 >= x22) {
  2047. X_1 = (x11 + x22) / 2;
  2048. X_2 = X_1;
  2049. }
  2050. // Y判断:
  2051. var y11 = n1.top,
  2052. y12 = n1.top + n1.height,
  2053. y21 = n2.top,
  2054. y22 = n2.top + n2.height;
  2055. // 结点2在结点1上边
  2056. if (y11 >= y22) {
  2057. Y_1 = y11;
  2058. Y_2 = y22;
  2059. }
  2060. // 结点2在结点1下边
  2061. else if (y12 <= y21) {
  2062. Y_1 = y12;
  2063. Y_2 = y21;
  2064. }
  2065. // 结点2在结点1垂直部分重合
  2066. else if (y11 <= y21 && y12 >= y21 && y12 <= y22) {
  2067. Y_1 = (y12 + y21) / 2;
  2068. Y_2 = Y_1;
  2069. } else if (y11 >= y21 && y12 <= y22) {
  2070. Y_1 = (y11 + y12) / 2;
  2071. Y_2 = Y_1;
  2072. } else if (y21 >= y11 && y22 <= y12) {
  2073. Y_1 = (y21 + y22) / 2;
  2074. Y_2 = Y_1;
  2075. } else if (y11 <= y22 && y12 >= y22) {
  2076. Y_1 = (y11 + y22) / 2;
  2077. Y_2 = Y_1;
  2078. }
  2079. return {
  2080. 'start': [X_1, Y_1],
  2081. 'end': [X_2, Y_2]
  2082. };
  2083. },
  2084. // 计算两个结点间要连折线的话,连线的所有坐标
  2085. calcPolyPoints: function (n1, n2, type, M) {
  2086. // 开始/结束两个结点的中心
  2087. var SP = {
  2088. x: n1.left + n1.width / 2,
  2089. y: n1.top + n1.height / 2
  2090. };
  2091. var EP = {
  2092. x: n2.left + n2.width / 2,
  2093. y: n2.top + n2.height / 2
  2094. };
  2095. var sp = [],
  2096. m1 = [],
  2097. m2 = [],
  2098. ep = [];
  2099. // 如果是允许中段可左右移动的折线,则参数M为可移动中段线的X坐标
  2100. // 粗略计算起始点
  2101. sp = [SP.x, SP.y];
  2102. ep = [EP.x, EP.y];
  2103. if (type == 'lr') {
  2104. // 粗略计算2个中点
  2105. m1 = [M, SP.y];
  2106. m2 = [M, EP.y];
  2107. // 再具体分析修改开始点和中点1
  2108. if (m1[0] > n1.left && m1[0] < n1.left + n1.width) {
  2109. m1[1] = (SP.y > EP.y ? n1.top : n1.top + n1.height);
  2110. sp[0] = m1[0];
  2111. sp[1] = m1[1];
  2112. } else {
  2113. sp[0] = (m1[0] < n1.left ? n1.left : n1.left + n1.width)
  2114. }
  2115. // 再具体分析中点2和结束点
  2116. if (m2[0] > n2.left && m2[0] < n2.left + n2.width) {
  2117. m2[1] = (SP.y > EP.y ? n2.top + n2.height : n2.top);
  2118. ep[0] = m2[0];
  2119. ep[1] = m2[1];
  2120. } else {
  2121. ep[0] = (m2[0] < n2.left ? n2.left : n2.left + n2.width)
  2122. }
  2123. }
  2124. // 如果是允许中段可上下移动的折线,则参数M为可移动中段线的Y坐标
  2125. else if (type == 'tb') {
  2126. // 粗略计算2个中点
  2127. m1 = [SP.x, M];
  2128. m2 = [EP.x, M];
  2129. // 再具体分析修改开始点和中点1
  2130. if (m1[1] > n1.top && m1[1] < n1.top + n1.height) {
  2131. m1[0] = (SP.x > EP.x ? n1.left : n1.left + n1.width);
  2132. sp[0] = m1[0];
  2133. sp[1] = m1[1];
  2134. } else {
  2135. sp[1] = (m1[1] < n1.top ? n1.top : n1.top + n1.height)
  2136. }
  2137. // 再具体分析中点2和结束点
  2138. if (m2[1] > n2.top && m2[1] < n2.top + n2.height) {
  2139. m2[0] = (SP.x > EP.x ? n2.left + n2.width : n2.left);
  2140. ep[0] = m2[0];
  2141. ep[1] = m2[1];
  2142. } else {
  2143. ep[1] = (m2[1] < n2.top ? n2.top : n2.top + n2.height);
  2144. }
  2145. }
  2146. return {
  2147. start: sp,
  2148. m1: m1,
  2149. m2: m2,
  2150. end: ep
  2151. };
  2152. },
  2153. // 初始化折线中段的X/Y坐标,mType='rb'时为X坐标,mType='tb'时为Y坐标
  2154. getMValue: function (n1, n2, mType) {
  2155. if (mType == 'lr') {
  2156. return (n1.left + n1.width / 2 + n2.left + n2.width / 2) / 2;
  2157. } else if (mType == 'tb') {
  2158. return (n1.top + n1.height / 2 + n2.top + n2.height / 2) / 2;
  2159. }
  2160. },
  2161. // 原lineData已经设定好的情况下,只在绘图工作区画一条线的页面元素
  2162. addLineDom: function (id, lineData) {
  2163. var n1 = this.$nodeData[lineData.from],
  2164. n2 = this.$nodeData[lineData.to]; // 获取开始/结束结点的数据
  2165. if (!n1 || !n2) return;
  2166. // 开始计算线端点坐标
  2167. var res;
  2168. if (lineData.type && lineData.type != 'sl') {
  2169. res = GooFlow.prototype.calcPolyPoints(n1, n2, lineData.type, lineData.M);
  2170. } else {
  2171. res = GooFlow.prototype.calcStartEnd(n1, n2);
  2172. }
  2173. if (!res) return;
  2174. if (lineData.type == 'sl') {
  2175. this.$lineDom[id] = GooFlow.prototype.drawLine(id, res.start, res.end, lineData.marked);
  2176. } else {
  2177. this.$lineDom[id] = GooFlow.prototype.drawPoly(id, res.start, res.m1, res.m2, res.end, lineData.marked);
  2178. }
  2179. this.$draw.appendChild(this.$lineDom[id]);
  2180. if (GooFlow.prototype.useSVG == '') {
  2181. this.$lineDom[id].childNodes[1].innerHTML = lineData.name;
  2182. if (lineData.type != 'sl') {
  2183. var Min = (res.start[0] > res.end[0] ? res.end[0] : res.start[0]);
  2184. if (Min > res.m2[0]) Min = res.m2[0];
  2185. if (Min > res.m1[0]) Min = res.m1[0];
  2186. this.$lineDom[id].childNodes[1].style.left = (res.m2[0] + res.m1[0]) / 2 - Min - this.$lineDom[id].childNodes[1].offsetWidth / 2 + 4;
  2187. Min = (res.start[1] > res.end[1] ? res.end[1] : res.start[1]);
  2188. if (Min > res.m2[1]) Min = res.m2[1];
  2189. if (Min > res.m1[1]) Min = res.m1[1];
  2190. this.$lineDom[id].childNodes[1].style.top = (res.m2[1] + res.m1[1]) / 2 - Min - this.$lineDom[id].childNodes[1].offsetHeight / 2;
  2191. } else {
  2192. this.$lineDom[id].childNodes[1].style.left =
  2193. ((res.end[0] - res.start[0]) * (res.end[0] > res.start[0] ? 1 : -1) - this.$lineDom[id].childNodes[1].offsetWidth) / 2 + 4;
  2194. }
  2195. } else this.$lineDom[id].childNodes[2].textContent = lineData.name;
  2196. },
  2197. // 增加一条线
  2198. addLine: function (id, json) {
  2199. if (this.onItemAdd != null && !this.onItemAdd(id, 'line', json)) return;
  2200. if (this.$undoStack && this.$editable) {
  2201. this.pushOper('delLine', [id]);
  2202. }
  2203. if (json.from == json.to) return;
  2204. var n1 = this.$nodeData[json.from],
  2205. n2 = this.$nodeData[json.to]; // 获取开始/结束结点的数据
  2206. if (!n1 || !n2) return;
  2207. // 避免两个节点间不能有一条以上同向接连线
  2208. for (var k in this.$lineData) {
  2209. if ((json.from == this.$lineData[k].from && json.to == this.$lineData[k].to)) {
  2210. return;
  2211. }
  2212. }
  2213. // 设置$lineData[id]
  2214. this.$lineData[id] = {};
  2215. if (json.type) {
  2216. this.$lineData[id].type = json.type;
  2217. this.$lineData[id].M = json.M;
  2218. } else this.$lineData[id].type = 'sl'; // 默认为直线
  2219. this.$lineData[id].from = json.from;
  2220. this.$lineData[id].to = json.to;
  2221. this.$lineData[id].name = json.name;
  2222. this.$lineData[id].rules = '';
  2223. if (json.marked) this.$lineData[id].marked = json.marked;
  2224. else this.$lineData[id].marked = false;
  2225. // 设置$lineData[id]完毕
  2226. this.addLineDom(id, this.$lineData[id]);
  2227. ++this.$lineCount;
  2228. if (this.$editable) {
  2229. this.$lineData[id].alt = true;
  2230. if (this.$deletedItem[id]) delete this.$deletedItem[id]; // 在回退删除操作时,去掉该元素的删除记录
  2231. }
  2232. },
  2233. // 重构所有连向某个结点的线的显示,传参结构为$nodeData数组的一个单元结构
  2234. resetLines: function (id, node) {
  2235. for (var i in this.$lineData) {
  2236. var other = null; // 获取结束/开始结点的数据
  2237. var res;
  2238. if (this.$lineData[i].from == id) { // 找结束点
  2239. other = this.$nodeData[this.$lineData[i].to] || null;
  2240. if (other == null) continue;
  2241. if (this.$lineData[i].type == 'sl') {
  2242. res = GooFlow.prototype.calcStartEnd(node, other);
  2243. } else {
  2244. res = GooFlow.prototype.calcPolyPoints(node, other, this.$lineData[i].type, this.$lineData[i].M)
  2245. }
  2246. if (!res) break;
  2247. } else if (this.$lineData[i].to == id) { // 找开始点
  2248. other = this.$nodeData[this.$lineData[i].from] || null;
  2249. if (other == null) continue;
  2250. if (this.$lineData[i].type == 'sl') {
  2251. res = GooFlow.prototype.calcStartEnd(other, node);
  2252. } else {
  2253. res = GooFlow.prototype.calcPolyPoints(other, node, this.$lineData[i].type, this.$lineData[i].M);
  2254. }
  2255. if (!res) break;
  2256. }
  2257. if (other == null) continue;
  2258. this.$draw.removeChild(this.$lineDom[i]);
  2259. if (this.$lineData[i].type == 'sl') {
  2260. this.$lineDom[i] = GooFlow.prototype.drawLine(i, res.start, res.end, this.$lineData[i].marked);
  2261. } else {
  2262. this.$lineDom[i] = GooFlow.prototype.drawPoly(i, res.start, res.m1, res.m2, res.end, this.$lineData[i].marked);
  2263. }
  2264. this.$draw.appendChild(this.$lineDom[i]);
  2265. if (GooFlow.prototype.useSVG == '') {
  2266. this.$lineDom[i].childNodes[1].innerHTML = this.$lineData[i].name;
  2267. if (this.$lineData[i].type != 'sl') {
  2268. var Min = (res.start[0] > res.end[0] ? res.end[0] : res.start[0]);
  2269. if (Min > res.m2[0]) Min = res.m2[0];
  2270. if (Min > res.m1[0]) Min = res.m1[0];
  2271. this.$lineDom[i].childNodes[1].style.left = (res.m2[0] + res.m1[0]) / 2 - Min - this.$lineDom[i].childNodes[1].offsetWidth / 2 + 4;
  2272. Min = (res.start[1] > res.end[1] ? res.end[1] : res.start[1]);
  2273. if (Min > res.m2[1]) Min = res.m2[1];
  2274. if (Min > res.m1[1]) Min = res.m1[1];
  2275. this.$lineDom[i].childNodes[1].style.top = (res.m2[1] + res.m1[1]) / 2 - Min - this.$lineDom[i].childNodes[1].offsetHeight / 2 - 4;
  2276. } else {
  2277. this.$lineDom[i].childNodes[1].style.left =
  2278. ((res.end[0] - res.start[0]) * (res.end[0] > res.start[0] ? 1 : -1) - this.$lineDom[i].childNodes[1].offsetWidth) / 2 + 4;
  2279. }
  2280. } else this.$lineDom[i].childNodes[2].textContent = this.$lineData[i].name;
  2281. }
  2282. },
  2283. // 重新设置连线的样式 newType= "sl":直线, "lr":中段可左右移动型折线, "tb":中段可上下移动型折线
  2284. setLineType: function (id, newType, M) {
  2285. if (!newType || newType == null || newType == '' || newType == this.$lineData[id].type) return false;
  2286. if (this.onLineSetType != null && !this.onLineSetType(id, newType)) return;
  2287. if (this.$undoStack) {
  2288. var paras = [id, this.$lineData[id].type, this.$lineData[id].M];
  2289. this.pushOper('setLineType', paras);
  2290. }
  2291. var from = this.$lineData[id].from;
  2292. var to = this.$lineData[id].to;
  2293. this.$lineData[id].type = newType;
  2294. var res;
  2295. // 如果是变成折线
  2296. if (newType != 'sl') {
  2297. var res = GooFlow.prototype.calcPolyPoints(this.$nodeData[from], this.$nodeData[to], this.$lineData[id].type, this.$lineData[id].M);
  2298. if (M) {
  2299. this.setLineM(id, M, true);
  2300. } else {
  2301. this.setLineM(id, this.getMValue(this.$nodeData[from], this.$nodeData[to], newType), true);
  2302. }
  2303. }
  2304. // 如果是变回直线
  2305. else {
  2306. delete this.$lineData[id].M;
  2307. this.$lineMove.hide().removeData('type').removeData('tid');
  2308. res = GooFlow.prototype.calcStartEnd(this.$nodeData[from], this.$nodeData[to]);
  2309. if (!res) return;
  2310. this.$draw.removeChild(this.$lineDom[id]);
  2311. this.$lineDom[id] = GooFlow.prototype.drawLine(id, res.start, res.end, this.$lineData[id].marked);
  2312. this.$draw.appendChild(this.$lineDom[id]);
  2313. if (GooFlow.prototype.useSVG == '') {
  2314. this.$lineDom[id].childNodes[1].innerHTML = this.$lineData[id].name;
  2315. this.$lineDom[id].childNodes[1].style.left =
  2316. ((res.end[0] - res.start[0]) * (res.end[0] > res.start[0] ? 1 : -1) - this.$lineDom[id].childNodes[1].offsetWidth) / 2 + 4;
  2317. } else {
  2318. this.$lineDom[id].childNodes[2].textContent = this.$lineData[id].name;
  2319. }
  2320. }
  2321. if (this.$focus == id) {
  2322. this.focusItem(id);
  2323. }
  2324. if (this.$editable) {
  2325. this.$lineData[id].alt = true;
  2326. }
  2327. },
  2328. // 设置折线中段的X坐标值(可左右移动时)或Y坐标值(可上下移动时)
  2329. setLineM: function (id, M, noStack) {
  2330. if (!this.$lineData[id] || M < 0 || !this.$lineData[id].type || this.$lineData[id].type == 'sl') return false;
  2331. if (this.onLineMove != null && !this.onLineMove(id, M)) return false;
  2332. if (this.$undoStack && !noStack) {
  2333. var paras = [id, this.$lineData[id].M];
  2334. this.pushOper('setLineM', paras);
  2335. }
  2336. var from = this.$lineData[id].from;
  2337. var to = this.$lineData[id].to;
  2338. this.$lineData[id].M = M;
  2339. var ps = GooFlow.prototype.calcPolyPoints(this.$nodeData[from], this.$nodeData[to], this.$lineData[id].type, this.$lineData[id].M);
  2340. this.$draw.removeChild(this.$lineDom[id]);
  2341. this.$lineDom[id] = GooFlow.prototype.drawPoly(id, ps.start, ps.m1, ps.m2, ps.end, this.$lineData[id].marked);
  2342. this.$draw.appendChild(this.$lineDom[id]);
  2343. if (GooFlow.prototype.useSVG == '') {
  2344. this.$lineDom[id].childNodes[1].innerHTML = this.$lineData[id].name;
  2345. var Min = (ps.start[0] > ps.end[0] ? ps.end[0] : ps.start[0]);
  2346. if (Min > ps.m2[0]) Min = ps.m2[0];
  2347. if (Min > ps.m1[0]) Min = ps.m1[0];
  2348. this.$lineDom[id].childNodes[1].style.left = (ps.m2[0] + ps.m1[0]) / 2 - Min - this.$lineDom[id].childNodes[1].offsetWidth / 2 + 4;
  2349. Min = (ps.start[1] > ps.end[1] ? ps.end[1] : ps.start[1]);
  2350. if (Min > ps.m2[1]) Min = ps.m2[1];
  2351. if (Min > ps.m1[1]) Min = ps.m1[1];
  2352. this.$lineDom[id].childNodes[1].style.top = (ps.m2[1] + ps.m1[1]) / 2 - Min - this.$lineDom[id].childNodes[1].offsetHeight / 2 - 4;
  2353. } else this.$lineDom[id].childNodes[2].textContent = this.$lineData[id].name;
  2354. if (this.$editable) {
  2355. this.$lineData[id].alt = true;
  2356. }
  2357. },
  2358. // 删除转换线
  2359. delLine: function (id, trigger) {
  2360. if (!this.$lineData[id]) return;
  2361. if (trigger != false && this.onItemDel != null && !this.onItemDel(id, 'node')) return;
  2362. if (this.$undoStack) {
  2363. var paras = [id, this.$lineData[id]];
  2364. this.pushOper('addLine', paras);
  2365. }
  2366. this.$draw.removeChild(this.$lineDom[id]);
  2367. delete this.$lineData[id];
  2368. delete this.$lineDom[id];
  2369. if (this.$focus == id) this.$focus = '';
  2370. --this.$lineCount;
  2371. if (this.$editable) {
  2372. // 在回退新增操作时,如果节点ID以this.$id+"_line_"开头,则表示为本次编辑时新加入的节点,这些节点的删除不用加入到$deletedItem中
  2373. if (id.indexOf(this.$id + '_line_') < 0) {
  2374. this.$deletedItem[id] = 'line';
  2375. }
  2376. this.$mpFrom.hide().removeData('p');
  2377. this.$mpTo.hide().removeData('p');
  2378. }
  2379. if (this.$lineOper) {
  2380. this.$lineOper.hide().removeData('tid');
  2381. }
  2382. },
  2383. // 变更连线两个端点所连的结点
  2384. // 参数:要变更端点的连线ID,新的开始结点ID、新的结束结点ID;如果开始/结束结点ID是传入null或者"",则表示原端点不变
  2385. moveLinePoints: function (lineId, newStart, newEnd, noStack) {
  2386. if (newStart == newEnd) return;
  2387. if (!lineId || !this.$lineData[lineId]) return;
  2388. if (newStart == null || newStart == '') {
  2389. newStart = this.$lineData[lineId].from;
  2390. }
  2391. if (newEnd == null || newEnd == '') {
  2392. newEnd = this.$lineData[lineId].to;
  2393. }
  2394. // 避免两个节点间不能有一条以上同向接连线
  2395. for (var k in this.$lineData) {
  2396. if ((newStart == this.$lineData[k].from && newEnd == this.$lineData[k].to)) {
  2397. return;
  2398. }
  2399. }
  2400. if (this.onLinePointMove != null && !this.onLinePointMove(id, newStart, newEnd)) return;
  2401. if (this.$undoStack && !noStack) {
  2402. var paras = [lineId, this.$lineData[lineId].from, this.$lineData[lineId].to];
  2403. this.pushOper('moveLinePoints', paras);
  2404. }
  2405. if (newStart != null && newStart != '') {
  2406. this.$lineData[lineId].from = newStart;
  2407. }
  2408. if (newEnd != null && newEnd != '') {
  2409. this.$lineData[lineId].to = newEnd;
  2410. }
  2411. // 重建转换线
  2412. this.$draw.removeChild(this.$lineDom[lineId]);
  2413. this.addLineDom(lineId, this.$lineData[lineId]);
  2414. if (this.$editable) {
  2415. this.$lineData[lineId].alt = true;
  2416. }
  2417. },
  2418. // 用颜色标注/取消标注一个结点或转换线,常用于显示重点或流程的进度。
  2419. // 这是一个在编辑模式中无用,但是在纯浏览模式中非常有用的方法,实际运用中可用于跟踪流程的进度。
  2420. markItem: function (id, type, mark) {
  2421. if (type == 'node') {
  2422. if (!this.$nodeData[id]) return;
  2423. if (this.onItemMark != null && !this.onItemMark(id, 'node', mark)) return;
  2424. this.$nodeData[id].marked = mark || false;
  2425. if (mark) {
  2426. this.$nodeDom[id].addClass('item_mark').css('border-color', GooFlow.prototype.color.mark);
  2427. } else {
  2428. this.$nodeDom[id].removeClass('item_mark');
  2429. if (id != this.$focus) this.$nodeDom[id].css('border-color', 'transparent');
  2430. }
  2431. } else if (type == 'line') {
  2432. if (!this.$lineData[id]) return;
  2433. if (this.onItemMark != null && !this.onItemMark(id, 'line', mark)) return;
  2434. this.$lineData[id].marked = mark || false;
  2435. if (GooFlow.prototype.useSVG != '') {
  2436. if (mark) {
  2437. this.$lineDom[id].childNodes[1].setAttribute('stroke', GooFlow.prototype.color.mark || '#ff8800');
  2438. this.$lineDom[id].childNodes[1].setAttribute('marker-end', 'url(#arrow2)');
  2439. this.$lineDom[id].childNodes[1].setAttribute('stroke-width', 2.4);
  2440. } else {
  2441. this.$lineDom[id].childNodes[1].setAttribute('stroke', GooFlow.prototype.color.line || '#3892D3');
  2442. this.$lineDom[id].childNodes[1].setAttribute('marker-end', 'url(#arrow1)');
  2443. this.$lineDom[id].childNodes[1].setAttribute('stroke-width', 1.4);
  2444. }
  2445. } else {
  2446. if (mark) {
  2447. this.$lineDom[id].strokeColor = GooFlow.prototype.color.mark || '#ff8800';
  2448. this.$lineDom[id].strokeWeight = '2.4';
  2449. } else {
  2450. this.$lineDom[id].strokeColor = GooFlow.prototype.color.line || '#3892D3';
  2451. this.$lineDom[id].strokeWeight = '1.2';
  2452. }
  2453. }
  2454. }
  2455. if (this.$undoStatck) {
  2456. var paras = [id, type, !mark];
  2457. this.pushOper('markItem', paras);
  2458. }
  2459. },
  2460. /// /////////////////////以下为区域分组块操作
  2461. moveArea: function (id, left, top) {
  2462. if (!this.$areaData[id]) return;
  2463. if (this.onItemMove != null && !this.onItemMove(id, 'area', left, top)) return;
  2464. if (this.$undoStack) {
  2465. var paras = [id, this.$areaData[id].left, this.$areaData[id].top];
  2466. this.pushOper('moveNode', paras);
  2467. }
  2468. if (left < 0) left = 0;
  2469. if (top < 0) top = 0;
  2470. $('#' + id).css({
  2471. left: left + 'px',
  2472. top: top + 'px'
  2473. });
  2474. this.$areaData[id].left = left;
  2475. this.$areaData[id].top = top;
  2476. if (this.$editable) {
  2477. this.$areaData[id].alt = true;
  2478. }
  2479. },
  2480. // 删除区域分组
  2481. delArea: function (id, trigger) {
  2482. if (!this.$areaData[id]) return;
  2483. if (this.$undoStack) {
  2484. var paras = [id, this.$areaData[id]];
  2485. this.pushOper('addArea', paras);
  2486. }
  2487. if (trigger != false && this.onItemDel != null && !this.onItemDel(id, 'node')) return;
  2488. delete this.$areaData[id];
  2489. this.$areaDom[id].remove();
  2490. delete this.$areaDom[id];
  2491. --this.$areaCount;
  2492. if (this.$editable) {
  2493. // 在回退新增操作时,如果节点ID以this.$id+"_area_"开头,则表示为本次编辑时新加入的节点,这些节点的删除不用加入到$deletedItem中
  2494. if (id.indexOf(this.$id + '_area_') < 0) {
  2495. this.$deletedItem[id] = 'area';
  2496. }
  2497. }
  2498. },
  2499. // 设置区域分组的颜色
  2500. setAreaColor: function (id, color) {
  2501. if (!this.$areaData[id]) return;
  2502. if (this.$undoStack) {
  2503. var paras = [id, this.$areaData[id].color];
  2504. this.pushOper('setAreaColor', paras);
  2505. }
  2506. if (color == 'red' || color == 'yellow' || color == 'blue' || color == 'green') {
  2507. this.$areaDom[id].removeClass('area_' + this.$areaData[id].color).addClass('area_' + color);
  2508. this.$areaData[id].color = color;
  2509. }
  2510. if (this.$editable) {
  2511. this.$areaData[id].alt = true;
  2512. }
  2513. },
  2514. // 设置区域分块的尺寸
  2515. resizeArea: function (id, width, height) {
  2516. if (!this.$areaData[id]) return;
  2517. if (this.onItemResize != null && !this.onItemResize(id, 'area', width, height)) return;
  2518. if (this.$undoStack) {
  2519. var paras = [id, this.$areaData[id].width, this.$areaData[id].height];
  2520. this.pushOper('resizeArea', paras);
  2521. }
  2522. this.$areaDom[id].children('.bg').css({
  2523. width: width + 'px',
  2524. height: height + 'px'
  2525. });
  2526. // width=this.$areaDom[id].outerWidth();
  2527. // height=this.$areaDom[id].outerHeight();
  2528. // this.$areaDom[id].children("bg").css({width:width+"px",height:height+"px"});
  2529. this.$areaData[id].width = width;
  2530. this.$areaData[id].height = height;
  2531. if (this.$editable) {
  2532. this.$areaData[id].alt = true;
  2533. }
  2534. },
  2535. addArea: function (id, json) {
  2536. if (this.onItemAdd != null && !this.onItemAdd(id, 'area', json)) return;
  2537. if (this.$undoStack && this.$editable) {
  2538. this.pushOper('delArea', [id]);
  2539. }
  2540. this.$areaDom[id] = $("<div id='" + id + "' class='GooFlow_area area_" + json.color + "' style='top:" + json.top + 'px;left:' + json.left + "px'><div class='bg' style='width:" + (json.width) + 'px;height:' + (json.height) + "px'></div>" +
  2541. '<label>' + json.name + "</label><i></i><div><div class='rs_bottom'></div><div class='rs_right'></div><div class='rs_rb'></div><div class='rs_close'></div></div></div>");
  2542. this.$areaData[id] = json;
  2543. this.$group.append(this.$areaDom[id]);
  2544. if (this.$nowType != 'group') this.$areaDom[id].children('div:eq(1)').css('display', 'none');
  2545. ++this.$areaCount;
  2546. if (this.$editable) {
  2547. this.$areaData[id].alt = true;
  2548. if (this.$deletedItem[id]) delete this.$deletedItem[id]; // 在回退删除操作时,去掉该元素的删除记录
  2549. }
  2550. },
  2551. // 重构整个流程图设计器的宽高
  2552. reinitSize: function (width, height) {
  2553. var w = (width || 800);
  2554. var h = (height || 500);
  2555. this.$bgDiv.css({
  2556. height: h + 'px',
  2557. width: w + 'px'
  2558. });
  2559. var headHeight = 0,
  2560. hack = 8;
  2561. if (this.$head != null) {
  2562. headHeight = 26;
  2563. hack = 5;
  2564. }
  2565. if (this.$tool != null) {
  2566. this.$tool.css({
  2567. height: h - headHeight - hack + 'px'
  2568. });
  2569. w -= 31;
  2570. }
  2571. w -= 9;
  2572. h = h - headHeight - (this.$head != null ? 5 : 8);
  2573. this.$workArea.parent().css({
  2574. height: h + 'px',
  2575. width: w + 'px'
  2576. });
  2577. if (this.$workArea.width() > w) {
  2578. w = this.$workArea.width();
  2579. }
  2580. if (this.$workArea.height() > h) {
  2581. h = this.$workArea.height();
  2582. }
  2583. this.$workArea.css({
  2584. height: h + 'px',
  2585. width: w + 'px'
  2586. });
  2587. if (GooFlow.prototype.useSVG == '') {
  2588. this.$draw.coordsize = w + ',' + h;
  2589. }
  2590. this.$draw.style.width = w + 'px';
  2591. this.$draw.style.height = +h + 'px';
  2592. if (this.$group != null) {
  2593. this.$group.css({
  2594. height: h + 'px',
  2595. width: w + 'px'
  2596. });
  2597. }
  2598. }
  2599. }
  2600. GooFlow.prototype.color = {};
  2601. // 将此类的构造函数加入至JQUERY对象中
  2602. jQuery.extend({
  2603. createGooFlow: function (bgDiv, property) {
  2604. return new GooFlow(bgDiv, property);
  2605. }
  2606. });