File: mod_wsf.spl

package info (click to toggle)
spl 1.0~pre2-1
  • links: PTS
  • area: main
  • in suites: etch, etch-m68k
  • size: 2,240 kB
  • ctags: 1,987
  • sloc: ansic: 15,272; yacc: 3,167; sh: 272; makefile: 186; xml: 156
file content (709 lines) | stat: -rw-r--r-- 19,422 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
/*
 *  SPL - The SPL Programming Language
 *  Copyright (C) 2004, 2005  Clifford Wolf <clifford@clifford.at>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  mod_wsf.spl: WebSPL Forms Library
 */

/**
 * WebSPL Forms Library
 *
 * This module provides a frame work for doing application development
 * with WebSPL. The basic idea is to split down the user interface into
 * so-called components. Each component provides a part of the DOM tree
 * which is displayed in the browser window. There is a seperate program
 * task for each component - so they can act as independent as the
 * programmer wants.
 *
 * This module provides [[WsfComponent]], the base object for WSF Components,
 * and [[WsfDocument]], the object which manages the interaction with the
 * Browser.
 *
 * Other Modules provide additional WSF Components. E.g.:
 *
 *	[[wsf_debug:WsfDebug]]	[[wsf_dialog:WsfDialog]]
 *	[[wsf_display:WsfDisplay]]	[[wsf_edit:WsfEdit]]
 *	[[wsf_edit_sql:WsfEditSql]]	[[wsf_graph:WsfGraph]]
 *	[[wsf_menu:WsfMenu]]		[[wsf_switch:WsfSwitch]]
 *
 * If the browser has support for it, [[WsfDocument]] does only send those
 * parts of the DOM tree to the browser which have actually changed and replace
 * them 'in place' in the current page using a little JavaScript hack.
 */

load "task";
load "cgi";
load "encode_xml";


/**
 * Checks the HTTP Agent string and auto-detects if the browser is able
 * to synamically update the DOM tree.
 *
 * It is possible to specify methods as arguments in the order of preference.
 * Then this methods are checked in that order. E.g.:
 *
 *	var method = wsf_get_domupdate_status("iframe", "xmlhttprequest");
 *
 * Will return "iframe", "xmlhttprequest" or "none".
 *
 * If no arguments are given the function eighter returns "iframe" or
 * "none". The "xmlhttprequest" method is left out in this case.
 *
 * Usually this is only used internally by the [[WsfDocument]] object to
 * initialize [[WsfDocument.domupdate]] and doesn't need to be called by the
 * user.
 */
function wsf_get_domupdate_status(@methods) {
	// Agent examples, Mozilla: "Mozilla/5.0 (X11; U; Linux i686; en-US;
	// rv:1.7) Gecko/20040917 Firefox/0.8"

	// Agent examples, KHTML:
	// "Mozilla/5.0 (compatible; Konqueror/3.3; Linux) (KHTML, like Gecko)"
	// "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.5.6 (KHTML, like Gecko) Safari/125.12"

	if (elementsof methods == 0)
		methods = [ "iframe" ];

	if (not declared cgi.agent)
		return "none";

	foreach[] m (methods)
	{
		if (m ~== "none")
			return "none";

		if (m ~== "iframe")
		{
			if (cgi.agent =~ /Mozilla/)
			{
				if (cgi.agent =~ /KHTML, like Gecko/)
					continue;

				if (cgi.agent =~ /compatible/)
					continue;

				return "iframe";
			}

			continue;
		}

		if (m ~== "xmlhttprequest")
		{
			if (cgi.agent =~ /Mozilla/)
				return "xmlhttprequest";

			continue;
		}
	}

	return "none";
}

/**
 * The base WsfComponent object. All other Wsf Components are derived from
 * this Object.
 */
object WsfComponent
{

	/**
	 * This counter is used by the constructor to create the [[.id]] for
	 * new instances of Wsf Components.
	 */
	static id_counter = 0;

