r19357: More ldbbrowse work.
[kai/samba.git] / swat / apps / samba / utils / ldbbrowse.html
1 <html>
2 <head>
3   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
4   <title>qooxdoo &raquo; Demo</title>
5   <link type="text/css" rel="stylesheet" href="../../resource/css/layout.css"/>
6   <!--[if IE]>
7   <link
8    type="text/css" rel="stylesheet" href="../../resource/css/layout_ie.css"/>
9   <![endif]-->
10   <script type="text/javascript" src="../../script/qx.js"></script>
11 </head>
12 <body>
13   <script type="text/javascript" src="../../script/layout.js"></script>
14
15
16   <div id="demoDescription">
17     <p>
18     A simple LDB browser.
19   </div>
20
21 <script type="text/javascript">
22
23 // All of our variables which must be in a global scope will be part of this
24 // object
25 globals = new Object();
26
27 // Default database File to open
28 globals.dbFile = "/usr/local/samba/private/sam.ldb";
29
30 // No database is yet open
31 globals.dbHandle = null;
32         
33
34
35 /*
36 Root is as found by:
37   source/bin/ldbsearch -H /usr/local/samba/private/sam.ldb -b '' \
38     -s base defaultNamingContext
39
40 Schema page:
41   source/bin/ldbsearch -H /usr/local/samba/private/sam.ldb -b '' \
42     -s base subschemaSubentry
43   source/bin/ldbsearch -H /usr/local/samba/private/sam.ldb -b \
44     'CN=Aggregate,CN=Schema,CN=Configuration,DC=workgroup,DC=example,DC=com' \
45     -s base objectClasses attributeTypes matchingRules ldapSyntaxes        
46 */
47
48 function setAppearances()
49 {
50     // Modify the default appearance of a ComboBox for use in Search tab:
51     //   use more of the available width.
52     //
53     // If we had multiple uses, we'd create a new appearance.  Since we don't,
54     // we can just modify this default appearance.
55     //
56     // See http://qooxdoo.org/documentation/user_manual/appearance for an
57     // explanation of what's going on here.  The missing significant point in
58     // the discussion is that in the current qooxdoo appearance
59     // implementation, it's not possible to override a specific widget's
60     // appearance with explicit settings just for that widget (stupid!).  I
61     // expect that to change in a future version.
62     var appMgr = qx.manager.object.AppearanceManager.getInstance();
63     var theme = appMgr.getAppearanceTheme();
64     var appearance = theme._appearances["combo-box"];
65     if (! appearance)
66     {
67         return;
68     }
69     var oldInitial = appearance.initial;
70     appearance.initial = function(vTheme)
71     {
72         var res = oldInitial ? oldInitial.apply(this, arguments) : {};
73         res.width = "80%";
74         return res;
75     }
76 }
77
78 function setupMenu(clientDocument)
79 {
80     var c1 = new qx.client.Command();
81     c1.addEventListener("execute", function(e) {
82                             this.debug("Execute: " + e.getData().getLabel());
83                         });
84
85     // Create the File menu pulldown
86     var fileMenu_ = new qx.ui.menu.Menu();
87       
88     // Create items for within File menu
89     var fileMenu_NewTab_ = new qx.ui.menu.Menu();
90     {
91         var fileMenu_NewTab_Search =
92             new qx.ui.menu.MenuButton("Search", null, c1);
93         var fileMenu_NewTab_Browse =
94             new qx.ui.menu.MenuButton("Browse", null, c1);
95         var fileMenu_NewTab_Schema =
96             new qx.ui.menu.MenuButton("Schema", null, c1);
97         fileMenu_NewTab_.add(fileMenu_NewTab_Search,
98                              fileMenu_NewTab_Browse,
99                              fileMenu_NewTab_Schema);
100     }
101     var fileMenu_NewTab =
102         new qx.ui.menu.MenuButton("New tab", null, null, fileMenu_NewTab_);
103
104     var fileMenu_Preferences =
105         new qx.ui.menu.MenuButton("Preferences", null, c1);
106     var fileMenu_CloseTab =
107         new qx.ui.menu.MenuButton("Close Tab", null, c1);
108     var fileMenu_ShowMessageLog =
109         new qx.ui.menu.MenuButton("Show Message Log", null, c1);
110     var fileMenu_Quit =
111         new qx.ui.menu.MenuButton("Quit", null, c1);
112       
113     // Add the menu items to the menu
114     fileMenu_.add(fileMenu_NewTab,
115                   fileMenu_Preferences,
116                   fileMenu_CloseTab,
117                   fileMenu_ShowMessageLog,
118                   fileMenu_Quit);
119
120
121     // Create the Filter menu pulldown
122     var filterMenu_ = new qx.ui.menu.Menu();
123
124     // Create items for within Filter menu
125     var filterMenu_NewFilter =
126         new qx.ui.menu.MenuButton("New Filter", null, c1);
127     var filterMenu_EditFilters =
128         new qx.ui.menu.MenuButton("Edit Filters", null, c1);
129     var filterMenu_Separator =
130         new qx.ui.menu.MenuSeparator();
131
132     // Add the menu items to the menu
133     filterMenu_.add(filterMenu_NewFilter,
134                     filterMenu_EditFilters,
135                     filterMenu_Separator);
136
137
138     // Add the menu items to the document
139     clientDocument.add(fileMenu_,
140                        fileMenu_NewTab_,
141                        filterMenu_);
142
143
144     // Create and position the toolbar which will act as our menubar
145     var toolBar = new qx.ui.toolbar.ToolBar();
146     toolBar.set(
147         {
148             top: 28,
149             left: 20,
150             right: 300
151         });
152       
153     // Create the toolbar menu items and associate them with the pulldowns
154     var fileMenuButton =
155         new qx.ui.toolbar.ToolBarMenuButton("File", fileMenu_);
156     var filterMenuButton =
157         new qx.ui.toolbar.ToolBarMenuButton("Filters", filterMenu_);
158             
159     // Add the toolbar items to the toolbar
160     toolBar.add(fileMenuButton,
161                 filterMenuButton);
162       
163     // Add the toolbar to the document
164     clientDocument.add(toolBar);
165
166     // Give 'em what we built!
167     return toolBar;
168 }
169
170 function setupTabs(clientDocument)
171 {
172     // Create and position the tabview
173     var tabView_ = new qx.ui.pageview.tabview.TabView;
174     tabView_.set(
175         {
176             top: 60,
177             left: 20,
178             right: 300,
179             bottom: 30
180         });
181
182     // Create each of the tabs
183     var tabView_Search =
184         new qx.ui.pageview.tabview.TabViewButton("Search");
185     var tabView_Browse =
186         new qx.ui.pageview.tabview.TabViewButton("Browse");
187     var tabView_Schema =
188         new qx.ui.pageview.tabview.TabViewButton("Schema");
189
190     // Specify the initially-selected tab
191     tabView_Search.setChecked(true);
192
193     // Add each of the tabs to the tabview
194     tabView_.getBar().add(tabView_Search,
195                           tabView_Browse,
196                           tabView_Schema);
197
198     // Create the pages to display when each tab is selected
199     var tabViewPage_Search =
200         new qx.ui.pageview.tabview.TabViewPage(tabView_Search);
201     var tabViewPage_Browse =
202         new qx.ui.pageview.tabview.TabViewPage(tabView_Browse);
203     var tabViewPage_Schema =
204         new qx.ui.pageview.tabview.TabViewPage(tabView_Schema);
205
206     // Build the search page
207     var searchResultsTable = buildPageSearch(tabViewPage_Search);
208
209     // Provide access to the search results table
210     tabView_.searchResultTable = searchResultsTable;
211
212     // Build the browse page
213     buildPageBrowse(tabViewPage_Browse);
214
215     // Build the schema page
216     buildPageSchema(tabViewPage_Schema);
217
218     // Add the pages to the tabview
219     tabView_.getPane().add(tabViewPage_Search,
220                            tabViewPage_Browse,
221                            tabViewPage_Schema);
222
223     // Add the tabview to the document
224     clientDocument.add(tabView_);
225
226     // Give 'em what we built!
227     return tabView_;
228 }
229
230 function buildPageSearch(page)
231 {
232     // We need a vertical box layout for the various input fields
233     var vlayout = new qx.ui.layout.VerticalBoxLayout();
234     vlayout.setWidth("100%");
235
236     // We need a horizontal box layout for the search combo box and its label
237     var hlayout = new qx.ui.layout.HorizontalBoxLayout();
238     hlayout.setWidth("100%");
239     hlayout.setHeight(25);
240
241     // Create a label for the list of required attributes
242     var label = new qx.ui.basic.Atom("Search Expression:");
243     label.setWidth(100);
244     label.setHorizontalChildrenAlign("right");
245
246     // Add the label to the horizontal layout
247     hlayout.add(label);
248
249     // Create a combo box for entry of the search expression
250     var search = new qx.ui.form.ComboBox();
251     search.getField().setWidth("100%");
252     search.setEditable(true);
253     
254     // Add the combo box to the horizontal layout
255     hlayout.add(search);
256
257     // Add the horizontal layout to the vertical layout
258     vlayout.add(hlayout);
259
260     // We need a horizontal box layout for the base combo box and its label
261     hlayout = new qx.ui.layout.HorizontalBoxLayout();
262     hlayout.setWidth("100%");
263     hlayout.setHeight(25);
264
265     // Create a label for the list of required attributes
266     var label = new qx.ui.basic.Atom("Base:");
267     label.setWidth(100);
268     label.setHorizontalChildrenAlign("right");
269
270     // Add the label to the horizontal layout
271     hlayout.add(label);
272
273     // Create a combo box for entry of the search expression
274     var base = new qx.ui.form.ComboBox();
275     base.getField().setWidth("100%");
276     base.setEditable(true);
277     
278     // Add the combo box to the horizontal layout
279     hlayout.add(base);
280
281     // Add the horizontal layout to the vertical layout
282     vlayout.add(hlayout);
283
284     // We need a horizontal box layout for scope radio buttons
285     hlayout = new qx.ui.layout.HorizontalBoxLayout();
286     hlayout.setWidth("100%");
287     hlayout.setHeight(25);
288
289     // Create a label for the list of required attributes
290     var label = new qx.ui.basic.Atom("Scope:");
291     label.setWidth(100);
292     label.setHorizontalChildrenAlign("right");
293
294     // Add the label to the horizontal layout
295     hlayout.add(label);
296
297     // Create a radio button for each scope
298     var rbDefault = new qx.ui.form.RadioButton("Default",   "default");
299     var rbBase    = new qx.ui.form.RadioButton("Base",      "base");
300     var rbOne     = new qx.ui.form.RadioButton("One Level", "one");
301     var rbSubtree = new qx.ui.form.RadioButton("Subtree",   "subtree");
302
303     // Use a default of "Default"
304     rbDefault.setChecked(true);
305
306     // Add the radio buttons to the horizontal layout
307     hlayout.add(rbDefault, rbBase, rbOne, rbSubtree);
308
309     // Group the radio buttons so only one in the group may be selected
310     var scope = new qx.manager.selection.RadioManager("scope",
311                                                       [
312                                                           rbDefault,
313                                                           rbBase,
314                                                           rbOne,
315                                                           rbSubtree
316                                                       ]);
317     
318     // Right-justify the 'Find' button
319     var spacer = new qx.ui.basic.HorizontalSpacer;
320     hlayout.add(spacer);
321
322     // Create the 'Find' button
323     var find = new qx.ui.form.Button('Find');
324     hlayout.add(find);
325
326     // Add the Find button line to the vertical layout
327     vlayout.add(hlayout);
328
329     // We'll be setting url and service upon execute; no need to do it now.
330     var rpc = new qx.io.remote.Rpc();
331     rpc.setTimeout(10000);
332     var mycall = null;
333
334     find.addEventListener("execute", function()
335     {
336         // Set the URL and Service
337         rpc.setUrl("/services/");
338         rpc.setServiceName("samba.ldb");
339         rpc.setCrossDomain(false);
340
341         find.setEnabled(false);
342         mycall = rpc.callAsync(function(result, ex, id)
343         {
344             mycall = null;
345             if (ex == null)
346             {
347                 var rowData = [];
348                 for (var i = 0; i < result.length; i++)
349                 {
350                     var o = result[i];
351                     if (typeof(o) != "object")
352                     {
353                         alert("Found unexpected result, type " +
354                               typeof(o) +
355                               ", " +
356                               o +
357                               "\n");
358                         continue;
359                     }
360                     for (var field in o)
361                     {
362                         // skip dn and distinguishedName fields;
363                         // they're shown in each row anyway.
364                         if (field == "dn" || field == "distinguishedName")
365                         {
366                             continue;
367                         }
368                         rowData.push( [
369                                           o["dn"],
370                                           field,
371                                           o[field]
372                                       ] );
373                     }
374
375                     // Tell the table to use the new data
376                     tableModel.setData(rowData);
377                 }
378             }
379             else
380             {
381                 alert("Async(" + id + ") exception: " + ex);
382             }
383             find.setEnabled(true);
384         },
385         "search",                       // method
386         globals.dbHandle,               // database handle
387         search.getValue(),              // search expression
388         base.getValue(),                // baseDN
389         scope.getSelected().getValue(), // scope
390         [ "*" ]);                       // attributes
391     });
392
393     // Add the horizontal box layout to the page
394     page.add(vlayout);
395
396     // Create a simple table model
397     var tableModel = new qx.ui.table.SimpleTableModel();
398     tableModel.setColumns([ "Distinguished Name", "Attribute", "Value" ]);
399
400     tableModel.setColumnEditable(0, false);
401     tableModel.setColumnEditable(1, false);
402     tableModel.setColumnEditable(2, false);
403
404     // Create a table
405     var table = new qx.ui.table.Table(tableModel);
406     with (table)
407     {
408       set({
409               top: 80,
410               left: 0,
411               right: 0,
412               bottom: 10,
413               statusBarVisible: false,
414               columnVisibilityButtonVisible: false
415           });
416       setColumnWidth(0, 300);
417       setColumnWidth(1, 180);
418       setColumnWidth(2, 240);
419       setMetaColumnCounts([ 1, -1 ]); // h-scroll attribute and value together
420     };
421
422     page.add(table);
423
424     return table;
425 }
426
427 function buildPageBrowse(page)
428 {
429     /*
430      * Reset the default of always showing the plus/minus symbol.  The
431      * default is 'false'.  We want to always display it for each folder
432      * (and then stop displaying it if we determine upon open that there
433      * are no contents).
434      */
435     var constructor = qx.OO.classes["qx.ui.treefullcontrol.TreeFolder"];
436     qx.Proto = constructor.prototype;
437     qx.OO.changeProperty(
438         {
439             name         : "alwaysShowPlusMinusSymbol",
440             type         : qx.constant.Type.BOOLEAN,
441             defaultValue : true
442         });
443
444     // Create a vertical splitpane for tree (top) and table (bottom)
445     var splitpane = new qx.ui.splitpane.VerticalSplitPane("1*", "2*");
446     splitpane.setEdge(0);
447
448     // Create a tree row structure for the tree root
449     var trs = qx.ui.treefullcontrol.TreeRowStructure.getInstance().standard(globals.dbFile);
450
451     // Create the tree and set its characteristics
452     var tree = new qx.ui.treefullcontrol.Tree(trs);
453     tree.set(
454         {
455             backgroundColor: 255,
456             border: qx.renderer.border.BorderPresets.getInstance().inset,
457             overflow: "auto",
458             height: null,
459             top: 10,
460             left: 0,
461             right: 0,
462             bottom: 10,
463             open: false
464         });
465
466     var addChildren = function(parent, children, retrieve)
467     {
468         var t;
469         var trs;
470         var child;
471
472         // Any children?
473         if (! children || children["length"] == 0)
474         {
475             // Nope.  Allow parent's expand/contract button to be removed
476             parent.setAlwaysShowPlusMinusSymbol(false);
477             return;
478         }
479
480         for (i = 0; i < children.length; i++)
481         {
482             var name;
483
484             child = children[i];
485
486             // Determine name for new tree row.  If first level, use entire
487             // DN.  Otherwise, strip off first additional component.
488             if (retrieve == "defaultNamingContext")
489             {
490                 name = child["defaultNamingContext"];
491             }
492             else
493             {
494                 name = child["dn"].split(",")[0];
495             }
496
497             // Build a standard tree row
498             trs = qx.ui.treefullcontrol.TreeRowStructure.getInstance().standard(name);
499
500             // This row is a "folder" (it can have children)
501             t = new qx.ui.treefullcontrol.TreeFolder(trs);
502
503             // Add this row to its parent
504             parent.add(t);
505         }
506
507         // Force flushing of pending DOM updates.  This is actually a
508         // work-around for a bug.  Without this, occasionally, updates to the
509         // gui aren't displayed until some 'action' takes place, e.g. key
510         // press or mouse movement.
511         qx.ui.core.Widget.flushGlobalQueues(true);
512     }
513
514     // Prepare to issue RPC calls
515     var rpc = new qx.io.remote.Rpc();
516     rpc.setUrl("/services/");
517     rpc.setServiceName("samba.ldb");
518     rpc.setCrossDomain(false);
519
520     /*
521      * All subtrees will use this root node's event listeners.  Create an
522      * event listener for an open while empty.
523      */
524     tree.addEventListener(
525         qx.constant.Event.TREEOPENWHILEEMPTY,
526         function(e)
527         {
528             var parent = e.getData();
529             var hierarchy = parent.getHierarchy(new Array());
530
531             parent.debug("Requesting children...");
532
533             // Strip off the root node
534             hierarchy.shift();
535
536             // Determine the children.  Differs depending on root or otherwise
537             var attributes;
538             var scope;
539             var baseDN;
540             
541             // If parent is the root...
542             if (parent == tree)
543             {
544                 // ... then we want the defaultNamingContext, ...
545                 attributes = "defaultNamingContext";
546
547                 // ... and we want only base scope
548                 scope = "base";
549
550                 // ... and an empty base DN
551                 baseDN = "";
552             }
553             else
554             {
555                 // otherwise, retrieve the DN,
556                 attributes = "dn";
557
558                 // ... and we want one level of scope
559                 scope = "one";
560
561                 // ... and base DN is the parent
562                 baseDN = hierarchy.reverse().join(",");
563             }
564
565             mycall = rpc.callAsync(function(result, ex, id)
566             {
567                 mycall = null;
568                 if (ex == null)
569                 {
570                     parent.debug("Children obtained.  Rendering...");
571                     addChildren(parent, result, attributes);
572                     parent.debug("Rendering complete.");
573                 }
574                 else
575                 {
576                     alert("Async(" + id + ") exception: " + ex);
577                 }
578             },
579             "search",                       // method
580             globals.dbHandle,               // database handle
581             "(objectclass=*)",              // search expression
582             baseDN,                         // baseDN
583             scope,                          // scope
584             [ attributes ]);                // attributes
585         });
586
587     /*
588      * All subtrees will use this root node's event listeners.  Create an
589      * event listener for selection changed, to populate attribute/value table
590      */
591     tree.getManager().addEventListener(
592         qx.constant.Event.CHANGESELECTION,
593         function(e)
594         {
595             var element = e.getData()[0];
596             var hierarchy = element.getHierarchy(new Array());
597
598             // Strip off the root node
599             hierarchy.shift();
600
601             // Determine the children.  Differs depending on root or otherwise
602             var attributes;
603             var scope;
604             var baseDN;
605             
606             // If element is the root...
607             if (element == tree)
608             {
609                 // ... then just clear out the attribute/value table.
610                 tableModel.setData([]);
611                 return;
612             }
613
614             // We want all attributes
615             attributes = "*";
616
617             // We want the attributes only for the current element
618             scope = "base";
619
620             // Base DN is the current element
621             baseDN = hierarchy.reverse().join(",");
622
623             mycall = rpc.callAsync(function(result, ex, id)
624             {
625                 mycall = null;
626                 if (ex == null)
627                 {
628                     // If we received an empty list, ...
629                     if (result == null)
630                     {
631                         // ... then just clear the attribute/value table.
632                         tableModel.setData([]);
633                         return;
634                     }
635
636                     // Start with an empty table dataset
637                     var rowData = [];
638
639                     // The result contains a single object: attributes
640                     var attributes = result[0];
641
642                     // For each attribute we received...
643                     for (var attr in attributes)
644                     {
645                         // If it's multi-valued (type is an array)...
646                         if (typeof(attributes[attr]) == "object")
647                         {
648                             // ... then add each value with same name
649                             var a = attributes[attr];
650                             for (var i = 0; i < a.length; i++)
651                             {
652                                 rowData.push([ attr, a[i] ]);
653                             }
654                         }
655                         else    // single-valued
656                         {
657                             // ... add its name and value to the table dataset
658                             rowData.push([ attr, attributes[attr] ]);
659                         }
660                     }
661
662                     // Add the dataset to the table
663                     tableModel.setData(rowData);
664                 }
665                 else
666                 {
667                     alert("Async(" + id + ") exception: " + ex);
668                 }
669             },
670             "search",                       // method
671             globals.dbHandle,               // database handle
672             "(objectclass=*)",              // search expression
673             baseDN,                         // baseDN
674             scope,                          // scope
675             [ attributes ]);                // attributes
676         });
677
678
679
680
681     // Add the tree to the page.
682     splitpane.addTop(tree);
683
684     // Create a simple table model
685     var tableModel = new qx.ui.table.SimpleTableModel();
686     tableModel.setColumns([ "Attribute", "Value" ]);
687
688     tableModel.setColumnEditable(0, false);
689     tableModel.setColumnEditable(1, true);
690
691     // Create a table
692     var table = new qx.ui.table.Table(tableModel);
693     with (table) {
694       set({
695               top: 10,
696               left: 0,
697               right: 0,
698               bottom: 10,
699               statusBarVisible: false,
700               columnVisibilityButtonVisible: false
701           });
702       setColumnWidth(0, 200);
703       setColumnWidth(1, 440);
704       setMetaColumnCounts([1, -1]);
705     };
706
707     // Add the table to the bottom portion of the splitpane
708     splitpane.addBottom(table);
709
710     // Add the first splitpane to the page
711     page.add(splitpane);
712 }
713
714 function buildPageSchema(page)
715 {
716     // Create a vertical splitpane for tree (top) and remainder (bottom)
717     var splitpane1 = new qx.ui.splitpane.VerticalSplitPane("1*", "2*");
718     splitpane1.setEdge(0);
719
720     // Create a tree row structure for the tree root
721     var trs = qx.ui.treefullcontrol.TreeRowStructure.getInstance().standard(globals.dbFile);
722
723     // Create the tree and set its characteristics
724     var tree = new qx.ui.treefullcontrol.Tree(trs);
725     tree.set(
726         {
727             backgroundColor: 255,
728             border: qx.renderer.border.BorderPresets.getInstance().inset,
729             overflow: "auto",
730             height: null,
731             top: 10,
732             left: 0,
733             right: 0,
734             bottom: 10
735         });
736
737     // Add the tree to the page.
738     splitpane1.addTop(tree);
739
740     // Create another vertical splitpane for table (top) and required/allowed
741     // attributes lists (bottom)
742     var splitpane2 = new qx.ui.splitpane.VerticalSplitPane("1*", "2*");
743     splitpane2.setEdge(0);
744
745     // Create a simple table model
746     var tableModel = new qx.ui.table.SimpleTableModel();
747     tableModel.setColumns([ "Attribute", "Value" ]);
748
749     // Add some garbage data to it
750     var attributeNames =
751         [
752             [ "Nickname" ],
753             [ "Hostname" ],
754             [ "Port" ],
755             [ "Connection caching" ],
756             [ "TLS" ],
757             [ "Client-side caching" ],
758             [ "Connections so far" ],
759             [ "LDAP protocol version" ],
760             [ "Vendor Name" ],
761             [ "Vendor Version" ],
762             [ "Support LDAP Version" ],
763             [ "Supported SASL Mechanisms" ],
764             [ "Junk 1" ],
765             [ "Junk 2" ],
766             [ "Junk 3" ]
767         ];
768             
769
770     var rowData = [];
771     for (var row = 0; row < attributeNames.length; row++)
772     {
773         rowData.push([ attributeNames[row], "" + (Math.random() * 10000) ]);
774     }
775     tableModel.setData(rowData);
776     tableModel.setColumnEditable(0, false);
777     tableModel.setColumnEditable(1, true);
778
779     // Create a table
780     var table = new qx.ui.table.Table(tableModel);
781     with (table) {
782       set({
783               top: 10,
784               left: 0,
785               right: 0,
786               bottom: 10,
787               statusBarVisible: false,
788               columnVisibilityButtonVisible: false
789           });
790       setColumnWidth(0, 200);
791       setColumnWidth(1, 440);
792       setMetaColumnCounts([1, -1]);
793     };
794
795     splitpane2.addTop(table);
796
797     // Create a horizontal splitpane for required attributes (left) and
798     // allowed attributes (right)
799     var splitpane3 = new qx.ui.splitpane.HorizontalSplitPane("1*", "1*");
800     splitpane3.setEdge(0);
801
802     // Create a vertical box layout for a label and list
803     var layout = new qx.ui.layout.VerticalBoxLayout();
804     layout.setWidth("100%");
805     layout.setHeight("100%");
806
807     // Create a label for the list of required attributes
808     var label = new qx.ui.basic.Atom("Required Attributes");
809
810     // Add the label to the vertical box layout
811     layout.add(label);
812
813     // Create a list for required attributes
814     var requiredAttributes = new qx.ui.form.List();
815     requiredAttributes.setWidth("100%");
816       
817     requiredAttributes.set(
818         {
819             top: 0,
820             left: 0,
821             width: "98%",
822             height: "90%",
823             overflow : "scrollY"
824         });
825       
826     var item;
827     for( var i=1; i<=35; i++ ) 
828     {
829         item = new qx.ui.form.ListItem("Item No " + i);
830         !(i % 9) && (item.setEnabled(false));
831         requiredAttributes.add(item);
832     };
833     
834     // Add the required attributes to the layout
835     layout.add(requiredAttributes);
836
837     // Add the vertical box layout to the left of the third splitpane
838     splitpane3.addLeft(layout);
839
840     // Create a vertical box layout for a label and list
841     layout = new qx.ui.layout.VerticalBoxLayout();
842     layout.set(
843         {
844             width: "100%",
845             height: "100%"
846         });
847
848     // Create a label for the list of allowed attributes
849     var label = new qx.ui.basic.Atom("Allowed Attributes");
850     label.setLeft(10);
851
852     // Add the label to the vertical box layout
853     layout.add(label);
854
855     // Create a list for allowed attributes
856     var allowedAttributes = new qx.ui.form.List();
857     allowedAttributes.setWidth("100%");
858       
859     allowedAttributes.set(
860         {
861             top: 0,
862             left: 10,
863             width: "98%",
864             height: "90%",
865             overflow : "scrollY"
866         });
867       
868     var item;
869     for( var i=1; i<=35; i++ ) 
870     {
871         item = new qx.ui.form.ListItem("Item No " + i);
872         !(i % 9) && (item.setEnabled(false));
873         allowedAttributes.add(item);
874     };
875     
876     // Add the allowed attributes to the layout
877     layout.add(allowedAttributes);
878
879     // Add the vertical box layout to the left of the third splitpane
880     splitpane3.addRight(layout);
881
882     // Add the third splitpane to the bottom of the second splitpane
883     splitpane2.addBottom(splitpane3);
884
885     // Add the second splitpane to the bottom of the first splitpane
886     splitpane1.addBottom(splitpane2);
887
888     // Add the first splitpane to the page
889     page.add(splitpane1);
890 }
891
892 qx.core.Init.getInstance().defineMain(
893     function()
894     {
895         // Enable JSON-RPC debugging
896         qx.Settings.setCustomOfClass("qx.io.Json", "enableDebug", true);
897
898         if (false)
899         {
900             // We'd like all table columns to do "live resize". Unfortunately,
901             // it's too slow.  Maybe someone wants to work on it...
902             var constructor = qx.OO.classes["qx.ui.table.TablePaneScroller"];
903             qx.Proto = constructor.prototype;
904             qx.OO.changeProperty(
905                 {
906                     name         : "liveResize",
907                     type         : qx.constant.Type.BOOLEAN,
908                     defaultValue : true
909                 });
910         }
911
912         // Set appearances for this application
913         setAppearances();
914
915         // Get the client document
916         var clientDocument = qx.ui.core.ClientDocument.getInstance();
917
918         // Create the toolbar and attach it to the client document
919         var toolBar = setupMenu(clientDocument);
920
921         // Create the tabs and their windows, and attach to client document
922         var tabView = setupTabs(clientDocument);
923
924         // Open a database connection.  Uses the dangerous sync request.
925         var rpc = new qx.io.remote.Rpc();
926         rpc.setUrl("/services/");
927         rpc.setServiceName("samba.ldb");
928         rpc.setCrossDomain(false);
929
930         try
931         {
932             // Database handle
933             globals.dbHandle = rpc.callSync("connect", globals.dbFile);
934         }
935         catch (ex)
936         {
937             alert("Sync exception: " + ex);
938         }
939     });
940 /*
941  * Local Variables:
942  * mode: c
943  * End:
944  */
945 </script>
946
947 </body>
948 </html>
949