1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
4 <title>monolith documentation index</title>
5 <style type="text/css"><!--
10 background-color: #eeeeff;
22 padding: 6px 6px 6px 6px;
23 margin: 3px 3px 3px 3px;
28 <body bgcolor="#ffffff">
29 <h1>monolith documentation index</h1>
31 <h2>What is monolith?</h2>
34 <code>monolith</code> is an application framework which
35 generates web-based applications. It differs from existing web
36 tools because it doesn't work in terms of "pages" and "CGI
37 scripts". Instead it allows you to build applications using
38 reusable widgets (buttons, labels, etc.) which you arrange into
39 windows. This makes it much more like building a traditional
40 application in Windows/MFC, Java/JFC, Tcl/Tk, Motif, etc.
44 You can also use the basic widgets to build reusable
45 "super-widgets" which you can then embed in other applications,
46 give away or sell. An example might be a discussion system
47 "widget" which can be placed anywhere in another application.
51 Of course, there are limitations in the web and browsers which
52 means that you can't do everything you might do in a normal
53 application. But you can do most things.
57 <code>monolith</code> programs and widgets are written in C or
58 C++ (actually I've never used C++ with <code>monolith</code>,
59 but you are welcome to try).
62 <h3>CGI vs. rws's shared object scripts vs. monolith
66 (This section is from the <code>rws</code> documentation page).
70 Shared object scripts are the direct analogy to CGI scripts,
71 the only difference being that CGI scripts are usually written
72 in very high level languages like Perl and PHP, and shared
73 object scripts are loaded into the server process for efficiency.
74 (Perl CGI scripts can also be loaded into the Apache
75 server process using <code>mod_perl</code>, and this is done
76 for similar reasons of efficiency).
80 <code>monolith</code> programs are entire applications, the sort of
81 thing which normally would be written using dozens of
82 cooperating CGI scripts. In the case of <code>monolith</code>, however,
83 the entire application compiles down to a single <code>.so</code>
84 file which happens to be (you guessed it) a shared object script.
88 Imagine that you are going to write yet another web-based email
89 client. For some reason you want to write this in C (please
90 don't try this at home: I wrote one in Perl at my last job and
91 that was hard enough). Here are three possible approaches
92 using C and <code>rws</code>:
98 Write forty or so shared object scripts. Each displays
99 a single frame of the application, one might generate
100 the frameset, a couple of dozen to implement specific
101 operations like emptying trash or moving a message between
105 This is very much the normal way of writing CGI-based
108 <li> Write a <code>monolith</code> application. This will probably be
109 in lots of C files, but will compile down and be linked
110 into a single <code>.so</code> file (eg. <code>email.so</code>)
111 which is dropped into the <code>so-bin</code> directory.
114 Write a <code>monolith</code> email super-widget. This is going
115 to exist in a shared library called
116 <code>/usr/lib/libmyemail.so</code>
117 with a corresponding header file defining the interface
118 called <code>myemail.h</code>.
121 Write a tiny <code>monolith</code> application which just instantiates
122 a window and an email widget, and embeds the email widget
123 in the window. This will compile into <code>email.so</code>
124 (it'll be very tiny) which is dropped into <code>so-bin</code>.
127 The advantage of this final approach is that you can
128 reuse the email widget in other places, or indeed sell
129 it to other <code>monolith</code> users.
134 So <code>monolith</code> is good when you want to build applications
135 from widgets as you would if you were building a
136 Java/Swing, Windows MFC, gtk, Tcl/Tk graphical application.
137 It's also good if code re-use is important to you.
138 Shared object scripts are good when you are familiar with
139 CGI-based techniques to build websites.
143 Of course, the same <code>rws</code> server can serve
144 shared object scripts, multiple <code>monolith</code> applications,
145 flat files, and directory listings, all at the same time.
148 <h2>Compiling and installing monolith programs</h2>
151 <code>monolith</code> programs are C programs which compile into
152 <code>.so</code> files, called shared object scripts.
153 The <code>rws</code> webserver runs these directly.
157 To compile, probably the simplest thing to do is write a
162 gcc -Wall -Werror -c prog.c -o prog.o
163 gcc -shared -Wl,-soname,prog.so prog.o \
164 -lmonolithcore -lpthrlib -lc2 -lm -o prog.so
168 The last step generates the actual binary. Copy this into
170 <code>/so-bin</code> directory, and make sure it is mode 0755
171 (<code>-rwxr-xr-x</code>).
175 To create an <code>/so-bin</code> directory, add this to your
176 <code>rws</code> hosts file:
181 path: /path/to/your/so-bin
187 Now test it out by going to
188 <code>http://your.webserver/so-bin/prog.so</code>
192 If it doesn't work, here is a checklist before you email me:
196 <li> Make sure you have put the above alias section into
197 the correct host file.
198 <li> <code>exec so</code> option is set?
199 <li> Restarted <code>rwsd</code>?
200 <li> Directory is world readable, executable (mode 0755)?
201 <li> Program is world readable, executable (mode 0755)?
202 <li> Any unresolved symbols (<code>ldd -r script.so</code>), apart
203 from the <code>rws_request_*</code> symbols which will be resolved
204 when the library is loaded into <code>rws</code>?
205 <li> Check the contents of your error_log file to see
206 if any error messages were reported.
210 I have quite successfully used <code>gdb</code> on a running
211 server to debug and diagnose problems in <code>monolith</code> programs.
212 However note that by default <code>gdb</code> may have trouble
213 loading the symbol table for the <code>monolithcore</code>
214 library and your program. Use the <code>sharedlibrary monolith;
215 sharedlibrary script.so</code> command to load symbols instead.
221 This tutorial begins with the basics of <code>monolith</code>
222 application design, and then goes through some of the examples
223 which you will find in the <code>examples/</code> directory
224 in the source distribution.
227 <h3>A simple "hello, world" program</h3>
230 The simple "hello, world" program is useful because it
231 lets us (a) make sure our development environment is <em>really</em>
232 working, and (b) sort out the boilerplate code that every
233 <code>monolith</code> application needs. You can find
234 the full program in the source distribution as
235 <code>doc/hello.c</code>.
239 Start by including some necessary headers:
243 #include <pool.h>
245 #include <monolith.h>
246 #include <ml_window.h>
247 #include <ml_text_label.h>
251 Then there is some standard boilerplate code that needs to
252 go in (once only) into every <code>monolith</code> application.
253 You don't need to worry about what it does: it's just
254 glue between <code>rws</code>'s shared object scripts and
255 the <code>monolith</code> core code:
259 /*----- The following standard boilerplate code must appear -----*/
261 /* Main entry point to the app. */
262 static void app_main (ml_session);
265 handle_request (rws_request rq)
267 return ml_entry_point (rq, app_main);
270 /*----- End of standard boilerplate code -----*/
274 Then the "hello, world" program itself:
279 app_main (ml_session session)
281 pool pool = ml_session_pool (session);
285 /* Create the top-level window. */
286 w = new_ml_window (session, pool);
288 /* Create the text widget. */
289 text = new_ml_text_label (pool, "Hello, World!");
291 /* Pack the text widget into the window. */
292 ml_window_pack (w, text);
297 <code>app_main</code> is the entry point into the
298 application. It has one parameter, the opaque
299 <code>ml_session</code> object, which is explained below. Every
300 <code>monolith</code> program needs a top-level window, so we
301 create this using <code>new_ml_window(3)</code>.
302 Now we need to put something into the window, otherwise
303 it'll appear completely blank. In this case we're
304 going to put a text label widget inside this window
305 containing the familiar greeting. We have to tell the
306 window that it contains the text widget (otherwise
307 they'd be just two completely separate variables),
308 so we call <code>ml_window_pack(3)</code> to place the
309 text label inside the window.
313 Now you should compile and run this example
314 (in <code>doc/hello.c</code>) using the instructions
315 above and verify that it runs.
318 <h3>Design: sessions, session pools, shared data, session data</h3>
321 The <code>app_main</code> function has just one argument
322 passed to it, the opaque <code>ml_session</code> object.
323 What is this used for?
327 Think of a normal GUI application written in C or C++ (or
328 another language if you like). A user starts up the
329 application. The application interacts exclusively
330 with that one user. Global variables in the application
331 belong entirely to that application and that user.
332 The application continues to be used until the user
333 closes it down. We will define this process of the user
334 starting up the application, using the application and
335 then finally closing it down, as a <dfn>session</dfn>.
339 Web-based applications are slightly different in that
340 the same code running in the same Unix process can be
341 used by many users at the same time. For the benefit
342 of programmers, <code>monolith</code> automatically
343 keeps all of these users' sessions separate for you,
344 maintaining a different opaque <code>ml_session</code>
345 object for each session.
349 One implication of this is that <code>app_main</code> is
350 called with a different <code>ml_session</code> object
351 each time, because a session only starts once. (The same
352 <i>user</i> might come back later and use the same
353 <i>application</i>, but that would be a <em>different session</em>).
357 So a session is different from a user. A session is also
358 different from an HTTP request. Typically during a session
359 a user might fill in a few forms, press some buttons,
360 browse through tables and so on. Each of these operations
361 probably involves an HTTP request. So in <code>monolith</code>
362 HTTP requests are very short-lived (of the order of 1ms - 1s),
363 but sessions can be quite long (hours of activity).
367 <code>monolith</code> allocates a separate session pool
368 for each session, and most applications are expected
369 to allocate most of their data on this session pool.
370 Of course when a user finishes their session, the session
371 pool will be deleted. This normally results in all of the
372 widgets and stuff created during the session being nuked,
373 and this is generally a good thing.
377 C static variables (ie. globals and variables in functions
378 declared using <code>static</code>) are shared between
379 all sessions. This can be useful under some circumstances,
380 but if what you really want is <em>persistent</em> data,
381 then you are far better off using a database of some sort
382 at the back-end. This is because static variables will
383 obviously be trashed if the <code>monolith</code> application
384 is unloaded or the webserver crashes.
388 <code>c2lib</code>'s <code>global_pool</code> is also
389 shared between all sessions (but don't use it directly:
390 create a subpool in your <code>_init</code> function
391 and delete the subpool in your <code>_fini</code> function).
395 If C static variables are shared, how do we keep a separate
396 set of variables for each session? Generally the easiest
397 way to do this is to allocate a per-session structure
398 once in <code>app_main</code> and pass it around. This
399 is called the <dfn>session data structure</dfn>. We'll
400 see this being done in example 01 below.
403 <h3>Design (for CGI programmers): what are widgets?</h3>
406 A short aside: what are widgets? If you have any experience
407 of using a traditional GUI library or GUI-builder tool, like
408 Tcl/Tk, gtk, Windows MFC, Motif, Java Swing, etc., then you'll
409 probably already know what a widget (or "control" in MS-speak) is,
410 so skip to the next section. This section is for people
411 coming from a non-graphical or purely CGI background.
415 In traditional GUI environments, applications are not
416 built up using pages, forms, CGI scripts and so on,
417 but are instead built up using small reusable objects
418 called widgets. A typical basic widget might be a
419 push button, a label, or an image. There are also
420 compound widgets which store other widgets inside
421 themselves. In <code>monolith</code> for example,
422 a table layout can be used to arrange other widgets
423 into a table. A table layout is itself a widget, and
424 you can use a table layout (populated with widgets inside)
425 any place you would use a basic widget. This is important
426 because complex layouts are often constructed from several
427 layers of compound and basic widgets (buttons and labels inside
428 table layouts inside other table layouts inside windows, etc.)
432 Here is an example form constructed using nested widgets:
440 <i> Table layout </i>
441 <table border="1" width="100%">
443 <td colspan="2"> <i>Label</i> </td>
446 <td> <i>Label</i> </td>
447 <td> <i>Form input</i> </td>
450 <td> <i>Label</i> </td>
451 <td> <i>Form input</i> </td>
454 <td> <i>Empty</i> </td>
455 <td> <i>Form submit</i> </td>
462 <h3>Example 01: Label and button</h3>
465 The first example, <code>examples/01_label_and_button.c</code>,
466 is very simple. It displays a label and a button. Clicking
467 on the button increments the number on the label. Try this
468 now. Also try running it from two different browsers and
469 machines. Notice how the label starts counting up from 0
470 independently on each machine (demonstrating that each session
471 is really independent because this demo uses a session data
476 As before we begin by including some necessary headers and
477 the same boilerplate code as in our "hello, world" example
478 above. I won't repeat that here, because it's identical.
479 Then we declare our session data structure and a few private
486 ml_label lb; /* Label. */
487 int count; /* Count of number of button presses. */
490 static void increment (ml_session, void *);
491 static void update_label (pool pool, ml_label lb, int count);
495 We keep (a pointer to) the label widget and the count of
496 button presses in our session data structure. In theory
497 we could keep more here, but in fact it's not necessary. These
498 are the only two variables that we need to make "global"
499 to the session, because these are the only two variables
500 which our callback function will need when it comes to
501 update the label. Our callback function is going to be
502 called when the user clicks on the button, as we'll see
507 The main entry point to our application is called
508 <code>app_main</code>. It's called at the beginning
514 app_main (ml_session session)
516 pool pool = ml_session_pool (session);
525 Notice that we use <code>ml_session_pool(3)</code> to get the
526 current session pool, where we are going to make all of our
531 <code>data</code> will point to our session data structure, but
532 we have to allocate and initialise it first:
536 /* Create the private, per-session data area and save it in the
539 data = pmalloc (pool, sizeof *data);
544 Next we create the window, label and button. We're going to
545 pack the label and button into a flow layout which is the
546 simplest sort of compound widget: it just displays the widgets
547 inside itself one after another.
551 /* Create the top-level window. */
552 w = new_ml_window (session, pool);
554 /* Create the flow layout widget which will be packed into the window. */
555 lay = new_ml_flow_layout (pool);
557 /* Create the label and button. */
558 data->lb = lb = new_ml_label (pool, 0);
559 update_label (pool, data->lb, 0);
561 b = new_ml_button (pool, "Push me!");
562 ml_button_set_callback (b, increment, session, data);
564 /* Pack the label and button into the flow layout widget. */
565 ml_flow_layout_pack (lay, lb);
566 ml_flow_layout_pack (lay, b);
568 /* Pack the flow layout widget into the window. */
569 ml_window_pack (w, lay);
574 Notice the call to <code>ml_button_set_callback</code>. When the
575 button is pressed, the <code>increment</code> function will
576 be called like this: <code>increment (session, data)</code>.
577 (Recall that <code>data</code> is our session data pointer).
581 This is the definition of <code>increment</code>:
586 increment (ml_session session, void *vp)
588 struct data *data = (struct data *) vp;
590 update_label (ml_session_pool (session), data->lb, ++data->count);
595 It increments <code>data->count</code> and calls
596 <code>update_label</code> which is the function which
597 actually changes the message on the label.
601 <code>update_label</code> is defined as:
606 update_label (pool pool, ml_label lb, int count)
608 ml_label_set_text (lb,
610 "Button pressed: <b>%d</b><br>",
616 (If you are unfamiliar with the function <code>psprintf</code>
617 then you should read the <code>c2lib</code> documentation).
621 That's the end of our first significant <code>monolith</code>
622 application! At around 31 lines of code, it's considerably smaller
623 than the equivalent CGI script.
626 <h3>Design: Applications and widgets</h3>
629 This section talks about one of the more fundamental
630 design considerations you need to think about when first
631 designing a new <code>monolith</code> application.
632 Namely when to build application code and when to
633 build reusable widgets.
637 The label and button example above is a complete
638 <code>monolith</code> application. What happens
639 however if we needed to embed this label and button
640 combo in another program (not a very likely scenario,
641 I'll admit, but let's imagine that you've developed
642 a calendar program or something else quite substantial).
646 The answer is to turn your application into a reusable
647 widget. Widgets are often composed of many other
648 more fundamental widgets, and in this case it is possible
649 to turn the label and button combo into a full-blown
650 widget. This widget could be used just like any of the
651 core widgets which <code>monolith</code> provides.
655 Turning an application into a widget isn't too hard, depending
656 on the complexity of the application itself, but it's better
657 when designing the application if you first of all work out
658 whether the application as a whole -- or parts of the
659 application -- can be designed as reusable widgets.
663 If you were designing a calendar program in <code>monolith</code>
664 then you might decompose the design like this:
670 <th> Specification </th>
671 <th> Can be used as a widget? </th>
674 <td> Whole application </td>
675 <td> Calendar: A tool for storing and tracking daily
676 events, providing appointments, backed up in a database </td>
677 <td> Yes. By writing the whole calendar as a reusable
678 widget, we can include the calendar widget in an Outlook-style
679 personal information manager (PIM). </td>
682 <td> New event form </td>
683 <td> Form which appears when the user adds a new event. </td>
684 <td> No. Quite specific to this calendar, so reuse doesn't
685 make much sense. </td>
688 <td> Date selector </td>
689 <td> Form input which allows the user to choose a date
690 and automatically verifies it. </td>
691 <td> Yes. This is applicable on many different forms in
692 other applications. </td>
696 <h3>Example 03: Toy calculators</h3>
699 Example 03 will demonstrate how to write a simple reusable
700 widget. In fact the reusable widget that we're going to write
701 is the same as the application in example 02 (not covered
702 here, but supplied with the source in the <code>examples/</code>
703 directory). So if you want you can study the process of
704 converting a whole application into a reusable widget.
708 The source code for example 03 is divided into three
713 <li> <code>03_many_toy_calculators.c</code> <br>
714 The application. This just instantiates four widgets
716 <li> <code>toy_calculator.h</code> <br>
717 The widget's header file. Every widget should have a
718 header file defining the external interface which
719 callers are allowed to use, and defining also the
720 opaque widget type (in this case, <code>toy_calculator</code>).
721 <li> <code>toy_calculator.c</code> <br>
722 The actual code which implements the widget.
726 It's helpful at this point if you run the example. You
727 should see four calculators. Try doing some sums. Notice
728 how each calculator acts completely independently of
733 Let's start with the application code. This is very simple. It
734 just creates four toy_calculator objects and populates a
735 table layout widget with them:
740 app_main (ml_session session)
742 pool pool = ml_session_pool (session);
745 toy_calculator calcs[4];
747 /* Create the top-level window. */
748 w = new_ml_window (session, pool);
750 /* Create a table layout widget to arrange the calculators. */
751 tbl = new_ml_table_layout (pool, 2, 2);
753 /* Create the calculators and pack them into the table layout. */
754 calcs[0] = new_toy_calculator (pool, session);
755 ml_table_layout_pack (tbl, calcs[0], 0, 0);
756 calcs[1] = new_toy_calculator (pool, session);
757 ml_table_layout_pack (tbl, calcs[1], 0, 1);
758 calcs[2] = new_toy_calculator (pool, session);
759 ml_table_layout_pack (tbl, calcs[2], 1, 0);
760 calcs[3] = new_toy_calculator (pool, session);
761 ml_table_layout_pack (tbl, calcs[3], 1, 1);
763 /* Pack the table into the window. */
764 ml_window_pack (w, tbl);
769 The header file <code>toy_calculator.h</code> defines the
770 interface. It's very simple, because there's only one
771 thing you can do with a <code>toy_calculator</code> at
772 the moment, and that's create one by using the
773 <code>new_toy_calculator</code> function. Notice the
774 <a href="http://www.annexia.org/freeware/c2lib/">cdoc</a>
775 documentation in the comments.
779 #ifndef TOY_CALCULATOR_H
780 #define TOY_CALCULATOR_H
782 #include <pool.h>
783 #include <monolith.h>
785 struct toy_calculator;
786 typedef struct toy_calculator *toy_calculator;
788 /* Function: new_toy_calculator - toy calculator widget
790 * @code{new_toy_calculator} creates a new reusable toy calculator
793 extern toy_calculator new_toy_calculator (pool, ml_session);
795 #endif /* TOY_CALCULATOR_H */
799 The actual implementation of the <code>toy_calculator</code>
800 widget is complicated, so we'll take it step by step here.
801 However remember that if all you want to do is to <em>use</em>
802 a <code>toy_calculator</code>, then you needn't worry about
803 the implementation at all. You only need to look at the
804 header file and use it just like you would any other widget.
808 <code>toy_calculator.c</code> begins by including many
813 #include <string.h>
815 #include <pool.h>
816 #include <pstring.h>
817 #include <pthr_cgi.h>
819 #include <monolith.h>
820 #include <ml_window.h>
821 #include <ml_table_layout.h>
822 #include <ml_text_label.h>
823 #include <ml_box.h>
824 #include <ml_button.h>
825 #include <ml_widget.h>
827 #include "toy_calculator.h"
831 Every widget has an associated structure, which is where it
832 stores all its private data. For callers, the widget structure
833 is opaque (notice how it is defined in the
834 <code>toy_calculator.h</code> header file above). The
835 <code>toy_calculator</code> object we've been passing around
836 above is in fact a pointer, typedef'd to <code>struct
837 toy_calculator *</code>. (This is a common coding convention in
838 <code>c2lib</code> and <code>pthrlib</code>). In the actual
839 implementation, obviously we need to see the private members.
843 Moreover, every widget structure <strong>must</strong> begin
844 with a pointer to <code>struct ml_widget_operations</code>
845 because the <code>toy_calculator</code> object "inherits"
846 from the abstract base class <code>ml_widget</code>.
850 Here is the definition of <code>struct toy_calculator</code>:
854 static void repaint (void *, ml_session, const char *, io_handle);
856 struct ml_widget_operations toy_calculator_ops =
861 struct toy_calculator
863 struct ml_widget_operations *ops;
864 pool pool; /* Pool for allocations. */
865 ml_text_label disp; /* The display. */
866 char digits[16]; /* Display digits (only 10+1 used). */
867 double reg; /* Hidden register. */
868 int op; /* Operation: PLUS, MINUS, TIMES, DIVIDE. */
869 ml_box box; /* The top-level box. */
874 <code>repaint</code> is going to be the function which
875 actually draws our widget, but we'll see that a little bit
880 The most important function we need to define is
881 <code>new_toy_calculator</code> which creates new
882 <code>toy_calculator</code> widgets. I won't show this
883 function in full because it's quite long (it needs to
884 create one <code>ml_button</code> object for every
885 button on the calculator, and there are 18 of them in all!).
886 But here's the important outline.
890 We start by allocating and initialising a new <code>struct
891 toy_calculator</code>. A pointer to this is stored in <code>w</code>.
896 new_toy_calculator (pool pool, ml_session session)
904 w = pmalloc (pool, sizeof *w);
905 w->ops = &toy_calculator_ops;
907 strcpy (w->digits, "0");
913 Then some code creates the actual widgets contained inside
914 the calculator, the top level being a box widget:
918 /* Create the box surrounding the calculator. */
919 box = new_ml_box (pool);
921 /* A table layout widget is used to arrange the buttons and the screen.
922 * There are 6 rows, each with 4 columns.
924 tbl = new_ml_table_layout (pool, 6, 4);
926 /* Create the numeric buttons. */
927 b[0] = new_ml_button (pool, "0");
928 ml_button_set_callback (b[0], press_button_0, session, w);
929 ml_button_set_key (b[0], 1);
936 Finally we pack everything up and return the widget
937 pointer (<code>w</code>):
944 /* Pack the table into the box. */
945 ml_box_pack (box, tbl);
947 /* Save the display widget in the widget structure so that the
948 * callback functions can update it.
952 /* Save the box, so we can repaint. */
960 When a button is pressed, one of the appropriate callback
961 functions is called. Because there are 18 buttons, there are
962 18 separate callback functions, but we only reproduce a few
963 here. Notice how the <code>toy_calculator</code> pointer
964 is passed as the second argument (the <code>void *</code>),
965 so we need to cast this back before using it:
969 static void press_button_N (toy_calculator, int);
972 press_button_0 (ml_session session, void *vw)
974 toy_calculator w = (toy_calculator) vw;
976 press_button_N (w, 0);
984 press_button_N (toy_calculator w, int n)
988 if (strcmp (w->digits, "0") == 0)
989 w->digits[0] = '\0';
991 len = strlen (w->digits);
993 if ((strchr (w->digits, '.') && len < 11) || len < 10)
995 w->digits[len] = '0' + n;
996 w->digits[len+1] = '\0';
997 ml_text_label_set_text (w->disp, w->digits);
1003 Here's the callback function which runs when the [AC] button
1009 press_button_AC (ml_session session, void *vw)
1011 toy_calculator w = (toy_calculator) vw;
1013 strcpy (w->digits, "0");
1015 ml_text_label_set_text (w->disp, "0");
1020 Finally our widget must know how to repaint (redisplay) itself.
1021 <code>monolith</code> will call the repaint function at
1022 the appropriate moment, and it must generate the HTML corresponding
1023 to the widget. In the case of this widget, it's a compound
1024 widget built up entirely out of core <code>monolith</code>
1025 widgets. The top-level widget inside the calculator is the
1026 box (<code>w->box</code>), so we just call the repaint
1032 repaint (void *vw, ml_session session, const char *windowid, io_handle io)
1034 toy_calculator w = (toy_calculator) vw;
1036 ml_widget_repaint (w->box, session, windowid, io);
1041 That's all. Our reusable toy calculator is now complete.
1044 <h3>Example 06: Forms</h3>
1047 Example 06 shows the various input controls available
1048 when using forms. The example is straightforward, albeit
1049 rather long because of the number of different controls
1050 demonstrated. We begin with an unusually long list of
1055 #include <string.h>
1057 #include <pool.h>
1058 #include <pstring.h>
1059 #include <pthr_cgi.h>
1061 #include <monolith.h>
1062 #include <ml_window.h>
1063 #include <ml_text_label.h>
1064 #include <ml_button.h>
1065 #include <ml_table_layout.h>
1066 #include <ml_flow_layout.h>
1067 #include <ml_form.h>
1068 #include <ml_form_submit.h>
1069 #include <ml_form_text.h>
1070 #include <ml_form_textarea.h>
1071 #include <ml_form_password.h>
1072 #include <ml_form_select.h>
1073 #include <ml_form_radio_group.h>
1074 #include <ml_form_radio.h>
1075 #include <ml_form_checkbox.h>
1076 #include <ml_form_textarea.h>
1080 The private session data structure contains pointers to the
1081 form input widgets so that our callback functions are able
1082 to read their contents:
1086 /* Private per-session data. */
1091 /* The form input fields themselves. */
1092 ml_form_text familyname, givenname;
1093 ml_form_password password;
1094 ml_form_select dd, mm, yyyy; /* Date of birth. */
1095 ml_form_radio_group gender;
1096 ml_form_radio m, f; /* Gender. */
1097 ml_form_checkbox eating, drinking, sleeping; /* Interests */
1098 /*ml_form_file photo; File upload, not yet implemented. */
1099 ml_form_select dept;
1100 ml_form_textarea comments;
1105 <code>app_main</code> allocates the session data structure
1106 and calls <code>create_form</code> which actually creates
1112 app_main (ml_session session)
1114 pool pool = ml_session_pool (session);
1117 /* Create the private, per-session data area and save it in the
1120 data = pmalloc (pool, sizeof *data);
1122 /* Create the top-level window. */
1123 data->win = new_ml_window (session, pool);
1125 create_form (session, data);
1130 <code>create_form</code> is quite a long, but not very
1131 complex function. It creates an input control of each
1132 type. Notice first the visual structure of the form:
1140 <i> Table layout </i>
1141 <table border="1" width="100%">
1143 <td> <i>Label</i> </td>
1144 <td> <i>Form input</i> </td>
1147 <td> <i>Label</i> </td>
1148 <td> <i>Form input</i> </td>
1151 <td colspan="2"> <i>etc.</i> </td>
1154 <td> <i>Empty</i> </td>
1155 <td> <i>Form submit</i> </td>
1163 I will not reproduce all of the form inputs here:
1168 create_form (ml_session session, void *vp)
1170 pool pool = ml_session_pool (session);
1171 struct data *data = (struct data *) vp;
1173 ml_table_layout tbl;
1175 ml_form_submit submit;
1176 ml_flow_layout flow;
1179 /* Create the form. */
1180 form = new_ml_form (pool);
1181 ml_form_set_callback (form, submit_form, session, data);
1183 /* Create the table. */
1184 tbl = new_ml_table_layout (pool, 10, 2);
1186 /* Create the contents of the form. */
1187 text = new_ml_text_label (pool, "Family name / surname");
1188 ml_table_layout_pack (tbl, text, 0, 0);
1189 data->familyname = new_ml_form_text (pool, form);
1190 ml_table_layout_pack (tbl, data->familyname, 0, 1);
1196 /* Submit button. */
1197 submit = new_ml_form_submit (pool, form, "Submit");
1198 ml_table_layout_pack (tbl, submit, 9, 1);
1200 /* Pack it all up. */
1201 ml_form_pack (form, tbl);
1202 ml_window_pack (data->win, form);
1207 Notice that we set a callback function on the form:
1211 ml_form_set_callback (form, submit_form, session, data);
1215 When the form is submitted by the user, <code>submit_form
1216 (session, data)</code> will be called. <code>submit_form</code>,
1217 reproduced next, can read the value that the user entered
1218 into each form field by calling <code>ml_form_input_get_value</code>:
1223 submit_form (ml_session session, void *vp)
1225 pool pool = ml_session_pool (session);
1226 struct data *data = (struct data *) vp;
1228 ml_table_layout tbl;
1239 "Date of birth: dd = %d, mm = %d, yyyy = %d\n"
1241 " M is checked: %d F is checked: %d\n"
1242 "Interests: Eating = %d, Drinking = %d, Sleeping = %d\n"
1243 "Dept fields checked: [ %s ]\n"
1248 ml_form_input_get_value (data->familyname),
1249 ml_form_input_get_value (data->givenname),
1250 ml_form_input_get_value (data->password),
1251 1 + ml_form_select_get_selection (data->dd),
1252 1 + ml_form_select_get_selection (data->mm),
1253 1900 + ml_form_select_get_selection (data->yyyy),
1254 ml_form_input_get_value (data->gender),
1255 ml_form_radio_get_checked (data->m),
1256 ml_form_radio_get_checked (data->f),
1257 ml_form_input_get_value (data->eating) ? 1 : 0,
1258 ml_form_input_get_value (data->drinking) ? 1 : 0,
1259 ml_form_input_get_value (data->sleeping) ? 1 : 0,
1261 pvitostr (pool, ml_form_select_get_selections (data->dept)), ", "),
1262 ml_form_input_get_value (data->comments));
1264 tbl = new_ml_table_layout (pool, 2, 1);
1265 text = new_ml_text_label (pool, str);
1266 ml_table_layout_pack (tbl, text, 0, 0);
1267 button = new_ml_button (pool, "Back to form");
1268 ml_button_set_callback (button, create_form, session, data);
1269 ml_table_layout_pack (tbl, button, 1, 0);
1271 ml_window_pack (data->win, tbl);
1276 Notice how we have added a button called <q>Back to form</q>
1277 which calls <code>create_form</code>.
1280 <h3>The end of this tutorial</h3>
1283 That's the end of this tutorial. You should be able to
1284 go and write <code>monolith</code> applications and
1285 widgets now. Full manual pages are included below.
1288 <h2>The monolith class hierarchy</h2>
1291 Of course <code>monolith</code> is written in C, so there are no
1292 classes <i>per se</i>, but this is the general class hierarchy of
1293 <code>monolith</code> widgets and windows:
1297 ml_window: window or frameset
1299 ml_session: user session
1304 +-- ml_box: draws a box around another widget
1306 +-- ml_button: simple button
1308 +-- ml_dialog: ask the user a question
1310 +-- ml_flow_layout: layout widgets one after another
1312 +-- ml_form: surrounds a collection of form inputs
1317 | +-- ml_form_checkbox: checkbox (tickbox)
1319 | +-- ml_form_file: file upload input [not implemented]
1321 | +-- ml_form_password: single line password input
1323 | +-- ml_form_radio_group: group of radio buttons
1325 | +-- ml_form_radio: radio button
1327 | +-- ml_form_select: drop-down list of options
1329 | +-- ml_form_submit: submit button
1331 | +-- ml_form_text: single line text input
1333 | +-- ml_form_textarea: multi-line text input
1335 +-- ml_image: display an icon or image
1337 +-- ml_label: display arbitrary HTML
1339 +-- ml_table_layout: powerful table layouts of widgets
1341 +-- ml_text_label: single line or paragraphs of plain text
1343 +-- ml_toggle_button: toggle button
1347 Inheritance is faked using a technique very similar to the vtables
1351 <h2>Other Notes</h2>
1353 <h3>Can I theme monolith applications?</h3>
1356 Yes, you can. At the moment, the easiest way to change the look
1357 and feel is to edit the default stylesheet
1358 (<code>default.css</code>). This way you can make extensive
1359 changes to how your application looks from a single file.
1363 If you don't like the idea of editing <code>default.css</code>,
1364 then copy it and make your own. Call
1365 <code>ml_window_set_stylesheet</code> on all your application
1366 windows to point to your new stylesheet.
1370 You can even provide different themes to different users
1371 (so-called "skinning" - ugh I hate that term). Once a user has
1372 logged into the app, call <code>ml_window_set_stylesheet</code>
1373 with the appropriate theme for that user.
1376 <h3>Frames considered harmful</h3>
1379 Although <code>monolith</code> supports frames and pop-up
1380 windows, care should be taken because these do not work in
1381 the way that most users expect.
1385 The problem arises when one frame tries to update the state
1386 of another frame (the same problem applies to two separate
1387 windows, but I'll just use the generic term "frame" here).
1388 For example, imagine the following simple frameset:
1393 <td style="width: 100px">
1394 <p><i>Left frame</i></p>
1399 <td style="width: 200px" valign="top">
1400 <p><i>Right frame</i></p>
1406 It's common to want the buttons in the left hand frame to
1407 change the contents displayed in the right hand frame, and
1408 a naive way to do this would be to set the callback for
1409 each button to a function like this:
1414 update_right_frame (ml_session session, void *vp)
1416 /* Get private per-session data. */
1417 struct data *data = (struct data *) vp;
1419 pool pool = ml_session_pool (session);
1421 ml_text_label label = new_ml_text_label (pool, <i>updated content</i>);
1423 /* Change the contents of the right hand frame. */
1424 ml_window_pack (data->right_frame, label);
1429 The problem is that this doesn't work at all. Pressing the
1430 buttons will not update the right hand frame.
1434 For seasoned HTML programmers, it will be obvious why this
1435 happens. For people used to traditional application development,
1436 it is confusing and seems like a bug.
1440 In future, we will add features to Monolith to allow careful
1441 developers to use frames in situations such as above. However
1442 at the moment, the advice is:
1446 <li> Consider each frame/window/pop-up to be a completely
1447 separate entity, almost like a separate session. Don't
1448 expect that updating properties in another frame/window/pop-up
1449 will work (it almost certainly will have no effect).
1450 <li> Avoid using framesets if possible. Table layouts are
1451 often a better substitute.
1454 <h3>Notes on forms</h3>
1457 You cannot nest forms. (This is a limitation of HTML.)
1461 If you have several forms on the same page, you can
1462 run into problems. A common problem happens when you
1463 have two forms on the same dialog like this:
1469 <i>First form</i><br>
1470 [ --- input #1 --- ] [submit]
1475 <i>Second form</i><br>
1476 [ --- input #2 --- ] [submit]
1482 If the user types something in input field #1, then types
1483 something in input field #2, and presses the second submit
1484 button, then the contents of input field #1 will disappear.
1488 To avoid this, either only use one form on a dialog, or design
1489 your dialogs so that it is clear that the first submit button
1490 must be pressed after filling out the first form.
1494 Another problem with forms (and again, a limitation of HTML)
1495 is that they are not interactive. The server cannot read the
1496 value of a form input field until the [Submit] button has
1501 If you are using forms, then only use form input widgets
1502 inside them. Toggle buttons and so on are not form input
1503 widgets, and cannot be part of a form.
1506 <h2>Links to manual pages</h2>
1509 <li> <a href="ml_box_pack.3.html"><code>ml_box_pack(3)</code></a> </li>
1510 <li> <a href="ml_button_get_text.3.html"><code>ml_button_get_text(3)</code></a> </li>
1511 <li> <a href="ml_button_set_callback.3.html"><code>ml_button_set_callback(3)</code></a> </li>
1512 <li> <a href="ml_button_set_key.3.html"><code>ml_button_set_key(3)</code></a> </li>
1513 <li> <a href="ml_button_set_text.3.html"><code>ml_button_set_text(3)</code></a> </li>
1514 <li> <a href="ml_dialog_add_button.3.html"><code>ml_dialog_add_button(3)</code></a> </li>
1515 <li> <a href="ml_dialog_clear_buttons.3.html"><code>ml_dialog_clear_buttons(3)</code></a> </li>
1516 <li> <a href="ml_dialog_get_icon.3.html"><code>ml_dialog_get_icon(3)</code></a> </li>
1517 <li> <a href="ml_dialog_get_text.3.html"><code>ml_dialog_get_text(3)</code></a> </li>
1518 <li> <a href="ml_dialog_get_title.3.html"><code>ml_dialog_get_title(3)</code></a> </li>
1519 <li> <a href="ml_dialog_set_icon.3.html"><code>ml_dialog_set_icon(3)</code></a> </li>
1520 <li> <a href="ml_dialog_set_text.3.html"><code>ml_dialog_set_text(3)</code></a> </li>
1521 <li> <a href="ml_dialog_set_title.3.html"><code>ml_dialog_set_title(3)</code></a> </li>
1522 <li> <a href="ml_entry_point.3.html"><code>ml_entry_point(3)</code></a> </li>
1523 <li> <a href="ml_flow_layout_clear.3.html"><code>ml_flow_layout_clear(3)</code></a> </li>
1524 <li> <a href="ml_flow_layout_erase.3.html"><code>ml_flow_layout_erase(3)</code></a> </li>
1525 <li> <a href="ml_flow_layout_get.3.html"><code>ml_flow_layout_get(3)</code></a> </li>
1526 <li> <a href="ml_flow_layout_insert.3.html"><code>ml_flow_layout_insert(3)</code></a> </li>
1527 <li> <a href="ml_flow_layout_pack.3.html"><code>ml_flow_layout_pack(3)</code></a> </li>
1528 <li> <a href="ml_flow_layout_pop_back.3.html"><code>ml_flow_layout_pop_back(3)</code></a> </li>
1529 <li> <a href="ml_flow_layout_pop_front.3.html"><code>ml_flow_layout_pop_front(3)</code></a> </li>
1530 <li> <a href="ml_flow_layout_push_back.3.html"><code>ml_flow_layout_push_back(3)</code></a> </li>
1531 <li> <a href="ml_flow_layout_push_front.3.html"><code>ml_flow_layout_push_front(3)</code></a> </li>
1532 <li> <a href="ml_flow_layout_replace.3.html"><code>ml_flow_layout_replace(3)</code></a> </li>
1533 <li> <a href="ml_flow_layout_size.3.html"><code>ml_flow_layout_size(3)</code></a> </li>
1534 <li> <a href="ml_form_input_clear_value.3.html"><code>ml_form_input_clear_value(3)</code></a> </li>
1535 <li> <a href="ml_form_input_get_value.3.html"><code>ml_form_input_get_value(3)</code></a> </li>
1536 <li> <a href="ml_form_input_set_value.3.html"><code>ml_form_input_set_value(3)</code></a> </li>
1537 <li> <a href="ml_form_radio_get_checked.3.html"><code>ml_form_radio_get_checked(3)</code></a> </li>
1538 <li> <a href="ml_form_radio_group_pack.3.html"><code>ml_form_radio_group_pack(3)</code></a> </li>
1539 <li> <a href="ml_form_radio_set_checked.3.html"><code>ml_form_radio_set_checked(3)</code></a> </li>
1540 <li> <a href="ml_form_select_clear.3.html"><code>ml_form_select_clear(3)</code></a> </li>
1541 <li> <a href="ml_form_select_erase.3.html"><code>ml_form_select_erase(3)</code></a> </li>
1542 <li> <a href="ml_form_select_get.3.html"><code>ml_form_select_get(3)</code></a> </li>
1543 <li> <a href="ml_form_select_get_multiple.3.html"><code>ml_form_select_get_multiple(3)</code></a> </li>
1544 <li> <a href="ml_form_select_get_selection.3.html"><code>ml_form_select_get_selection(3)</code></a> </li>
1545 <li> <a href="ml_form_select_get_selections.3.html"><code>ml_form_select_get_selections(3)</code></a> </li>
1546 <li> <a href="ml_form_select_get_size.3.html"><code>ml_form_select_get_size(3)</code></a> </li>
1547 <li> <a href="ml_form_select_insert.3.html"><code>ml_form_select_insert(3)</code></a> </li>
1548 <li> <a href="ml_form_select_pop_back.3.html"><code>ml_form_select_pop_back(3)</code></a> </li>
1549 <li> <a href="ml_form_select_pop_front.3.html"><code>ml_form_select_pop_front(3)</code></a> </li>
1550 <li> <a href="ml_form_select_push_back.3.html"><code>ml_form_select_push_back(3)</code></a> </li>
1551 <li> <a href="ml_form_select_push_front.3.html"><code>ml_form_select_push_front(3)</code></a> </li>
1552 <li> <a href="ml_form_select_replace.3.html"><code>ml_form_select_replace(3)</code></a> </li>
1553 <li> <a href="ml_form_select_set_multiple.3.html"><code>ml_form_select_set_multiple(3)</code></a> </li>
1554 <li> <a href="ml_form_select_set_selection.3.html"><code>ml_form_select_set_selection(3)</code></a> </li>
1555 <li> <a href="ml_form_select_set_selections.3.html"><code>ml_form_select_set_selections(3)</code></a> </li>
1556 <li> <a href="ml_form_select_set_size.3.html"><code>ml_form_select_set_size(3)</code></a> </li>
1557 <li> <a href="ml_form_select_size.3.html"><code>ml_form_select_size(3)</code></a> </li>
1558 <li> <a href="ml_frameset_get_title.3.html"><code>ml_frameset_get_title(3)</code></a> </li>
1559 <li> <a href="ml_frameset_set_description.3.html"><code>ml_frameset_set_description(3)</code></a> </li>
1560 <li> <a href="ml_frameset_set_title.3.html"><code>ml_frameset_set_title(3)</code></a> </li>
1561 <li> <a href="ml_image_get_src.3.html"><code>ml_image_get_src(3)</code></a> </li>
1562 <li> <a href="ml_image_set_src.3.html"><code>ml_image_set_src(3)</code></a> </li>
1563 <li> <a href="ml_label_get_text.3.html"><code>ml_label_get_text(3)</code></a> </li>
1564 <li> <a href="ml_label_set_text.3.html"><code>ml_label_set_text(3)</code></a> </li>
1565 <li> <a href="ml_register_action.3.html"><code>ml_register_action(3)</code></a> </li>
1566 <li> <a href="ml_session_args.3.html"><code>ml_session_args(3)</code></a> </li>
1567 <li> <a href="ml_session_canonical_path.3.html"><code>ml_session_canonical_path(3)</code></a> </li>
1568 <li> <a href="ml_session_pool.3.html"><code>ml_session_pool(3)</code></a> </li>
1569 <li> <a href="ml_session_script_name.3.html"><code>ml_session_script_name(3)</code></a> </li>
1570 <li> <a href="ml_session_sessionid.3.html"><code>ml_session_sessionid(3)</code></a> </li>
1571 <li> <a href="ml_table_layout_pack.3.html"><code>ml_table_layout_pack(3)</code></a> </li>
1572 <li> <a href="ml_table_layout_set_align.3.html"><code>ml_table_layout_set_align(3)</code></a> </li>
1573 <li> <a href="ml_table_layout_set_colspan.3.html"><code>ml_table_layout_set_colspan(3)</code></a> </li>
1574 <li> <a href="ml_table_layout_set_rowspan.3.html"><code>ml_table_layout_set_rowspan(3)</code></a> </li>
1575 <li> <a href="ml_table_layout_set_valign.3.html"><code>ml_table_layout_set_valign(3)</code></a> </li>
1576 <li> <a href="ml_text_label_set_font_size.3.html"><code>ml_text_label_set_font_size(3)</code></a> </li>
1577 <li> <a href="ml_text_label_set_font_weight.3.html"><code>ml_text_label_set_font_weight(3)</code></a> </li>
1578 <li> <a href="ml_text_label_set_text.3.html"><code>ml_text_label_set_text(3)</code></a> </li>
1579 <li> <a href="ml_text_label_set_text_align.3.html"><code>ml_text_label_set_text_align(3)</code></a> </li>
1580 <li> <a href="ml_unregister_action.3.html"><code>ml_unregister_action(3)</code></a> </li>
1581 <li> <a href="ml_widget_repaint.3.html"><code>ml_widget_repaint(3)</code></a> </li>
1582 <li> <a href="ml_window_get_charset.3.html"><code>ml_window_get_charset(3)</code></a> </li>
1583 <li> <a href="ml_window_get_stylesheet.3.html"><code>ml_window_get_stylesheet(3)</code></a> </li>
1584 <li> <a href="ml_window_get_title.3.html"><code>ml_window_get_title(3)</code></a> </li>
1585 <li> <a href="ml_window_pack.3.html"><code>ml_window_pack(3)</code></a> </li>
1586 <li> <a href="ml_window_set_charset.3.html"><code>ml_window_set_charset(3)</code></a> </li>
1587 <li> <a href="ml_window_set_stylesheet.3.html"><code>ml_window_set_stylesheet(3)</code></a> </li>
1588 <li> <a href="ml_window_set_title.3.html"><code>ml_window_set_title(3)</code></a> </li>
1589 <li> <a href="new_ml_box.3.html"><code>new_ml_box(3)</code></a> </li>
1590 <li> <a href="new_ml_button.3.html"><code>new_ml_button(3)</code></a> </li>
1591 <li> <a href="new_ml_dialog.3.html"><code>new_ml_dialog(3)</code></a> </li>
1592 <li> <a href="new_ml_flow_layout.3.html"><code>new_ml_flow_layout(3)</code></a> </li>
1593 <li> <a href="new_ml_form.3.html"><code>new_ml_form(3)</code></a> </li>
1594 <li> <a href="new_ml_form_checkbox.3.html"><code>new_ml_form_checkbox(3)</code></a> </li>
1595 <li> <a href="new_ml_form_password.3.html"><code>new_ml_form_password(3)</code></a> </li>
1596 <li> <a href="new_ml_form_radio.3.html"><code>new_ml_form_radio(3)</code></a> </li>
1597 <li> <a href="new_ml_form_radio_group.3.html"><code>new_ml_form_radio_group(3)</code></a> </li>
1598 <li> <a href="new_ml_form_select.3.html"><code>new_ml_form_select(3)</code></a> </li>
1599 <li> <a href="new_ml_form_submit.3.html"><code>new_ml_form_submit(3)</code></a> </li>
1600 <li> <a href="new_ml_form_text.3.html"><code>new_ml_form_text(3)</code></a> </li>
1601 <li> <a href="new_ml_form_textarea.3.html"><code>new_ml_form_textarea(3)</code></a> </li>
1602 <li> <a href="new_ml_frameset.3.html"><code>new_ml_frameset(3)</code></a> </li>
1603 <li> <a href="new_ml_image.3.html"><code>new_ml_image(3)</code></a> </li>
1604 <li> <a href="new_ml_label.3.html"><code>new_ml_label(3)</code></a> </li>
1605 <li> <a href="new_ml_table_layout.3.html"><code>new_ml_table_layout(3)</code></a> </li>
1606 <li> <a href="new_ml_text_label.3.html"><code>new_ml_text_label(3)</code></a> </li>
1607 <li> <a href="new_ml_window.3.html"><code>new_ml_window(3)</code></a> </li>
1611 <address><a href="mailto:rich@annexia.org">Richard Jones</a></address>
1612 <!-- Created: Wed May 1 19:36:16 BST 2002 -->
1613 <!-- hhmts start -->
1614 Last modified: Sat Sep 7 14:45:50 BST 2002