	/**
	 * The id of this component instance. It is set automatically by the
	 * contructor and must be included as "id" attribute in the top element 
	 * of the HTML tree returned by [[.get_html()]].
	 *
	 * This also is the name of the task created for this component by the
	 * constructor.
	 */
	var id;
	
	/**
	 * The sid (session id) for the task running this component. It must
	 * be included as parameter 'sid' in all query strings. The methods
	 * [[.add_action()]], [[.add_href()]] and [[.add_javascript()]] should
	 * be used for creating links, etc.
	 */
	var sid;

	/**
	 * This variable must be set to 1 whenever it is neccessary to call
	 * [[.get_html()]] again and refresh the browser view of this component.
	 *
	 * It is also possible to set this to 1 if there have been any changes
	 * in the [[.children]] array.
	 */
	var dirty = 0;

	/**
	 * An array of child components. The keys may be freely choosen. The
	 * [[.get_html()]] method must run [[.get_html()]] for all children and
	 * include the return value in its own output.
	 */
	var children;

	/**
	 * The name of the main task running the [[WsfDocument]] instance which
	 * is responsible for this component. This is automatically set by
	 * [[WsfDocument]] when checking for set dirty flags and by the
	 * [[.child_set()]] method.
	 */
	var main_task;

	/**
	 * Whenever a form is generated by [[.get_html()]], this method must be
	 * used to create the "action" attribute in the <form> tag. A hidden
	 * input field for the "sid" parameter mus also be generated. E.g.:
	 *
	 *	<form id="$id" ${add_action(cgi.url)}>
	 *	<input type="hidden" name="sid" value="${sid}" />
	 *	...
	 *	</form>
	 */
	method add_action(url)
	{
		if ( WsfDocument.domupdate ~== "iframe")
			return 'target="wsftarget" action="${xml::url}"';

		if ( WsfDocument.domupdate ~== "xmlhttprequest")
			return 'onsubmit="return wsf_req_form(this, \'${xml::url}\');"';

		return 'action="${xml::url}"';
	}

	/**
	 * Whenever a HTML link is created by [[.get_html()]], this method
	 * must be used to create the "href" attribute in the <a> tag. E.g.:
	 *
	 *	<a ${add_href("${cgi.url}?sid=${sid}&foo=bar")}>Foobar</a>
	 */
	method add_href(url)
	{
		if ( WsfDocument.domupdate ~== "iframe")
			return 'target="wsftarget" href="${xml::url}"';

		if ( WsfDocument.domupdate ~== "xmlhttprequest")
			return 'href="javascript:wsf_req_link(\'${xml::url}\')"';

		return 'href="${xml::url}"';
	}

	/**
	 * Whenever JavaScript is used in the code created by [[.get_html()]],
	 * this method must be used to create the statement which sets
	 * location.href to the new value. E.g.:
	 *
	 *	onClick="${ add_javascript("${cgi.url}?sid=${sid}&foo=bar") }"
	 *
	 * the URL will be quoted with single quotes in the generated code. So
	 * there is no problem with embedding it using "onFoobar" JavaScript
	 * event handlers. No additional quoting is done. So something like that
	 * for creating query string parameters with JavaScript is possible too:
	 *
	 *	onClick="${ add_javascript("${cgi.url}?sid=${sid}&foo=' + (3+5) + '") }"
	 */
	method add_javascript(url)
	{
		if ( WsfDocument.domupdate ~== "iframe")
			return "wsftarget.location.href='$url'";

		if ( WsfDocument.domupdate ~== "xmlhttprequest")
			return "wsf_req_link('$url')";

		return "location.href='$url'";
	}

	/**
	 * A simple method for calling [[.get_html_cached()]] on all children
	 * and concatenating the results.
	 */
	method get_html_children()
	{
		var html;
		foreach i (children) {
			html = html ~ children.[i].get_html_cached();
		}
		return html;
	}

	var __WsfComponent_get_html_cached_data;

