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