1/* DOKUWIKI:include raphael.js */ 2 3;style = 4{ 5 margin:JSINFO['plugin_callflow']['margin'], 6 txtsize:JSINFO['plugin_callflow']['txtsize'] , 7 titlesize:JSINFO['plugin_callflow']['titlesize'], 8 linespacing:JSINFO['plugin_callflow']['linespacing'],//note: MUST be bigger than txtsize 9 colspacing:JSINFO['plugin_callflow']['colspacing'], 10 strokewidth:JSINFO['plugin_callflow']['strokewidth'],//for arrows and breaks 11 strokecolor:JSINFO['plugin_callflow']['strokecolor'] , 12 txtcolor:JSINFO['plugin_callflow']['txtcolor'] , 13 bgr:JSINFO['plugin_callflow']['bgr'], 14 cols: 15 { 16 minlen:JSINFO['plugin_callflow']['cols_minlen'], 17 height:JSINFO['plugin_callflow']['cols_height'], 18 rectradius:JSINFO['plugin_callflow']['cols_rectradius'], 19 fill:JSINFO['plugin_callflow']['cols_fill'], 20 txtcolor:JSINFO['plugin_callflow']['cols_txtcolor'] 21 }, 22 note: 23 { 24 margin:JSINFO['plugin_callflow']['note_margin'], 25 rectradius:JSINFO['plugin_callflow']['note_rectradius'], 26 fill:JSINFO['plugin_callflow']['note_fill'], 27 align:JSINFO['plugin_callflow']['note_align'] 28 }, 29 tooltip: 30 { 31 txtsize:JSINFO['plugin_callflow']['tooltip_txtsize'], 32 txtcolor:JSINFO['plugin_callflow']['tooltip_txtcolor'], 33 border:JSINFO['plugin_callflow']['tooltip_border'], 34 bgr:JSINFO['plugin_callflow']['tooltip_background'] 35 } 36}; 37 38 39iconfolder = "./lib/plugins/callflow";//would start at docuwiki/ 40var paper,typenum=-1; 41var callActors = []; 42 43Raphael.el.tooltip = function (paper,x,y,text) { 44 // No tooltip if text is empty 45 if (!text) {return this;} 46 47 this.bb = paper.text(x,(y+style.txtsize/2)+10,text).attr({"font-size":style.tooltip.txtsize,"fill":style.tooltip.txtcolor, 'text-anchor': 'start'}); 48 var BB = this.bb.getBBox(); 49 // Translate of tooltip height/2 for multi line TT 50 // BB.y is automatically updated 51 this.bb.translate(0,BB.height/2); 52 53 this.tp = paper.rect(BB.x-4,BB.y-2,BB.width+8,BB.height+4).attr({"fill":style.tooltip.bgr,"stroke":style.tooltip.border}); 54 this.tp.ox = 0; 55 this.tp.oy = 0; 56 this.tp.hide(); 57 this.bb.hide(); 58 this.hover( 59 function(event) { 60 this.tp.show().toFront(); 61 this.bb.show().toFront(); 62 }, 63 function(event) { 64 this.tp.hide(); 65 this.bb.hide(); 66 } 67 ); 68 return this; 69}; 70 71//from here to END1 the code is from: 72//http://stackoverflow.com/questions/1229518/javascript-regex-replace-html-chars 73var replaceHtmlEntites = (function() { 74 var translate_re = /&(nbsp|amp|quot|lt|gt);/g; 75 var translate = { 76 "nbsp": " ", 77 "amp" : "&", 78 "quot": "\"", 79 "lt" : "<", 80 "gt" : ">" 81 }; 82 return function(s) { 83 return ( s.replace(translate_re, function(match, entity) { 84 return translate[entity]; 85 }) ); 86 } 87})(); 88//END1 89//from here to END2 the code is from: 90//http://stackoverflow.com/ 91if (!String.prototype.trim) String.prototype.trim = function(){return this.replace(/^\s+|\s+$/g, '');}; 92String.prototype.ltrim = function(){return this.replace(/^\s+/,'');}; 93String.prototype.rtrim = function(){return this.replace(/\s+$/,'');}; 94//END3 95 96getElementsByAttribute = function(a,b) 97{ 98 var output = [],j=0; 99 //from here to END4 the code is from: 100 //http://stackoverflow.com/questions/8396541/javascript-to-get-elements-by-its-attribute 101 var arr_elms = []; 102 arr_elms = document.getElementsByTagName("pre"); 103 var elms_len = arr_elms.length; 104 for (var i = 0; i < elms_len; i++) { 105 if(arr_elms[i].getAttribute(a) == b)//changed this line to suite my needs from: 106 //if(arr_elms[i].getAttribute("someAttribute") != null) 107 { 108 output[j++] = arr_elms[i]//changed this line to suite my needs from: 109 } 110 } 111 //END4 112 return output; 113}; 114 115String.prototype.ssplit = function(by) 116{ 117 posDelimiter = this.indexOf(by); 118 if(posDelimiter>0)return [this.substring(0, posDelimiter),this.substring(posDelimiter+by.length)]; 119 else return [this]; 120}; 121checkActors = function() 122{ 123 aParsedCommands[curCommand][0]+=" "; 124 aParsedCommands[curCommand][1]+=" "; 125 if(!callActors[aParsedCommands[curCommand][0]] && aParsedCommands[curCommand][0]!=null) 126 { 127 //fline[curActor] = aParsedCommands[curCommand][0]; 128 //callActors[fline[curActor]]=1; 129 callActors[aParsedCommands[curCommand][0]] = (style.margin+l_colsminlen)/2+l_colspacing*curActor; 130 curActor++; 131 } 132 if(!callActors[aParsedCommands[curCommand][1]]&&aParsedCommands[curCommand][1]!=null) 133 { 134 //fline[curActor] = aParsedCommands[curCommand][1]; 135 //callActors[fline[curActor]]=1; 136 callActors[aParsedCommands[curCommand][1]] = (style.margin+l_colsminlen)/2+l_colspacing*curActor; 137 curActor++; 138 } 139}; 140 141draw = function(el) 142{ 143 callCode = el.innerHTML; 144 callCode = replaceHtmlEntites(callCode.replace(/<br>/gi,"\n")); 145 callCode = callCode.replace(/\n[\n]+/gm,"\n"); 146 aCommands = callCode.split("\n"); 147 148 el.style.clear = "both"; 149 el.innerHTML = ""; 150 151 delete paper; 152 paper = Raphael(el,0,0); 153 154 callActors = []; 155 156 callActors = []; 157 aParsedCommands = []; 158 curCommand = 0; 159 curActor = 0; 160 title = ""; 161 bInNote = 0; 162 // Local copy of default values 163 l_colsminlen = style.cols.minlen; 164 l_colspacing = style.colspacing; 165//==============================================Parsing============================================== 166 for(i = 0; i < aCommands.length; i++) 167 { 168 if(aCommands[i].match(/^\/\//)) continue; //comment 169 170 if(bInNote)//if in note it would not execute commands 171 { 172 if(matched = aCommands[i].match(/^\)/))//note stop 173 { 174 aParsedCommands[curCommand] = []; 175 aParsedCommands[curCommand][3] = "note-stop"; 176 aParsedCommands[curCommand][4] = i; 177 curCommand++; 178 bInNote=0; 179 } 180 else continue; 181 } 182 //----------------------------------------------arrows 183 else if(matched = aCommands[i].match(/^(.+)<-?>([^:]+):?([^:]+):?(.*)/)) //double arrow 184 { 185 aParsedCommands[curCommand] = [matched[1], matched[2], matched[3], matched[4], "double-arrow", i]; 186 187 checkActors(); 188 curCommand++; 189 } 190 else if(matched = aCommands[i].match(/^(.+)->([^:]+):?([^:]+):?(.*)/)) //arrow 191 { 192 aParsedCommands[curCommand] = [matched[1], matched[2], matched[3], matched[4], "arrow", i]; 193 194 checkActors(); 195 curCommand++; 196 } 197 else if(matched = aCommands[i].match(/^(.+)<-([^:]+):?([^:]+):?(.*)/)) //back arrow 198 { 199 aParsedCommands[curCommand] = [matched[2], matched[1], matched[3], matched[4], "arrow", i]; 200 201 checkActors(); 202 curCommand++; 203 } 204 //----------------------------------------------parallel 205 else if(matched = aCommands[i].match(/parallel[ ]*\{/)) //start 206 { 207 aParsedCommands[curCommand] = [0, 0, 0, "parallel-start", i]; 208 curCommand++; 209 } 210 else if(matched = aCommands[i].match(/^}/)) //stop 211 { 212 aParsedCommands[curCommand] = [0, 0, 0, "parallel-stop", i]; 213 curCommand++; 214 } 215 //----------------------------------------------notes 216 else if(matched = aCommands[i].match(/note over ([^,]*),?([^(]*)\(/)) //note over start 217 { 218 actor2 = (matched[2] != '')?matched[2] : matched[1]; 219 aParsedCommands[curCommand] = [matched[1], actor2, 0, "note-over", i]; 220 checkActors(); 221 curCommand++; 222 bInNote=1; 223 } 224 else if(matched = aCommands[i].match(/note ?\(/)) //note start 225 { 226 aParsedCommands[curCommand] = [0, 0, 0, "note", i]; 227 curCommand++; 228 bInNote=1; 229 } 230 //----------------------------------------------break 231 else if(matched = aCommands[i].match(/break:?(.*)/)) //break 232 { 233 aParsedCommands[curCommand] = [0, 0, matched[1], "break", i]; 234 curCommand++; 235 } 236 //----------------------------------------------title 237 else if(matched = aCommands[i].match(/title:(.+)/)) //title 238 { 239 title = matched[1]; 240 } 241 //--- Directive to force order 242 /* 243 * B<-A still puts A in first position 244 * order allows to workaround that by having control on where items appear 245 * ex graph: 246 247 A 248 B<-A 249 A->C 250 251 * ex code: 252 253 order:B 254 order:A 255 order:C 256 A<>A:do something 257 A->B:ask something 258 A->C:send something 259 260 261 * or even the following only forces B in the first place, others use natural order: 262 order:B 263 A<>A:do something 264 A->B:ask something 265 A->C:send something 266 267 */ 268 else if(matched = aCommands[i].match(/^order:(.+)/)) //order 269 { 270 aParsedCommands[curCommand] = [matched[1], matched[1], "", "", "", i]; 271 272 checkActors(); 273 curCommand++; 274 } 275 /* ------ Allow to change colwidth for each callflow tag 276 * Must be in the first lines, ignored as after first actor is found 277 */ 278 else if(curActor == 0 && (matched = aCommands[i].match(/colwidth:(.+)/))) //colwidth 279 { 280 l_colsminlen = parseInt(matched[1]); 281 l_colspacing = l_colsminlen + 40; 282 } 283 } 284 if(bInNote) aParsedCommands[curCommand] = [0,0,"note-stop",aCommands.length]; 285 286//==============================================Drawing============================================== 287 for(i in callActors) 288 { 289 paper.rect( 290 callActors[i]-l_colsminlen/2,// x 291 20+(title?style.titlesize:0), // y 292 l_colsminlen, // width 293 style.cols.height, // height 294 style.cols.rectradius) // corner radius 295 .attr("fill",style.cols.fill); 296 297 paper.text(callActors[i], // center x 298 5+style.cols.height+(title?style.titlesize:0),// center y 299 i) // text 300 .attr({"font-size":style.txtsize, "fill":style.cols.txtcolor}); 301 } 302 y = style.cols.height*2+10;//starting y 303 if (title) y += style.titlesize; 304 isParallel = 0; 305 n = 0; 306 breaks = []; 307 for(i = 0; i < aParsedCommands.length; i++) 308 { 309 bShouldIncrement = 1; 310 cmd = aParsedCommands[i]; 311 if(cmd[4] == "arrow" || cmd[4] == "double-arrow") 312 { 313 // if start and dest are the same, skip the arrow 314 if (cmd[0] != cmd[1]) { 315 // Draw a blank line where the line will be to clearly cut columns marker 316 var adjStart = style.strokewidth/2; 317 var adjEnd = -style.strokewidth/2; 318 if (callActors[cmd[0]] > callActors[cmd[1]]) { 319 adjStart = -style.strokewidth/2; 320 adjEnd = style.strokewidth/2; 321 } 322 paper.path( 323 "M"+(callActors[cmd[0]]+adjStart)+","+y+ // from 324 "L"+(callActors[cmd[1]]+adjEnd)+","+y // to 325 ).attr({ 326 "stroke-width":style.strokewidth+5, 327 "stroke":"#ffffff", 328 "stroke-linecap":"butt" 329 }); 330 331 // Factorized arrow and double-arrow 332 var arrstart = "none"; 333 if (cmd[4] == "double-arrow") { 334 arrstart = "block-wide-long"; 335 } 336 paper.path( 337 "M"+callActors[cmd[0]]+","+y+ // from 338 "L"+callActors[cmd[1]]+","+y // to 339 ).attr({ 340 "arrow-start":arrstart, 341 "arrow-end":"block-wide-long", 342 "stroke-width":style.strokewidth, 343 "stroke":style.strokecolor 344 }); 345 } 346 var txt = paper.text( //text above arrow 347 (callActors[cmd[0]]+callActors[cmd[1]])/2, 348 y-style.txtsize/2-2, 349 cmd[2] 350 ).tooltip( //show text tooltip on hover 351 paper, 352 (callActors[cmd[0]]+callActors[cmd[1]])/2, 353 y-style.txtsize/2-2, 354 cmd[3].replace(/\\n/g,"\n") 355 ).attr({ 356 "font-size":style.txtsize, 357 "fill":style.txtcolor 358 }); 359 // Add visual hint there is a tooltip 360 if (cmd[3] && txt.node) { 361 jQuery(txt.node).css('text-decoration','underline').css('text-decoration-style','dashed'); 362 } 363 var BB = txt.getBBox(); 364 paper.rect(BB.x,BB.y,BB.width,BB.height).attr({"stroke":"none","fill":style.bgr}).toBack();//background box 365 } 366 else if(cmd[3]=="parallel-start") 367 { 368 isParallel=1; 369 } 370 else if(cmd[3]=="parallel-stop") 371 { 372 if (!isParallel) bShouldIncrement = 0; 373 isParallel = 0; 374 } 375 else if(cmd[3]=="note" || cmd[3]=="note-over") 376 { 377 midx=0; 378 ml=0; 379 if(cmd[3] == "note")//find the position of the note 380 { 381 for(curCommand = i;curCommand > -1;curCommand--) 382 { 383 if(typeof(aParsedCommands[curCommand][4]) == 'string' && 384 aParsedCommands[curCommand][4].match("arrow")) 385 { 386 midx = (callActors[aParsedCommands[curCommand][0]]+callActors[aParsedCommands[curCommand][1]])/2; 387 break; 388 } 389 } 390 } 391 else if(cmd[3]=="note-over") 392 { 393 midx = (callActors[cmd[0]]+callActors[cmd[1]])/2; 394 ml = l_colspacing; 395 } 396 else 397 { 398 midx = callActors[cmd[1]]; 399 } 400 if(!midx)continue; 401 lasti = i; 402 while(i < aParsedCommands.length && aParsedCommands[i][3] != "note-stop")//find stop 403 { 404 i++; 405 } 406 note = ""; 407 noteHeight=0; 408 for(curCommand = aParsedCommands[lasti][4]+1;curCommand<aParsedCommands[((i==aParsedCommands.length)?i-1:i)][4];curCommand++)//then text 409 { 410 note+=aCommands[curCommand]+"\n"; 411 noteHeight+=style.txtsize; 412 } 413 if(!note)continue;//if there is no text it won't draw anything 414 t = paper.text(midx,y+noteHeight/2,note).attr("font-size",style.txtsize);//text 415 BB = t.getBBox();//bounding box 416 417 if(!style.note.align||style.note.align=="left")t.attr({"x":BB.x,"text-anchor":"start"});//left align 418 419 else if(style.note.align=="right")t.attr({"x":BB.x+BB.width,"text-anchor":"end"});//right align 420 421 paper.rect( 422 BB.x-style.note.margin, 423 BB.y-style.note.margin, 424 BB.width+style.note.margin*2, 425 BB.height+style.note.margin*2, 426 style.note.rectradius) 427 .attr("fill",style.note.fill); 428 t.toFront();//gets the text in front of the rect 429 y += noteHeight+style.txtsize; 430 } 431 else if(cmd[3]=="break") 432 { 433 breaks[n] = []; 434 breaks[n][0] = y; 435 breaks[n][1] = (cmd[2]?cmd[2]:"");//save for later 436 n++; 437 } 438 else 439 { 440 bShouldIncrement = 0;//don't increment y 441 } 442 if(bShouldIncrement&&!isParallel)y+=style.linespacing; 443 } 444 y-=style.linespacing-10; 445 cactLen = 0; 446 for(i in callActors)//find max(callActors) and draw bottom rects and paths 447 { 448 cactLen++; 449 paper.path("M"+callActors[i]+","+y+ // from 450 "L"+callActors[i]+","+(20+(title?style.titlesize:0))) // to 451 .attr("stroke",style.strokecolor).toBack(); 452 paper.rect( 453 callActors[i]-l_colsminlen/2, 454 y, 455 l_colsminlen, 456 style.cols.height, 457 style.cols.rectradius) 458 .attr("fill", style.cols.fill); 459 paper.text( 460 callActors[i], 461 y+style.cols.height/2, 462 i) 463 .attr({"font-size":style.txtsize, "fill":style.cols.txtcolor}); 464 } 465 maxx = cactLen*l_colspacing-style.margin/2; 466 for(i in breaks)//draw breaks 467 { 468 paper.path( 469 "M"+(breaks[i][1]?0:style.margin/2)+","+breaks[i][0]+ // from 470 "L"+(breaks[i][1]?(maxx+style.margin):(maxx+style.margin-l_colsminlen/2))+","+breaks[i][0]) // to 471 .attr({"stroke-dasharray":"--", "stroke-width":style.strokewidth, "stroke":style.strokecolor}); 472 if(breaks[i][1]) 473 { 474 paper.text(10, 475 breaks[i][0]-style.txtsize/2, 476 breaks[i][1]) 477 .attr({"font-size":style.txtsize,"text-anchor":"start","fill":style.txtcolor}); 478 } 479 } 480 if(title)//draw title 481 { 482 paper.text((maxx+style.margin)/2, 483 10+style.titlesize/2, 484 title) 485 .attr({"font-size":style.titlesize, "fill":style.txtcolor}); 486 } 487 paper.rect(0, 0, maxx+style.margin, y+(style.cols.height+20)) 488 .attr({"fill":style.bgr, "stroke-dasharray":"."}).toBack();//dashed border 489 paper.setSize(maxx+style.margin,y+(style.cols.height+20))//resize paper so it fits 490 491 492}; 493window.onload = function() 494{ 495 callflows = getElementsByAttribute("callflow","true"); 496 for(var i = 0, max = callflows.length; i < max; i++) 497 { 498 draw(callflows[i]); 499 } 500}; 501