	/**
	 * This method returns the HTML code for this component. If the
	 * [[.dirty]] flag is set, [[.get_html()]] is called to generate
	 * the HTML code. Otherwise a cached version of the HTML code is
	 * returned.
	 */
	method get_html_cached()
	{
		if (not dirty and defined __WsfComponent_get_html_cached_data)
			return __WsfComponent_get_html_cached_data;
		dirty = 0;
		return __WsfComponent_get_html_cached_data = get_html();
	}

	/**
	 * The method for creating the HTML code. The top HTML element must
	 * have the attribute "id" set to the value of the [[.id]] member
	 * variable. The default behavior is to simply return:
	 *
	 *	'<div id="$id">\n' ~ get_html_children() ~ '</div>\n'
	 */
	method get_html()
	{
		return '<div id="$id">\n' ~ get_html_children() ~ '</div>\n';
	}

	/**
	 * The main function for the task running this component. It is first
	 * called by the constructor and is running until it calls
	 * [[task:task_co_return()]].
	 *
	 * After that, [[.get_html()]] is called by the [[WsfDocument]] object
	 * to create the HTML representation.
	 *
	 * The [[task:task_co_return()]] function returns when the user has done
	 * something in his browser window which effects this component; i.e.
	 * has clicked a link or submitted a form generated by [[.get_html()]].
	 *
	 * Then this function can react to the event and call [[task:task_co_return()]]
	 * again when it has finished processing this event. Don't forget to
	 * set [[.dirty]] to 1 if [[.get_html()]] needs to be called again.
	 *
	 * If this method does not call [[task:task_co_return()]], the task will hang
	 * in an endless loop. So don't forget to do that!
	 */
	method main()
	{
		task_co_return();
	}

	/**
	 * Create (substitute) a child component.
	 * The 1st parameter is the key in the [[.children]] array, the 2nd
	 * parameter the new component object.
	 */
	method child_set(name, obj)
	{
		if (declared children[name])
			children[name].destroy();
		task_co_setdefault(obj.id, obj.main_task = main_task);
		children[name] = obj;
		dirty = 1;
	}

	/**
	 * Remove a child component.
	 * The parameter is the key in the [[.children]] array.
	 */
	method child_remove(name)
	{
		if (declared children[name]) {
			children[name].destroy();
			delete children[name];
			dirty = 1;
		}
	}

	/**
	 * The destructor. It needs to be called when the object isn't needed
	 * anymore to kill the task assigned to that object. It also calls the
	 * destroy method of all child components.
	 *
	 * It is save to call that from the [[.main()]] method. If you do so,
	 * killing the task is postponed until [[.main()]] calls [[task:task_co_return()]]
	 * the next time. [[.main()]] will then never return from this function
	 * again.
	 */
	method destroy()
	{
		foreach i (children) {
			children.[i].destroy();
			delete children.[i];
		}

		task_late_kill(id);
	}

	/**
	 * The constructor.
	 */
	method init()
	{
		id = "wsfc_" ~ (++id_counter);
		sid = cgi.sid_vm ~ ":" ~ id;

		task_create(id, "while (1) { main(); }", this);
		task_public(id);
		task_co_call(id);
		return this;
	}
}

/**
 * The WsfDocument object. There must be one WsfDocument object for
 * every browser window which is under control of WSF. Usually it
 * is instanciated and assigned to a variable called 'page', then
 * initialized and finally the [[.main()]] method is called. E.g.:
 *
 *	var page = new WsfDocument();
 *	page.title = "My Application Title";
 *	page.root = new MyFunnyRootWsfComponent();
 *	page.main();
 *
 * The [[.main()]] method never returns.
 */
object WsfDocument
{
	/**
	 * This variable is automatically set when the module is loaded.
	 * It contains the return code of [[wsf_get_domupdate_status()]].
	 *
	 * It is possible to change that variable before instanciating
	 * WsfDocument the first time. After that, the variable shouldn't
	 * be touched anymore.
	 *
	 * Additionally it is possible to change that variable by passing
	 * a cgi query string parameter on program startup:
	 *
	 *	wsf_domupdate={ iframe | xmlhttprequest | none }
	 *
	 * If the domupdate mechanism is running in "iframe" mode,
	 * the iframe can be set visible by passing the query string
	 * parameter "wsf_showiframe".
	 *
	 * The "xmlhttprequest" method is probably the best implementation,
	 * but right now it is not possible to do file uploads using this
	 * method.
	 */
	static domupdate = wsf_get_domupdate_status();

