r20514: implement idl for DsGetNT4ChangeLog() which transferres the meta data
[ira/wip.git] / webapps / swat / source / class / swat / module / ldbbrowse / Gui.js
1 /*
2  * Copyright:
3  *   (C) 2006 by Derrell Lipman
4  *       All rights reserved
5  *
6  * License:
7  *   LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/
8  */
9
10 /**
11  * Swat LDB Browser class graphical user interface
12  */
13 qx.OO.defineClass("swat.module.ldbbrowse.Gui", qx.core.Object,
14 function()
15 {
16   qx.core.Object.call(this);
17 });
18
19
20 /**
21  * Build the raw graphical user interface.
22  */
23 qx.Proto.buildGui = function(module)
24 {
25   var o;
26   var fsm = module.fsm;
27
28   // We need a horizontal box layout for the database name
29   var hlayout = new qx.ui.layout.HorizontalBoxLayout();
30   hlayout.set({
31                   top: 20,
32                   left: 20,
33                   right: 20,
34                   height: 30
35               });
36
37   // Create a label for the database name
38   o = new qx.ui.basic.Atom("Database:");
39   o.setWidth(100);
40   o.setHorizontalChildrenAlign("right");
41
42   // Add the label to the horizontal layout
43   hlayout.add(o);
44
45   // Create a combo box for for the database name
46   o = new qx.ui.form.ComboBox();
47   o.getField().setWidth("100%");
48   o.setEditable(false);
49
50   // Add our global database name (the only option, for now)
51   var item = new qx.ui.form.ListItem(module.dbFile);
52   o.add(item);
53   
54   // We want to be notified if the selection changes
55   o.addEventListener("changeSelection", fsm.eventListener, fsm);
56
57   // Save the database name object so we can react to changes
58   fsm.addObject("dbName", o);
59     
60   // Add the combo box to the horizontal layout
61   hlayout.add(o);
62
63   // Add the database name selection to the canvas
64   module.canvas.add(hlayout);
65
66   // Create and position the tabview
67   var tabView_ = new qx.ui.pageview.tabview.TabView;
68   tabView_.set({
69                    top: 60,
70                    left: 20,
71                    right: 20,
72                    bottom: 20
73                });
74
75   // Create each of the tabs
76   var tabView_Search =
77   new qx.ui.pageview.tabview.Button("Search");
78   var tabView_Browse =
79   new qx.ui.pageview.tabview.Button("Browse");
80
81   // Specify the initially-selected tab
82   tabView_Search.setChecked(true);
83
84   // Add each of the tabs to the tabview
85   tabView_.getBar().add(tabView_Search, tabView_Browse);
86
87   // Create the pages to display when each tab is selected
88   var tabViewPage_Search =
89   new qx.ui.pageview.tabview.Page(tabView_Search);
90   var tabViewPage_Browse =
91   new qx.ui.pageview.tabview.Page(tabView_Browse);
92
93   // Build the search page
94   this._buildPageSearch(module, tabViewPage_Search);
95
96   // Build the browse page
97   this._buildPageBrowse(module, tabViewPage_Browse);
98
99   // Add the pages to the tabview
100   tabView_.getPane().add(tabViewPage_Search, tabViewPage_Browse);
101
102   // Add the tabview to our canvas
103   module.canvas.add(tabView_);
104 };
105
106
107 /**
108  * Populate the graphical user interface with the specified data
109  *
110  * @param module {swat.module.Module}
111  *   The module descriptor for the module.
112  *
113  * @result {Object}
114  *   The result returned by SAMBA to our request.  We display the data
115  *   provided by this result.
116  */
117 qx.Proto.displayData = function(module, request)
118 {
119   var gui = module.gui;
120   var fsm = module.fsm;
121   var result = request.getUserData("result")
122   var requestType = request.getUserData("requestType");
123
124   // Did the request fail?
125   if (result.type == "failed")
126   {
127     // Yup.  We're not going to do anything particularly elegant...
128     alert("Async(" + result.id + ") exception: " + result.data);
129     return;
130   }
131
132   // Dispatch to the appropriate handler, depending on the request type
133   switch(requestType)
134   {
135   case "find":
136     this._displayFindResults(module, request);
137     break;
138     
139   case "tree_open":
140     this._displayTreeOpenResults(module, request);
141     break;
142
143   case "tree_selection_changed":
144     this._displayTreeSelectionChangedResults(module, request);
145     break;
146
147   case "database_name_changed":
148     this._clearAllFields(module, request);
149     break;
150
151   default:
152     throw new Error("Unexpected request type: " + requestType);
153   }
154
155   // Force flushing of pending DOM updates.  This is actually a
156   // work-around for a bug.  Without this, occasionally, updates to the
157   // gui aren't displayed until some 'action' takes place, e.g. key
158   // press or mouse movement.
159   qx.ui.core.Widget.flushGlobalQueues();
160 };
161
162
163 qx.Proto._setAppearances = function()
164 {
165     // Modify the default appearance of a ComboBox for use in Search tab:
166     //   use more of the available width.
167     //
168     // If we had multiple uses, we'd create a new appearance which didn't
169     // contain a width.  That way, we'd be able to assign a specific width to
170     // each ComboBox instance.  Since we don't have multiple of them, we can
171     // just modify this default appearance.
172     //
173     // See http://qooxdoo.org/documentation/user_manual/appearance for an
174     // explanation of what's going on here.  The missing significant point in
175     // the discussion is that in the current qooxdoo appearance
176     // implementation, it's not possible to override a specific widget's
177     // appearance with explicit settings just for that widget (stupid!).  I
178     // expect that to change in a future version.
179     var appMgr = qx.manager.object.AppearanceManager.getInstance();
180     var theme = appMgr.getAppearanceTheme();
181     var appearance = theme._appearances["combo-box"];
182     if (! appearance)
183     {
184         return;
185     }
186     var oldInitial = appearance.initial;
187     appearance.initial = function(vTheme)
188     {
189         var res = oldInitial ? oldInitial.apply(this, arguments) : {};
190         res.width = "80%";
191         return res;
192     }
193 };
194
195
196 qx.Proto._buildPageSearch = function(module, page)
197 {
198   var fsm = module.fsm;
199
200   // We need a vertical box layout for the various input fields
201   var vlayout = new qx.ui.layout.VerticalBoxLayout();
202   vlayout.setWidth("100%");
203
204   // We need a horizontal box layout for the search combo box and its label
205   var hlayout = new qx.ui.layout.HorizontalBoxLayout();
206   hlayout.setWidth("100%");
207   hlayout.setHeight(25);
208
209   // Create a label for the list of required attributes
210   var label = new qx.ui.basic.Atom("Search Expression:");
211   label.setWidth(100);
212   label.setHorizontalChildrenAlign("right");
213
214   // Add the label to the horizontal layout
215   hlayout.add(label);
216
217   // Create a combo box for entry of the search expression
218   var search = new qx.ui.form.ComboBox();
219   search.getField().setWidth("100%");
220   search.setEditable(true);
221   fsm.addObject("searchExpr", search);
222     
223   // Add the combo box to the horizontal layout
224   hlayout.add(search);
225
226   // Add the horizontal layout to the vertical layout
227   vlayout.add(hlayout);
228
229   // We need a horizontal box layout for the base combo box and its label
230   hlayout = new qx.ui.layout.HorizontalBoxLayout();
231   hlayout.setWidth("100%");
232   hlayout.setHeight(25);
233
234   // Create a label for the list of required attributes
235   var label = new qx.ui.basic.Atom("Base:");
236   label.setWidth(100);
237   label.setHorizontalChildrenAlign("right");
238
239   // Add the label to the horizontal layout
240   hlayout.add(label);
241
242   // Create a combo box for entry of the search expression
243   var base = new qx.ui.form.ComboBox();
244   base.getField().setWidth("100%");
245   base.setEditable(true);
246   fsm.addObject("baseDN", base);
247     
248   // Add the combo box to the horizontal layout
249   hlayout.add(base);
250
251   // Add the horizontal layout to the vertical layout
252   vlayout.add(hlayout);
253
254   // We need a horizontal box layout for scope radio buttons
255   hlayout = new qx.ui.layout.HorizontalBoxLayout();
256   hlayout.setWidth("100%");
257   hlayout.setHeight(25);
258
259   // Create a label for the list of required attributes
260   var label = new qx.ui.basic.Atom("Scope:");
261   label.setWidth(100);
262   label.setHorizontalChildrenAlign("right");
263
264   // Add the label to the horizontal layout
265   hlayout.add(label);
266
267   // Create a radio button for each scope
268   var rbDefault = new qx.ui.form.RadioButton("Default",   "default");
269   var rbBase    = new qx.ui.form.RadioButton("Base",      "base");
270   var rbOne     = new qx.ui.form.RadioButton("One Level", "one");
271   var rbSubtree = new qx.ui.form.RadioButton("Subtree",   "subtree");
272
273   // Use a default of "Default"
274   rbBase.setChecked(true);
275
276   // Add the radio buttons to the horizontal layout
277   hlayout.add(rbDefault, rbBase, rbOne, rbSubtree);
278
279   // Group the radio buttons so only one in the group may be selected
280   var scope = new qx.manager.selection.RadioManager("scope",
281                                                     [
282                                                         rbDefault,
283                                                         rbBase,
284                                                         rbOne,
285                                                         rbSubtree
286                                                     ]);
287   fsm.addObject("scope", scope);
288     
289   // Right-justify the 'Find' button
290   var spacer = new qx.ui.basic.HorizontalSpacer;
291   hlayout.add(spacer);
292
293   // Create the 'Find' button
294   var find = new qx.ui.form.Button('Find');
295   find.setWidth(100);
296   find.addEventListener("execute", fsm.eventListener, fsm);
297
298   // We'll be receiving events on the find object, so save its friendly name
299   fsm.addObject("find", find, "swat.module.fsmUtils.disable_during_rpc");
300
301   hlayout.add(find);
302
303   // Add the Find button line to the vertical layout
304   vlayout.add(hlayout);
305
306   // Add the horizontal box layout to the page
307   page.add(vlayout);
308
309   // Create a simple table model
310   var tableModel = new qx.ui.table.SimpleTableModel();
311   tableModel.setColumns([ "Distinguished Name", "Attribute", "Value" ]);
312
313   tableModel.setColumnEditable(0, false);
314   tableModel.setColumnEditable(1, false);
315   tableModel.setColumnEditable(2, false);
316   fsm.addObject("tableModel:search", tableModel);
317
318   // Create a table
319   var table = new qx.ui.table.Table(tableModel);
320   table.set({
321                 top: 80,
322                 left: 0,
323                 right: 0,
324                 bottom: 10,
325                 statusBarVisible: false,
326                 columnVisibilityButtonVisible: false
327             });
328   table.setColumnWidth(0, 300);
329   table.setColumnWidth(1, 180);
330   table.setColumnWidth(2, 240);
331   table.setMetaColumnCounts([ 1, -1 ]);// h-scroll attribute and value together
332   fsm.addObject("table:search", table);
333
334   page.add(table);
335 };
336
337 qx.Proto._buildPageBrowse = function(module, page)
338 {
339   var fsm = module.fsm;
340
341   // Create a vertical splitpane for tree (top) and table (bottom)
342   var splitpane = new qx.ui.splitpane.VerticalSplitPane("1*", "2*");
343   splitpane.setEdge(0);
344
345   // Create a tree row structure for the tree root
346   var trsInstance = qx.ui.treefullcontrol.TreeRowStructure.getInstance();
347   var trs = trsInstance.standard(module.dbFile);
348
349   // Create the tree and set its characteristics
350   var tree = new qx.ui.treefullcontrol.Tree(trs);
351   tree.set({
352                backgroundColor: 255,
353                border: qx.renderer.border.BorderPresets.getInstance().inset,
354                overflow: "auto",
355                height: null,
356                top: 10,
357                left: 0,
358                right: 0,
359                bottom: 10,
360                open: false,
361                alwaysShowPlusMinusSymbol: true
362            });
363
364   // All subtrees will use this root node's event listeners.  Create an event
365   // listener for an open while empty.
366   tree.addEventListener("treeOpenWhileEmpty", fsm.eventListener, fsm);
367
368   // All subtrees will use this root node's event listeners.  Create an event
369   // listener for selection changed, to populate attribute/value table
370   tree.getManager().addEventListener("changeSelection",
371                                      fsm.eventListener,
372                                      fsm);
373
374   // We'll be receiving events on the tree object, so save its friendly name
375   fsm.addObject("tree", tree);
376   fsm.addObject("tree:manager", tree.getManager());
377
378   // Add the tree to the page.
379   splitpane.addTop(tree);
380
381   // Create a simple table model
382   var tableModel = new qx.ui.table.SimpleTableModel();
383   tableModel.setColumns([ "Attribute", "Value" ]);
384
385   tableModel.setColumnEditable(0, false);
386   tableModel.setColumnEditable(1, false);
387   fsm.addObject("tableModel:browse", tableModel);
388
389   // Create a table
390   var table = new qx.ui.table.Table(tableModel);
391   table.set({
392                 top: 10,
393                 left: 0,
394                 right: 0,
395                 bottom: 10,
396                 statusBarVisible: false,
397                 columnVisibilityButtonVisible: false
398             });
399   table.setColumnWidth(0, 200);
400   table.setColumnWidth(1, 440);
401   table.setMetaColumnCounts([1, -1]);
402   fsm.addObject("table:browse", table);
403
404   // Add the table to the bottom portion of the splitpane
405   splitpane.addBottom(table);
406
407   // Add the first splitpane to the page
408   page.add(splitpane);
409 };
410
411
412 qx.Proto._displayFindResults = function(module, request)
413 {
414   var rowData = [];
415   var fsm = module.fsm;
416
417   // Track the maximum length of the attribute values
418   var maxLen = 0;
419
420   // Obtain the result object
421   result = request.getUserData("result").data;
422
423   if (result && result["length"])
424   {
425     len = result["length"];
426     for (var i = 0; i < result["length"]; i++)
427     {
428       var o = result[i];
429       if (typeof(o) != "object")
430       {
431         alert("Found unexpected result, type " +
432               typeof(o) +
433               ", " +
434               o +
435               "\n");
436         continue;
437       }
438       for (var field in o)
439       {
440         // skip dn and distinguishedName fields;
441         // they're shown in each row anyway.
442         if (field == "dn" || field == "distinguishedName")
443         {
444           continue;
445         }
446
447         // If it's multi-valued (type is an array)...
448         if (typeof(o[field]) == "object")
449         {
450           // ... then add each value with same name
451           var a = o[field];
452           for (var i = 0; i < a.length; i++)
453           {
454             if (a[i].length > maxLen)
455             {
456               maxLen = a[i].length;
457             }
458             rowData.push( [
459                             o["dn"],
460                             field,
461                             a[i]
462                             ] );
463           }
464         }
465         else    // single-valued
466         {
467           // ... add its name and value to the table
468           // dataset
469           if (o[field].length > maxLen)
470           {
471             maxLen = o[field].length;
472           }
473           rowData.push( [
474                           o["dn"],
475                           field,
476                           o[field]
477                           ] );
478         }
479       }
480
481       // Obtain the table and tableModel objects
482       var table = fsm.getObject("table:search");
483       var tableModel = fsm.getObject("tableModel:search");
484
485       // Adjust the width of the value column based on
486       // maxLen
487       table.setColumnWidth(2, maxLen * 7);
488
489       // Tell the table to use the new data
490       tableModel.setData(rowData);
491     }
492   }
493   else
494   {
495     alert("No rows returned.");
496   }
497 };
498
499
500 qx.Proto._displayTreeOpenResults = function(module, request)
501 {
502   var t;
503   var trs;
504   var child;
505
506   // Obtain the result object
507   var result = request.getUserData("result").data;
508
509   // We also need some of the original parameters passed to the request
510   var parent = request.getUserData("parent");
511   var attributes = request.getUserData("attributes");
512
513   // Any children?
514   if (! result || result["length"] == 0)
515   {
516     // Nope.  Allow parent's expand/contract button to be removed
517     parent.setAlwaysShowPlusMinusSymbol(false);
518     return;
519   }
520
521   for (var i = 0; i < result.length; i++)
522   {
523     var name;
524
525     child = result[i];
526
527     // Determine name for new tree row.  If first level, use entire
528     // DN.  Otherwise, strip off first additional component.
529     if (attributes == "defaultNamingContext")
530     {
531       name = child["defaultNamingContext"];
532     }
533     else
534     {
535       name = child["dn"].split(",")[0];
536     }
537
538     // Build a standard tree row
539     trs = qx.ui.treefullcontrol.TreeRowStructure.getInstance().standard(name);
540
541     // This row is a "folder" (it can have children)
542     t = new qx.ui.treefullcontrol.TreeFolder(trs);
543     t.setAlwaysShowPlusMinusSymbol(true);
544
545     // Add this row to its parent
546     parent.add(t);
547   }
548 };
549
550
551 qx.Proto._displayTreeSelectionChangedResults = function(module, request)
552 {
553   var fsm = module.fsm;
554
555   // Obtain the result object
556   var result = request.getUserData("result").data;
557
558   // If we received an empty list, ...
559   if (result == null)
560   {
561     // ... then just clear the attribute/value table.
562     tableModel.setData([ ]);
563     return;
564   }
565
566   // Start with an empty table dataset
567   var rowData = [ ];
568
569   // The result contains a single object: attributes
570   var attributes = result[0];
571
572   // Track the maximum length of the attribute values
573   var maxLen = 0;
574
575   // For each attribute we received...
576   for (var attr in attributes)
577   {
578     // If it's multi-valued (type is an array)...
579     if (typeof(attributes[attr]) == "object")
580     {
581       // ... then add each value with same name
582       var a = attributes[attr];
583       for (var i = 0; i < a.length; i++)
584       {
585         if (a[i].length > maxLen)
586         {
587           maxLen = a[i].length;
588         }
589         rowData.push([ attr, a[i] ]);
590       }
591     }
592     else    // single-valued
593     {
594       // ... add its name and value to the table dataset
595       if (attributes[attr].length > maxLen)
596       {
597         maxLen = attributes[attr].length;
598       }
599       rowData.push([ attr, attributes[attr] ]);
600     }
601   }
602
603   // Obtain the table and tableModel objects
604   var table = fsm.getObject("table:browse");
605   var tableModel = fsm.getObject("tableModel:browse");
606
607   // Adjust the width of the value column based on maxLen
608   table.setColumnWidth(1, maxLen * 7);
609
610   // Add the dataset to the table
611   tableModel.setData(rowData);
612 };
613
614
615 qx.Proto._clearAllFields = function(module, request)
616 {
617   // Obtain the result object
618   var result = request.getUserData("result").data;
619
620   // Retrieve the database handle
621   module.dbHandle = result;
622
623   // In the future, when we support more than one database, we'll want to
624   // clear all fields here.  For now, there's no need.
625 };
626
627
628
629 /**
630  * Singleton Instance Getter
631  */
632 qx.Class.getInstance = qx.util.Return.returnInstance;