3 * (C) 2006 by Derrell Lipman
7 * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/
11 * Swat LDB Browser class graphical user interface
13 qx.OO.defineClass("swat.module.ldbbrowse.Gui", qx.core.Object,
16 qx.core.Object.call(this);
21 * Build the raw graphical user interface.
23 qx.Proto.buildGui = function(module)
28 // We need a horizontal box layout for the database name
29 var hlayout = new qx.ui.layout.HorizontalBoxLayout();
37 // Create a label for the database name
38 o = new qx.ui.basic.Atom("Database:");
40 o.setHorizontalChildrenAlign("right");
42 // Add the label to the horizontal layout
45 // Create a combo box for for the database name
46 o = new qx.ui.form.ComboBox();
47 o.getField().setWidth("100%");
50 // Add our global database name (the only option, for now)
51 var item = new qx.ui.form.ListItem(module.dbFile);
54 // We want to be notified if the selection changes
55 o.addEventListener("changeSelection", fsm.eventListener, fsm);
57 // Save the database name object so we can react to changes
58 fsm.addObject("dbName", o);
60 // Add the combo box to the horizontal layout
63 // Add the database name selection to the canvas
64 module.canvas.add(hlayout);
66 // Create and position the tabview
67 var tabView_ = new qx.ui.pageview.tabview.TabView;
75 // Create each of the tabs
77 new qx.ui.pageview.tabview.Button("Browse");
79 new qx.ui.pageview.tabview.Button("Search");
81 // Specify the initially-selected tab
82 tabView_Browse.setChecked(true);
84 // Add each of the tabs to the tabview
85 tabView_.getBar().add(tabView_Browse, tabView_Search);
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);
93 // Build the browse page
94 this._buildPageBrowse(module, tabViewPage_Browse);
96 // Build the search page
97 this._buildPageSearch(module, tabViewPage_Search);
99 // Add the pages to the tabview
100 tabView_.getPane().add(tabViewPage_Browse, tabViewPage_Search);
102 // Add the tabview to our canvas
103 module.canvas.add(tabView_);
108 * Populate the graphical user interface with the specified data
110 * @param module {swat.main.Module}
111 * The module descriptor for the module.
114 * The result returned by SAMBA to our request. We display the data
115 * provided by this result.
117 qx.Proto.displayData = function(module, rpcRequest)
119 var gui = module.gui;
120 var fsm = module.fsm;
121 var result = rpcRequest.getUserData("result")
122 var requestType = rpcRequest.getUserData("requestType");
124 // Did the request fail?
125 if (result.type == "failed")
127 // Yup. We're not going to do anything particularly elegant...
128 alert("Async(" + result.id + ") exception: " + result.data);
132 // Dispatch to the appropriate handler, depending on the request type
136 this._displaySearchResults(module, rpcRequest);
140 this._displayTreeOpenResults(module, rpcRequest);
143 case "tree_selection_changed":
144 this._displayTreeSelectionChangedResults(module, rpcRequest);
147 case "database_name_changed":
148 this._clearAllFields(module, rpcRequest);
152 throw new Error("Unexpected request type: " + requestType);
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();
163 qx.Proto._setAppearances = function()
165 // Modify the default appearance of a ComboBox for use in Search tab:
166 // use more of the available width.
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.
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"];
186 var oldInitial = appearance.initial;
187 appearance.initial = function(vTheme)
189 var res = oldInitial ? oldInitial.apply(this, arguments) : {};
196 qx.Proto._buildPageSearch = function(module, page)
198 var fsm = module.fsm;
200 // We need a vertical box layout for the various input fields
201 var vlayout = new qx.ui.layout.VerticalBoxLayout();
211 // Create a label for the list of required attributes
212 var label = new qx.ui.basic.Atom("Search Expression");
213 label.setHorizontalChildrenAlign("left");
215 // Add the label to the horizontal layout
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);
223 // Add the combo box to the horizontal layout
226 // Create a label for the list of required attributes
227 var label = new qx.ui.basic.Atom("Base");
228 label.setHorizontalChildrenAlign("left");
230 // Add the label to the horizontal layout
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);
238 // Add the combo box to the horizontal layout
241 // Create a label for the list of required attributes
242 var label = new qx.ui.basic.Atom("Scope");
244 label.setHorizontalChildrenAlign("left");
246 // Add the label to the scope vertical layout
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);
254 var cbScope = new qx.ui.form.ComboBoxEx();
255 cbScope.setSelection([ ["subtree", "Subtree"], ["one", "One Level"], ["base", "Base"]]);
256 cbScope.setSelectedIndex(0);
258 fsm.addObject("scope", cbScope);
260 hlayout.add(cbScope);
263 hlayout.add(new qx.ui.basic.HorizontalSpacer());
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);
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");
273 // Add the search button to the vertical layout
276 vlayout.add(hlayout);
278 // Add the vlayout to the page
281 var ldifView = new swat.module.ldbbrowse.ldifViewer();
289 fsm.addObject("ldifView", ldifView);
291 // Add the output area to the page
295 qx.Proto._buildPageBrowse = function(module, page)
297 var fsm = module.fsm;
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);
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);
307 // Create the tree and set its characteristics
308 var tree = new qx.ui.treefullcontrol.Tree(trs);
310 backgroundColor: 255,
311 border: qx.renderer.border.BorderPresets.getInstance().inset,
319 alwaysShowPlusMinusSymbol: true
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);
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",
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());
336 // Add the tree to the page.
337 splitpane.addLeft(tree);
339 // Create a simple table model
340 var tableModel = new qx.ui.table.SimpleTableModel();
341 tableModel.setColumns([ "Attribute", "Value" ]);
343 tableModel.setColumnEditable(0, false);
344 tableModel.setColumnEditable(1, false);
345 fsm.addObject("tableModel:browse", tableModel);
348 var table = new qx.ui.table.Table(tableModel);
354 statusBarVisible: false,
355 columnVisibilityButtonVisible: false
357 table.setColumnWidth(0, 180);
358 table.setColumnWidth(1, 320);
359 table.setMetaColumnCounts([1, -1]);
360 fsm.addObject("table:browse", table);
362 // Add the table to the bottom portion of the splitpane
363 splitpane.addRight(table);
365 // Add the first splitpane to the page
370 qx.Proto._displaySearchResults = function(module, rpcRequest)
372 var fsm = module.fsm;
374 // Obtain the table and tableModel objects
375 var ldifView = fsm.getObject("ldifView");
379 // Obtain the result object
380 result = rpcRequest.getUserData("result").data;
382 if (result && result["length"])
384 len = result["length"];
385 for (var i = 0; i < result["length"]; i++)
388 if (typeof(obj) != "object")
390 alert("Found unexpected result, type " +
397 ldifView.appendObject(obj);
402 alert("No results.");
407 qx.Proto._displayTreeOpenResults = function(module, rpcRequest)
413 // Obtain the result object
414 var result = rpcRequest.getUserData("result").data;
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");
421 if (! result || result["length"] == 0)
423 // Nope. Allow parent's expand/contract button to be removed
424 parent.setAlwaysShowPlusMinusSymbol(false);
428 for (var i = 0; i < result.length; i++)
434 // Determine name for new tree row. If first level, use entire
435 // DN. Otherwise, strip off first additional component.
436 if (attributes == "defaultNamingContext")
438 name = child["defaultNamingContext"];
442 //FIXME: must check for escapes, as ',' is also a valid value, not only a separator
443 name = child["dn"].split(",")[0];
446 // Build a standard tree row
447 trs = qx.ui.treefullcontrol.TreeRowStructure.getInstance().standard(name);
449 // This row is a "folder" (it can have children)
450 t = new qx.ui.treefullcontrol.TreeFolder(trs);
451 t.setAlwaysShowPlusMinusSymbol(true);
453 // Add this row to its parent
459 qx.Proto._displayTreeSelectionChangedResults = function(module, rpcRequest)
461 var fsm = module.fsm;
463 // Obtain the result object
464 var result = rpcRequest.getUserData("result").data;
466 // If we received an empty list, ...
469 // ... then just clear the attribute/value table.
470 tableModel.setData([ ]);
474 // Start with an empty table dataset
477 // The result contains a single object: attributes
478 var attributes = result[0];
480 // Track the maximum length of the attribute values
483 // For each attribute we received...
484 for (var attr in attributes)
486 // If it's multi-valued (type is an array)...
487 if (typeof(attributes[attr]) == "object")
489 // ... then add each value with same name
490 var a = attributes[attr];
491 for (var i = 0; i < a.length; i++)
493 if (a[i].length > maxLen)
495 maxLen = a[i].length;
497 rowData.push([ attr, a[i] ]);
500 else // single-valued
502 // ... add its name and value to the table dataset
503 if (attributes[attr].length > maxLen)
505 maxLen = attributes[attr].length;
507 rowData.push([ attr, attributes[attr] ]);
511 // Obtain the table and tableModel objects
512 var table = fsm.getObject("table:browse");
513 var tableModel = fsm.getObject("tableModel:browse");
515 // Adjust the width of the value column based on maxLen
516 table.setColumnWidth(1, maxLen * 7);
518 // Add the dataset to the table
519 tableModel.setData(rowData);
523 qx.Proto._clearAllFields = function(module, rpcRequest)
525 // Obtain the result object
526 var result = rpcRequest.getUserData("result").data;
528 // Retrieve the database handle
529 module.dbHandle = result;
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.
538 * Singleton Instance Getter
540 qx.Class.getInstance = qx.util.Return.returnInstance;