	/**
	 * If [[.domupdate]] is set to "iframe" and this variable is set to 1,
	 * the iframe used for the domupdate mechanism will be visible. This
	 * is only of interest for debugging purposes.
	 */
	static showiframe = 0;

	if ( declared cgi.param.wsf_domupdate )
		domupdate = cgi.param.wsf_domupdate;

	if ( declared cgi.param.wsf_showiframe )
		showiframe = cgi.param.wsf_showiframe;

	/**
	 * This is the root component for this WsfDocument. It must be set
	 * to an instance of [[WsfComponent]] (or any derived object) before
	 * the [[.main()]] method is called.
	 */
	var root;

	/**
	 * The content for the <title> tag generated by this object. This
	 * can not be changed later, if [[.domupdate]] is set to "iframe".
	 * So it must be set before calling [[.main()]], or not at all.
	 */
	var title = "";

	/**
	 * HTML code to be included between <head> and </head>.
	 * Must be set before calling [[.main()]], or not at all.
	 */
	var html_head = "";

	/**
	 * HTML attributes to be set in the <body> tag.
	 * Must be set before calling [[.main()]], or not at all.
	 */
	var body_attr = "";

	/**
	 * A hash with arrays of callback functions. Use [[.callback_add]]
	 * and [[.callback_del]] to maintain the entries.
	 */
	var callbacks;

	/**
	 * Add a callback function to the [[.callbacks]] data structure.
	 *
	 * The following callback types are called by this object:
	 *
	 *	pre_update	called before the html (xml) output is created.
	 *	post_update	called after the html (xml) output is created.
	 */
	method callback_add(type, func)
	{
		push callbacks[type], func;
	}

	/**
	 * Add a callback function from the [[.callbacks]] data structure.
	 */
	method callback_del(type, func)
	{
		foreach i (callbacks[type])
			if (callbacks[type][i] ^== func)
				delete callbacks[type][i];
	}

	/**
	 * Call all callbacks of a type. The callback is passed the type as
	 * first parameter and this [[WfsDocument]] object as 2nd parameter.
	 * The named parameters are also passed thru to the callback functions.
	 */
	method callback_call(type, %options)
	{
		foreach[] c (callbacks[type])
			c(type, this, %options);
	}

