fcdbe1c0589f59ec6da0f1ec0e2602f59bce459f
[samba.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_Browse =
77   new qx.ui.pageview.tabview.Button("Browse");
78   var tabView_Search =
79   new qx.ui.pageview.tabview.Button("Search");
80
81   // Specify the initially-selected tab
82   tabView_Browse.setChecked(true);
83
84   // Add each of the tabs to the tabview
85   tabView_.getBar().add(tabView_Browse, tabView_Search);
86
87   // Create the pages to display when each tab is selected
88   var tabViewPage_Browse =
89   new qx.ui.pageview.tabview.Page(tabView_Browse);
90   var tabViewPage_Search =
91   new qx.ui.pageview.tabview.Page(tabView_Search);
92
93   // Build the browse page
94   this._buildPageBrowse(module, tabViewPage_Browse);
95
96   // Build the search page
97   this._buildPageSearch(module, tabViewPage_Search);
98
99   // Add the pages to the tabview
100   tabView_.getPane().add(tabViewPage_Browse, tabViewPage_Search);
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.main.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, rpcRequest)
118 {
119   var gui = module.gui;
120   var fsm = module.fsm;
121   var result = rpcRequest.getUserData("result")
122   var requestType = rpcRequest.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 "search":
136     this._displaySearchResults(module, rpcRequest);
137     break;
138     
139   case "tree_open":
140     this._displayTreeOpenResults(module, rpcRequest);
141     break;
142
143   case "tree_selection_changed":
144     this._displayTreeSelectionChangedResults(module, rpcRequest);
145     break;
146
147   case "database_name_changed":
148     this._clearAllFields(module, rpcRequest);
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.set({
203                overflow: "hidden",
204                height: 120,
205                top: 10,
206                left: 0,
207                right: 0,
208                bottom: 10
209            });
210
211   // Create a label for the list of required attributes
212   var label = new qx.ui.basic.Atom("Search Expression");
213   label.setHorizontalChildrenAlign("left");
214
215   // Add the label to the horizontal layout
216   vlayout.add(label);
217
218   // Create a combo box for entry of the search expression
219   var filter = new qx.ui.form.TextField();
220   filter.set({ width:300 });
221   fsm.addObject("searchExpr", filter);
222     
223   // Add the combo box to the horizontal layout
224   vlayout.add(filter);
225
226   // Create a label for the list of required attributes
227   var label = new qx.ui.basic.Atom("Base");
228   label.setHorizontalChildrenAlign("left");
229
230   // Add the label to the horizontal layout
231   vlayout.add(label);
232
233   // Create a combo box for entry of the search expression
234   var base = new qx.ui.form.TextField();
235   base.set({ width:300 });
236   fsm.addObject("baseDN", base);
237     
238   // Add the combo box to the horizontal layout
239   vlayout.add(base);
240
241   // Create a label for the list of required attributes
242   var label = new qx.ui.basic.Atom("Scope");
243   label.setWidth(100);
244   label.setHorizontalChildrenAlign("left");
245
246   // Add the label to the scope vertical layout
247   vlayout.add(label);
248
249   // Use a horizontal box layout to keep the search button aligned
250   var hlayout = new qx.ui.layout.HorizontalBoxLayout();
251   hlayout.setWidth(300);
252   hlayout.setHeight(30);
253
254   var cbScope = new qx.ui.form.ComboBoxEx();
255   cbScope.setSelection([ ["subtree", "Subtree"], ["one", "One Level"], ["base", "Base"]]);
256   cbScope.setSelectedIndex(0);
257
258   fsm.addObject("scope", cbScope);
259
260   hlayout.add(cbScope);
261
262   // Add a sapcer
263   hlayout.add(new qx.ui.basic.HorizontalSpacer());
264
265   // Create the 'Search' button
266   var search = new qx.ui.form.Button('Search');
267   search.setWidth(100);
268   search.addEventListener("execute", fsm.eventListener, fsm);
269
270   // We'll be receiving events on the search object, so save its friendly name
271   fsm.addObject("search", search, "swat.main.fsmUtils.disable_during_rpc");
272
273   // Add the search button to the vertical layout
274   hlayout.add(search);
275
276   vlayout.add(hlayout);
277
278   // Add the vlayout to the page
279   page.add(vlayout);
280
281   var ldifView = new swat.module.ldbbrowse.ldifViewer();
282   ldifView.set({
283                top: 130,
284                left: 10,
285                right: 10,
286                bottom: 10
287            });
288
289   fsm.addObject("ldifView", ldifView);
290
291   // Add the output area to the page
292   page.add(ldifView);
293 };
294
295 qx.Proto._buildPageBrowse = function(module, page)
296 {
297   var fsm = module.fsm;
298
299   // Create a horizontal splitpane for tree (left) and table (right)
300   var splitpane = new qx.ui.splitpane.HorizontalSplitPane("1*", "2*");
301   splitpane.setEdge(0);
302
303   // Create a tree row structure for the tree root
304   var trsInstance = qx.ui.treefullcontrol.TreeRowStructure.getInstance();
305   var trs = trsInstance.standard(module.dbFile);
306
307   // Create the tree and set its characteristics
308   var tree = new qx.ui.treefullcontrol.Tree(trs);
309   tree.set({
310                backgroundColor: 255,
311                border: qx.renderer.border.BorderPresets.getInstance().inset,
312                overflow: "auto",
313                height: null,
314                top: 10,
315                left: 0,
316                right: 0,
317                bottom: 10,
318                open: false,
319                alwaysShowPlusMinusSymbol: true
320            });
321
322   // All subtrees will use this root node's event listeners.  Create an event
323   // listener for an open while empty.
324   tree.addEventListener("treeOpenWhileEmpty", fsm.eventListener, fsm);
325
326   // All subtrees will use this root node's event listeners.  Create an event
327   // listener for selection changed, to populate attribute/value table
328   tree.getManager().addEventListener("changeSelection",
329                                      fsm.eventListener,
330                                      fsm);
331
332   // We'll be receiving events on the tree object, so save its friendly name
333   fsm.addObject("tree", tree);
334   fsm.addObject("tree:manager", tree.getManager());
335
336   // Add the tree to the page.
337   splitpane.addLeft(tree);
338
339   // Create a simple table model
340   var tableModel = new qx.ui.table.SimpleTableModel();
341   tableModel.setColumns([ "Attribute", "Value" ]);
342
343   tableModel.setColumnEditable(0, false);
344   tableModel.setColumnEditable(1, false);
345   fsm.addObject("tableModel:browse", tableModel);
346
347   // Create a table
348   var table = new qx.ui.table.Table(tableModel);
349   table.set({
350                 top: 10,
351                 left: 0,
352                 right: 0,
353                 bottom: 10,
354                 statusBarVisible: false,
355                 columnVisibilityButtonVisible: false
356             });
357   table.setColumnWidth(0, 180);
358   table.setColumnWidth(1, 320);
359   table.setMetaColumnCounts([1, -1]);
360   fsm.addObject("table:browse", table);
361
362   // Add the table to the bottom portion of the splitpane
363   splitpane.addRight(table);
364
365   // Add the first splitpane to the page
366   page.add(splitpane);
367 };
368
369
370 qx.Proto._displaySearchResults = function(module, rpcRequest)
371 {
372   var fsm = module.fsm;
373
374   // Obtain the table and tableModel objects
375   var ldifView = fsm.getObject("ldifView");
376
377   ldifView.reset();
378
379   // Obtain the result object
380   result = rpcRequest.getUserData("result").data;
381
382   if (result && result["length"])
383   {
384     len = result["length"];
385     for (var i = 0; i < result["length"]; i++)
386     {
387       var obj = result[i];
388       if (typeof(obj) != "object")
389       {
390         alert("Found unexpected result, type " +
391               typeof(obj) +
392               ", " +
393               obj +
394               "\n");
395         continue;
396       }
397       ldifView.appendObject(obj);
398     }
399   }
400   else
401   {
402     alert("No results.");
403   }
404 };
405
406
407 qx.Proto._displayTreeOpenResults = function(module, rpcRequest)
408 {
409   var t;
410   var trs;
411   var child;
412
413   // Obtain the result object
414   var result = rpcRequest.getUserData("result").data;
415
416   // We also need some of the original parameters passed to the request
417   var parent = rpcRequest.getUserData("parent");
418   var attributes = rpcRequest.getUserData("attributes");
419
420   // Any children?
421   if (! result || result["length"] == 0)
422   {
423     // Nope.  Allow parent's expand/contract button to be removed
424     parent.setAlwaysShowPlusMinusSymbol(false);
425     return;
426   }
427
428   for (var i = 0; i < result.length; i++)
429   {
430     var name;
431
432     child = result[i];
433
434     // Determine name for new tree row.  If first level, use entire
435     // DN.  Otherwise, strip off first additional component.
436     if (attributes == "defaultNamingContext")
437     {
438       name = child["defaultNamingContext"];
439     }
440     else
441     {
442       //FIXME: must check for escapes, as ',' is also a valid value, not only a separator
443       name = child["dn"].split(",")[0];
444     }
445
446     // Build a standard tree row
447     trs = qx.ui.treefullcontrol.TreeRowStructure.getInstance().standard(name);
448
449     // This row is a "folder" (it can have children)
450     t = new qx.ui.treefullcontrol.TreeFolder(trs);
451     t.setAlwaysShowPlusMinusSymbol(true);
452
453     // Add this row to its parent
454     parent.add(t);
455   }
456 };
457
458
459 qx.Proto._displayTreeSelectionChangedResults = function(module, rpcRequest)
460 {
461   var fsm = module.fsm;
462
463   // Obtain the result object
464   var result = rpcRequest.getUserData("result").data;
465
466   // If we received an empty list, ...
467   if (result == null)
468   {
469     // ... then just clear the attribute/value table.
470     tableModel.setData([ ]);
471     return;
472   }
473
474   // Start with an empty table dataset
475   var rowData = [ ];
476
477   // The result contains a single object: attributes
478   var attributes = result[0];
479
480   // Track the maximum length of the attribute values
481   var maxLen = 0;
482
483   // For each attribute we received...
484   for (var attr in attributes)
485   {
486     // If it's multi-valued (type is an array)...
487     if (typeof(attributes[attr]) == "object")
488     {
489       // ... then add each value with same name
490       var a = attributes[attr];
491       for (var i = 0; i < a.length; i++)
492       {
493         if (a[i].length > maxLen)
494         {
495           maxLen = a[i].length;
496         }
497         rowData.push([ attr, a[i] ]);
498       }
499     }
500     else    // single-valued
501     {
502       // ... add its name and value to the table dataset
503       if (attributes[attr].length > maxLen)
504       {
505         maxLen = attributes[attr].length;
506       }
507       rowData.push([ attr, attributes[attr] ]);
508     }
509   }
510
511   // Obtain the table and tableModel objects
512   var table = fsm.getObject("table:browse");
513   var tableModel = fsm.getObject("tableModel:browse");
514
515   // Adjust the width of the value column based on maxLen
516   table.setColumnWidth(1, maxLen * 7);
517
518   // Add the dataset to the table
519   tableModel.setData(rowData);
520 };
521
522
523 qx.Proto._clearAllFields = function(module, rpcRequest)
524 {
525   // Obtain the result object
526   var result = rpcRequest.getUserData("result").data;
527
528   // Retrieve the database handle
529   module.dbHandle = result;
530
531   // In the future, when we support more than one database, we'll want to
532   // clear all fields here.  For now, there's no need.
533 };
534
535
536
537 /**
538  * Singleton Instance Getter
539  */
540 qx.Class.getInstance = qx.util.Return.returnInstance;