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);
19 //qx.OO.addProperty({ name : "_table", type : "object" });
20 //qx.OO.addProperty({ name : "_ldbmod", type : "object" });
23 * Build the raw graphical user interface.
25 qx.Proto.buildGui = function(module)
30 // We need a horizontal box layout for the database name
31 var hlayout = new qx.ui.layout.HorizontalBoxLayout();
39 // Create a label for the database name
40 o = new qx.ui.basic.Atom("Database:");
42 o.setHorizontalChildrenAlign("right");
44 // Add the label to the horizontal layout
47 // Create a combo box for for the database name
48 o = new qx.ui.form.ComboBox();
49 o.getField().setWidth("100%");
52 // Add our global database name (the only option, for now)
53 var item = new qx.ui.form.ListItem(module.dbFile);
56 // We want to be notified if the selection changes
57 o.addEventListener("changeSelection", fsm.eventListener, fsm);
59 // Save the database name object so we can react to changes
60 fsm.addObject("dbName", o);
62 // Add the combo box to the horizontal layout
65 // Add the database name selection to the canvas
66 module.canvas.add(hlayout);
68 // Create and position the tabview
69 var tabView_ = new qx.ui.pageview.tabview.TabView;
77 // Create each of the tabs
79 new qx.ui.pageview.tabview.Button("Browse");
81 new qx.ui.pageview.tabview.Button("Search");
83 // Specify the initially-selected tab
84 tabView_Browse.setChecked(true);
86 // Add each of the tabs to the tabview
87 tabView_.getBar().add(tabView_Browse, tabView_Search);
89 // Create the pages to display when each tab is selected
90 var tabViewPage_Browse =
91 new qx.ui.pageview.tabview.Page(tabView_Browse);
92 var tabViewPage_Search =
93 new qx.ui.pageview.tabview.Page(tabView_Search);
95 // Build the browse page
96 this._buildPageBrowse(module, tabViewPage_Browse);
98 // Build the search page
99 this._buildPageSearch(module, tabViewPage_Search);
101 // Add the pages to the tabview
102 tabView_.getPane().add(tabViewPage_Browse, tabViewPage_Search);
104 // Add the tabview to our canvas
105 module.canvas.add(tabView_);
110 * Populate the graphical user interface with the specified data
112 * @param module {swat.main.Module}
113 * The module descriptor for the module.
116 * The result returned by SAMBA to our request. We display the data
117 * provided by this result.
119 qx.Proto.displayData = function(module, rpcRequest)
121 var gui = module.gui;
122 var fsm = module.fsm;
123 var result = rpcRequest.getUserData("result")
124 var requestType = rpcRequest.getUserData("requestType");
126 // Did the request fail?
127 if (result.type == "failed")
129 // Yup. We're not going to do anything particularly elegant...
130 alert("Async(" + result.id + ") exception: " + result.data);
134 // Dispatch to the appropriate handler, depending on the request type
138 this._displaySearchResults(module, rpcRequest);
142 this._displayModifyResults(module, rpcRequest);
146 this._displayAddResults(module, rpcRequest);
150 this._displayDeleteResults(module, rpcRequest);
154 this._displayTreeOpenResults(module, rpcRequest);
157 case "tree_selection_changed":
159 // Always update the table, even if it is not visible
160 this._displayTreeSelectionChangedResults(module, rpcRequest);
162 // Update the base field in ldbmod
163 this._displayLdbmodBaseChanged(module, rpcRequest);
167 case "database_name_changed":
168 this._clearAllFields(module, rpcRequest);
172 throw new Error("Unexpected request type: " + requestType);
175 // Force flushing of pending DOM updates. This is actually a
176 // work-around for a bug. Without this, occasionally, updates to the
177 // gui aren't displayed until some 'action' takes place, e.g. key
178 // press or mouse movement.
179 qx.ui.core.Widget.flushGlobalQueues();
183 qx.Proto._setAppearances = function()
185 // Modify the default appearance of a ComboBox for use in Search tab:
186 // use more of the available width.
188 // If we had multiple uses, we'd create a new appearance which didn't
189 // contain a width. That way, we'd be able to assign a specific width to
190 // each ComboBox instance. Since we don't have multiple of them, we can
191 // just modify this default appearance.
193 // See http://qooxdoo.org/documentation/user_manual/appearance for an
194 // explanation of what's going on here. The missing significant point in
195 // the discussion is that in the current qooxdoo appearance
196 // implementation, it's not possible to override a specific widget's
197 // appearance with explicit settings just for that widget (stupid!). I
198 // expect that to change in a future version.
199 var appMgr = qx.manager.object.AppearanceManager.getInstance();
200 var theme = appMgr.getAppearanceTheme();
201 var appearance = theme._appearances["combo-box"];
206 var oldInitial = appearance.initial;
207 appearance.initial = function(vTheme)
209 var res = oldInitial ? oldInitial.apply(this, arguments) : {};
216 qx.Proto._buildPageSearch = function(module, page)
218 var fsm = module.fsm;
220 // We need a vertical box layout for the various input fields
221 var vlayout = new qx.ui.layout.VerticalBoxLayout();
231 // Create a label for the list of required attributes
232 var label = new qx.ui.basic.Atom("Search Expression");
233 label.setHorizontalChildrenAlign("left");
235 // Add the label to the horizontal layout
238 // Create a combo box for entry of the search expression
239 var filter = new qx.ui.form.TextField();
240 filter.set({ width:300 });
241 fsm.addObject("searchExpr", filter);
243 // Add the combo box to the horizontal layout
246 // Create a label for the list of required attributes
247 var label = new qx.ui.basic.Atom("Base");
248 label.setHorizontalChildrenAlign("left");
250 // Add the label to the horizontal layout
253 // Create a combo box for entry of the search expression
254 var base = new qx.ui.form.TextField();
255 base.set({ width:300 });
256 fsm.addObject("baseDN", base);
258 // Add the combo box to the horizontal layout
261 // Create a label for the list of required attributes
262 var label = new qx.ui.basic.Atom("Scope");
264 label.setHorizontalChildrenAlign("left");
266 // Add the label to the scope vertical layout
269 // Use a horizontal box layout to keep the search button aligned
270 var hlayout = new qx.ui.layout.HorizontalBoxLayout();
271 hlayout.setWidth(300);
272 hlayout.setHeight(30);
274 var cbScope = new qx.ui.form.ComboBoxEx();
275 cbScope.setSelection([ ["subtree", "Subtree"], ["one", "One Level"], ["base", "Base"]]);
276 cbScope.setSelectedIndex(0);
278 fsm.addObject("scope", cbScope);
280 hlayout.add(cbScope);
283 hlayout.add(new qx.ui.basic.HorizontalSpacer());
285 // Create the 'Search' button
286 var search = new qx.ui.form.Button('Search');
287 search.setWidth(100);
288 search.addEventListener("execute", fsm.eventListener, fsm);
290 // We'll be receiving events on the search object, so save its friendly name
291 fsm.addObject("search", search, "swat.main.fsmUtils.disable_during_rpc");
293 // Add the search button to the vertical layout
296 vlayout.add(hlayout);
298 // Add the vlayout to the page
301 var ldifview = new swat.module.ldbbrowse.LdifViewer();
309 fsm.addObject("LdifView", ldifview);
311 // Add the output area to the page
315 qx.Proto._buildPageBrowse = function(module, page)
317 var fsm = module.fsm;
319 // Create a horizontal splitpane for tree (left) and table (right)
320 var splitpane = new qx.ui.splitpane.HorizontalSplitPane("1*", "2*");
321 splitpane.setEdge(0);
323 // We need a vertical box layout for the tree and the buttons
324 var vlayout = new qx.ui.layout.VerticalBoxLayout();
334 // Create the tree and set its characteristics
335 var tree = new qx.ui.treevirtual.TreeVirtual(["Browse"]);
337 backgroundColor: 255,
338 border : qx.renderer.border.BorderPresets.getInstance().thinInset,
342 alwaysShowOpenCloseSymbol: true
345 // We've only got one column, so we don't need cell focus indication.
346 tree.setCellFocusAttributes({ backgroundColor : "transparent" });
348 // Get the data model
349 var dataModel = tree.getDataModel();
351 // Add the database file as the first node
352 dataModel.addBranch(null, module.dbFile, false);
354 // We're finished adding nodes.
357 // Create event listeners
358 tree.addEventListener("treeOpenWhileEmpty", fsm.eventListener, fsm);
359 tree.addEventListener("changeSelection", fsm.eventListener, fsm);
361 // We'll be receiving events on the tree object, so save its friendly name,
362 // and cause the tree to be disabled during remote procedure calls.
363 fsm.addObject("tree", tree, "swat.main.fsmUtils.disable_during_rpc");
365 // Add the tree to the vlayout.
368 // Add an horizonatl layout for the "New" and "Modify" buttons
369 // We need a vertical box layout for the tree and the buttons
370 var hlayout = new qx.ui.layout.HorizontalBoxLayout();
376 // Add the "New" button
377 this._newb = new qx.ui.form.Button("New");
378 this._newb.addEventListener("execute", this._switchToNewrecord, this);
380 // Add the button to the hlayout
381 hlayout.add(this._newb);
383 // Add the "New" button
384 this._modb = new qx.ui.form.Button("Modify");
385 this._modb.addEventListener("execute", this._switchToModrecord, this);
387 // Add the button to the hlayout
388 hlayout.add(this._modb);
390 hlayout.add(new qx.ui.basic.HorizontalSpacer());
392 // Add the "Delete" button
393 this._delb = new qx.ui.form.Button("Delete");
394 this._delb.addEventListener("execute", this._confirmDeleteRecord, this);
396 // Add the button to the hlayout
397 hlayout.add(this._delb);
399 // Add the hlayout to the vlayout.
400 vlayout.add(hlayout);
402 //Add the left vlayout to the splitpane
403 splitpane.addLeft(vlayout);
405 // Create a simple table model
406 var tableModel = new qx.ui.table.SimpleTableModel();
407 tableModel.setColumns([ "Attribute", "Value" ]);
409 tableModel.setColumnEditable(0, false);
410 tableModel.setColumnEditable(1, false);
411 fsm.addObject("tableModel:browse", tableModel);
414 this._table = new qx.ui.table.Table(tableModel);
420 statusBarVisible: false,
421 columnVisibilityButtonVisible: false
423 this._table.setColumnWidth(0, 180);
424 this._table.setColumnWidth(1, 320);
425 this._table.setMetaColumnCounts([1, -1]);
426 fsm.addObject("table:browse", this._table);
428 //table.setDisplay(false);
430 // Add the table to the bottom portion of the splitpane
431 splitpane.addRight(this._table);
433 // Build the create/modify widget
434 this._ldbmod = new swat.module.ldbbrowse.LdbModify(fsm);
441 // Not displayed by default
442 this._ldbmod.setDisplay(false);
444 fsm.addObject("ldbmod", this._ldbmod);
446 splitpane.addRight(this._ldbmod);
448 // Add the first splitpane to the page
452 qx.Proto._switchToNormal = function()
454 this._table.setDisplay(true);
455 this._ldbmod.setDisplay(false);
456 this._newb.setEnabled(true);
457 this._modb.setEnabled(true);
458 this._delb.setEnabled(true);
461 qx.Proto._switchToNewrecord = function()
463 this._table.setDisplay(false);
464 this._ldbmod.setDisplay(true);
465 this._newb.setEnabled(false);
466 this._modb.setEnabled(false);
467 this._delb.setEnabled(false);
468 this._ldbmod.initNew(this._switchToNormal, this);
471 qx.Proto._switchToModrecord = function()
473 this._table.setDisplay(false);
474 this._ldbmod.setDisplay(true);
475 this._newb.setEnabled(false);
476 this._modb.setEnabled(false);
477 this._delb.setEnabled(false);
478 this._ldbmod.initMod(this._table.getTableModel(), this._switchToNormal, this);
481 qx.Proto._confirmDeleteRecord = function()
484 this._ldbmod.showConfirmDelete();
487 qx.Proto._displayModifyResults = function(module, rpcRequest)
489 var tree = module.fsm.getObject("tree");
490 tree.createDispatchDataEvent("changeSelection", tree.getSelectedNodes());
492 alert("Object successfully modified!");
494 this._switchToNormal();
495 //this._ldbmod.postCleanUp();
498 qx.Proto._displayAddResults = function(module, rpcRequest)
500 var result = rpcRequest.getUserData("result");
502 var tree = module.fsm.getObject("tree");
503 var node = tree.getSelectedNodes()[0];
505 tree.getDataModel().prune(node.nodeId, false);
506 node.bOpened = false;
507 tree.toggleOpened(node);
509 alert("Object successfully added!");
511 this._switchToNormal();
512 //this._ldbmod.postCleanUp();
515 qx.Proto._displayDeleteResults = function(module, rpcRequest, type)
517 var result = rpcRequest.getUserData("result");
519 var tree = module.fsm.getObject("tree");
520 var dataModel = tree.getDataModel();
521 var node = dataModel.getData()[tree.getSelectedNodes()[0].parentNodeId];
523 dataModel.prune(node.nodeId, false);
524 node.bOpened = false;
525 tree.toggleOpened(node);
527 alert("Object Successfully deleted!");
529 this._ldbmod.setBase("");
531 // just clear the attribute/value table.
532 var tableModel = module.fsm.getObject("tableModel:browse");
533 tableModel.setData([]);
536 qx.Proto._displaySearchResults = function(module, rpcRequest)
538 var fsm = module.fsm;
540 // Obtain the ldif object
541 var ldifview = fsm.getObject("LdifView");
545 // Obtain the result object
546 result = rpcRequest.getUserData("result").data;
548 if (result && result["length"])
550 len = result["length"];
551 for (var i = 0; i < result["length"]; i++)
554 if (typeof(obj) != "object")
556 alert("Found unexpected result, type " +
563 ldifview.appendObject(obj);
568 alert("No results.");
573 qx.Proto._displayTreeOpenResults = function(module, rpcRequest)
577 var fsm = module.fsm;
579 // Get the tree object
580 var tree = fsm.getObject("tree");
581 var dataModel = tree.getDataModel();
583 // Obtain the result object
584 var result = rpcRequest.getUserData("result").data;
586 // We also need some of the original parameters passed to the request
587 var parentNode = rpcRequest.getUserData("parentNode");
588 var attributes = rpcRequest.getUserData("attributes");
590 // Remove any existing children, they will be replaced by the result of this call (refresh)
594 if (! result || result["length"] == 0)
596 // Nope. Remove parent's expand/contract button.
597 dataModel.setState(parentNode.nodeId, { bHideOpenClose : true });
601 // base object, add naming contexts to the root
602 if ((result.length == 1) &&
603 ((result[0]["dn"] == "") ||
604 (result[0]["dn"].toLowerCase() == "cn=rootdse")))
606 defnc = result[0]["defaultNamingContext"];
608 // Build a tree row for the defaultNamingContext
611 dataModel.addBranch(parentNode.nodeId, defnc, false);
614 var ncs = result[0]["namingContexts"];
616 // If it's multi-valued (type is an array) we have other naming contexts
618 if (typeof(ncs) == "object")
620 for (var i = 0; i < ncs.length; i++)
624 //skip default naming context
625 dataModel.addBranch(parentNode.nodeId, ncs[i], false);
632 for (var i = 0; i < result.length; i++)
638 name = child["dn"].split(",")[0];
640 // Build a standard tree row
641 dataModel.addBranch(parentNode.nodeId, name, false);
645 // Cause the tree changes to be rendered
649 qx.Proto._displayLdbmodBaseChanged = function(module, rpcRequest)
651 var fsm = module.fsm;
653 // Obtain the result object
654 var result = rpcRequest.getUserData("result").data;
656 // If we received an empty list, ...
663 this._ldbmod.setBase(result[0]["dn"]);
666 qx.Proto._displayTreeSelectionChangedResults = function(module, rpcRequest)
668 var fsm = module.fsm;
670 // Obtain the result object
671 var result = rpcRequest.getUserData("result").data;
673 var tree = fsm.getObject("tree");
674 var dataModel = tree.getDataModel();
676 // If we received an empty list, ...
679 // ... then just clear the attribute/value table.
680 dataModel.setData([ ]);
684 // Start with an empty table dataset
687 // The result contains a single object: attributes
688 var attributes = result[0];
690 // Track the maximum length of the attribute values
693 // For each attribute we received...
694 for (var attr in attributes)
696 // If it's multi-valued (type is an array)...
697 if (typeof(attributes[attr]) == "object")
699 // ... then add each value with same name
700 var a = attributes[attr];
701 for (var i = 0; i < a.length; i++)
703 if (a[i].length > maxLen)
705 maxLen = a[i].length;
707 rowData.push([ attr, a[i] ]);
710 else // single-valued
712 // ... add its name and value to the table dataset
713 if (attributes[attr].length > maxLen)
715 maxLen = attributes[attr].length;
717 rowData.push([ attr, attributes[attr] ]);
721 // Obtain the table and tableModel objects
722 var table = fsm.getObject("table:browse");
723 var tableModel = fsm.getObject("tableModel:browse");
725 // Adjust the width of the value column based on maxLen
726 table.setColumnWidth(1, maxLen * 7);
728 // Add the dataset to the table
729 tableModel.setData(rowData);
733 qx.Proto._clearAllFields = function(module, rpcRequest)
735 // Obtain the result object
736 var result = rpcRequest.getUserData("result").data;
738 // Retrieve the database handle
739 module.dbHandle = result;
741 // In the future, when we support more than one database, we'll want to
742 // clear all fields here. For now, there's no need.
748 * Singleton Instance Getter
750 qx.Class.getInstance = qx.lang.Function.returnInstance;