	/**
	 * This method implements the main loop of a [[WsfDocument]].
	 * It must be called after setting up the variables described
	 * below.
	 *
	 * It handles the creation of HTML pages which are then passed
	 * to the browser. Whenever the [[WsfComponent.main()]] method
	 * calls task_co_return() after processing a user event, control
	 * is passed back to this method so it can update the browser window.
	 *
	 * This function does never return.
	 */
	method main()
	{
		function do_updates2(comp)
		{
			if (comp.main_task ~!= task_getname()) {
				comp.main_task = task_getname();
				task_co_setdefault(comp.id, task_getname());
			}
			foreach i (comp.children)
				do_updates2(comp.children.[i]);
		}

		function do_updates(comp, parents)
		{
			var html;
			if (comp.dirty or not defined comp.__WsfComponent_get_html_cached_data) {
				do_updates2(comp);
				html ~= comp.get_html_cached();
				if ( domupdate ~== "iframe" ) {
					html ~= <<EOT
<script><!--
copy_to_parent("${comp.id}");
//--></script>

EOT;
				} else
				if ( domupdate ~== "xmlhttprequest" ) {
					/* nothing to do */
				} else {
					foreach p (parents)
						parents[p].dirty = 1;
				}
			} else {
				push parents, comp;
				foreach i (comp.children)
					html ~= do_updates(comp.children.[i], parents);
				pop parents;
			}
			return html;
		}

		while (1) {
// Not stating we are XHTML - this will give us
// much more freedom with bad html code...
//
// <?xml version="1.0" encoding="iso-8859-1"?>
// <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
//   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
// <html xmlns="http://www.w3.org/1999/xhtml">
			write(<<EOT
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>${title}</title>
${html_head}
</head>
<body ${body_attr}>
EOT);
			if ( domupdate ~== "iframe" ) {
				if ( showiframe )
					write(<<EOT
<iframe style="width:100%" name="wsftarget" id="wsfiframe">
</iframe>
EOT);
				else
					write(<<EOT
<iframe style="display:none" name="wsftarget" id="wsfiframe">
</iframe>
EOT);
			}

			if ( domupdate ~== "xmlhttprequest" ) {
				write(<<EOT

<div style="display:none"><div id="wsf_elements"> WSF DOM BUFFER </div></div>

<script><!--

function wsf_req_and_copy(url, method, postdata) 
{
	var request;

	function copy_element(element)
	{
		var name = element.id;
		element.setAttribute("id", "new_" + name);
		var new_dom = element.cloneNode(true);
		var old_dom = document.getElementById(name);
		old_dom.parentNode.replaceChild(new_dom, old_dom);
		new_dom.setAttribute("id", name);
	}

	function processReqChange()
	{
		// only if req shows "complete"
		if (request.readyState == 4) {
			// only if "OK"
			if (request.status == 200) {
				document.getElementById('wsf_elements').innerHTML = request.responseText;
				var i, elements = document.getElementById('wsf_elements').childNodes;
				for (i=0; i < elements.length; i++)
					if ( elements[i].nodeType == 1 && elements[i].id )
						copy_element(elements[i]);
			} else {
				alert("There was a problem retrieving the XML data:" +
						request.statusText);
			}
		}
	}

	// branch for native XMLHttpRequest object
	if (window.XMLHttpRequest) {
		request = new XMLHttpRequest();
		request.onreadystatechange = processReqChange;
		request.open(method, url, true);
		request.send(postdata);
	} else
	// branch for IE/Windows ActiveX version
	if (window.ActiveXObject) {
		request = new ActiveXObject("Microsoft.XMLHTTP");
		if (request) {
			request.onreadystatechange = processReqChange;
			request.open(method, url, true);
			request.send(postdata);
		}
	}
}

function wsf_req_form(form, url)
{
	var i, postdata = '', delim = '';
	for (i=0; i<form.elements.length; i++) {
		var el = form.elements[i], useit = 1;
		if (el.name == '') useit = 0;
		if (el.type == 'submit') useit = 0;
		if (useit) {
			postdata += delim +
				encodeURI(el.name) + '=' +
				encodeURI(el.value);
			delim = '&';
		}
	}
	wsf_req_and_copy(url, "POST", postdata);
	return false;
}

function wsf_req_link(url)
{
	wsf_req_and_copy(url, "GET", null);
}

//--></script>
EOT);
			}

			callback_call("pre_update");

			do_updates(root);
			write(root.get_html_cached());

			callback_call("post_update");

			write("</body></html>\n");

			while ( domupdate ~== "iframe" )
			{
				task_pause();

// Not stating we are XHTML - this will give us
// much more freedom with bad html code...
//
// <?xml version="1.0" encoding="iso-8859-1"?>
// <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
//   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
// <html xmlns="http://www.w3.org/1999/xhtml">
				write(<<EOT
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>WSF Update</title>
</head>
<body>

<hr />
<script><!--

function copy_to_parent(name)
{
	var new_dom = parent.document.importNode(document.getElementById(name), true);
	var old_dom = parent.document.getElementById(name);
	old_dom.parentNode.replaceChild(new_dom, old_dom);
}

//--></script>

EOT);
				callback_call("pre_update");
				write(do_updates(root));
				callback_call("post_update");
				write("</body></html>\n");
			}

			while ( domupdate ~== "xmlhttprequest" )
			{
				task_pause();

				cgi.content_type = "text/plain";

				callback_call("pre_update");
				write(do_updates(root));
				callback_call("post_update");
			}

			task_pause();
		}
	}
}