r21371: Ehmm I was reseting the wrong dataModel...
[metze/samba/wb-ndr.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 //qx.OO.addProperty({ name : "_table", type : "object" });
20 //qx.OO.addProperty({ name : "_ldbmod", type : "object" });
21
22 /**
23  * Build the raw graphical user interface.
24  */
25 qx.Proto.buildGui = function(module)
26 {
27   var o;
28   var fsm = module.fsm;
29
30   // We need a horizontal box layout for the database name
31   var hlayout = new qx.ui.layout.HorizontalBoxLayout();
32   hlayout.set({
33                   top: 20,
34                   left: 20,
35                   right: 20,
36                   height: 30
37               });
38
39   // Create a label for the database name
40   o = new qx.ui.basic.Atom("Database:");
41   o.setWidth(100);
42   o.setHorizontalChildrenAlign("right");
43
44   // Add the label to the horizontal layout
45   hlayout.add(o);
46
47   // Create a combo box for for the database name
48   o = new qx.ui.form.ComboBox();
49   o.getField().setWidth("100%");
50   o.setEditable(false);
51
52   // Add our global database name (the only option, for now)
53   var item = new qx.ui.form.ListItem(module.dbFile);
54   o.add(item);
55   
56   // We want to be notified if the selection changes
57   o.addEventListener("changeSelection", fsm.eventListener, fsm);
58
59   // Save the database name object so we can react to changes
60   fsm.addObject("dbName", o);
61     
62   // Add the combo box to the horizontal layout
63   hlayout.add(o);
64
65   // Add the database name selection to the canvas
66   module.canvas.add(hlayout);
67
68   // Create and position the tabview
69   var tabView_ = new qx.ui.pageview.tabview.TabView;
70   tabView_.set({
71                    top: 60,
72                    left: 20,
73                    right: 20,
74                    bottom: 20
75                });
76
77   // Create each of the tabs
78   var tabView_Browse =
79   new qx.ui.pageview.tabview.Button("Browse");
80   var tabView_Search =
81   new qx.ui.pageview.tabview.Button("Search");
82
83   // Specify the initially-selected tab
84   tabView_Browse.setChecked(true);
85
86   // Add each of the tabs to the tabview
87   tabView_.getBar().add(tabView_Browse, tabView_Search);
88
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);
94
95   // Build the browse page
96   this._buildPageBrowse(module, tabViewPage_Browse);
97
98   // Build the search page
99   this._buildPageSearch(module, tabViewPage_Search);
100
101   // Add the pages to the tabview
102   tabView_.getPane().add(tabViewPage_Browse, tabViewPage_Search);
103
104   // Add the tabview to our canvas
105   module.canvas.add(tabView_);
106 };
107
108
109 /**
110  * Populate the graphical user interface with the specified data
111  *
112  * @param module {swat.main.Module}
113  *   The module descriptor for the module.
114  *
115  * @result {Object}
116  *   The result returned by SAMBA to our request.  We display the data
117  *   provided by this result.
118  */
119 qx.Proto.displayData = function(module, rpcRequest)
120 {
121   var gui = module.gui;
122   var fsm = module.fsm;
123   var result = rpcRequest.getUserData("result")
124   var requestType = rpcRequest.getUserData("requestType");
125
126   // Did the request fail?
127   if (result.type == "failed")
128   {
129     // Yup.  We're not going to do anything particularly elegant...
130     alert("Async(" + result.id + ") exception: " + result.data);
131     return;
132   }
133
134   // Dispatch to the appropriate handler, depending on the request type
135   switch(requestType)
136   {
137   case "search":
138     this._displaySearchResults(module, rpcRequest);
139     break;
140
141   case "modify":
142     this._displayModifyResults(module, rpcRequest);
143     break;
144
145   case "add":
146     this._displayAddResults(module, rpcRequest);
147     break;
148  
149   case "delete":
150     this._displayDeleteResults(module, rpcRequest);
151     break;
152  
153   case "tree_open":
154     this._displayTreeOpenResults(module, rpcRequest);
155     break;
156
157   case "tree_selection_changed":
158
159     // Always update the table, even if it is not visible
160     this._displayTreeSelectionChangedResults(module, rpcRequest);
161
162     // Update the base field in ldbmod
163     this._displayLdbmodBaseChanged(module, rpcRequest);
164
165     break;
166
167   case "database_name_changed":
168     this._clearAllFields(module, rpcRequest);
169     break;
170
171   default:
172     throw new Error("Unexpected request type: " + requestType);
173   }
174
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();
180 };
181
182
183 qx.Proto._setAppearances = function()
184 {
185     // Modify the default appearance of a ComboBox for use in Search tab:
186     //   use more of the available width.
187     //
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.
192     //
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"];
202     if (! appearance)
203     {
204         return;
205     }
206     var oldInitial = appearance.initial;
207     appearance.initial = function(vTheme)
208     {
209         var res = oldInitial ? oldInitial.apply(this, arguments) : {};
210         res.width = "80%";
211         return res;
212     }
213 };
214
215
216 qx.Proto._buildPageSearch = function(module, page)
217 {
218   var fsm = module.fsm;
219
220   // We need a vertical box layout for the various input fields
221   var vlayout = new qx.ui.layout.VerticalBoxLayout();
222   vlayout.set({
223                overflow: "hidden",
224                height: 120,
225                top: 10,
226                left: 0,
227                right: 0,
228                bottom: 10
229            });
230
231   // Create a label for the list of required attributes
232   var label = new qx.ui.basic.Atom("Search Expression");
233   label.setHorizontalChildrenAlign("left");
234
235   // Add the label to the horizontal layout
236   vlayout.add(label);
237
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);
242     
243   // Add the combo box to the horizontal layout
244   vlayout.add(filter);
245
246   // Create a label for the list of required attributes
247   var label = new qx.ui.basic.Atom("Base");
248   label.setHorizontalChildrenAlign("left");
249
250   // Add the label to the horizontal layout
251   vlayout.add(label);
252
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);
257     
258   // Add the combo box to the horizontal layout
259   vlayout.add(base);
260
261   // Create a label for the list of required attributes
262   var label = new qx.ui.basic.Atom("Scope");
263   label.setWidth(100);
264   label.setHorizontalChildrenAlign("left");
265
266   // Add the label to the scope vertical layout
267   vlayout.add(label);
268
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);
273
274   var cbScope = new qx.ui.form.ComboBoxEx();
275   cbScope.setSelection([ ["subtree", "Subtree"], ["one", "One Level"], ["base", "Base"]]);
276   cbScope.setSelectedIndex(0);
277
278   fsm.addObject("scope", cbScope);
279
280   hlayout.add(cbScope);
281
282   // Add a sapcer
283   hlayout.add(new qx.ui.basic.HorizontalSpacer());
284
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);
289
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");
292
293   // Add the search button to the vertical layout
294   hlayout.add(search);
295
296   vlayout.add(hlayout);
297
298   // Add the vlayout to the page
299   page.add(vlayout);
300
301   var ldifview = new swat.module.ldbbrowse.LdifViewer();
302   ldifview.set({
303                top: 130,
304                left: 10,
305                right: 10,
306                bottom: 10
307            });
308
309   fsm.addObject("LdifView", ldifview);
310
311   // Add the output area to the page
312   page.add(ldifview);
313 };
314
315 qx.Proto._buildPageBrowse = function(module, page)
316 {
317   var fsm = module.fsm;
318
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);
322
323   // We need a vertical box layout for the tree and the buttons
324   var vlayout = new qx.ui.layout.VerticalBoxLayout();
325   vlayout.set({
326                height: "100%",
327                top: 5,
328                left: 5,
329                right: 5,
330                bottom: 5,
331                spacing: 10
332            });
333
334   // Create the tree and set its characteristics
335   var tree = new qx.ui.treevirtual.TreeVirtual(["Browse"]);
336   tree.set({
337              backgroundColor: 255,
338              border : qx.renderer.border.BorderPresets.getInstance().thinInset,
339              overflow: "hidden",
340              width: "100%",
341              height: "1*",
342              alwaysShowOpenCloseSymbol: true
343            });
344
345   // We've only got one column, so we don't need cell focus indication.
346   tree.setCellFocusAttributes({ backgroundColor : "transparent" });
347
348   // Get the data model
349   var dataModel = tree.getDataModel();
350
351   // Add the database file as the first node
352   dataModel.addBranch(null, module.dbFile, false);
353
354   // We're finished adding nodes.
355   dataModel.setData();
356
357   // Create event listeners
358   tree.addEventListener("treeOpenWhileEmpty", fsm.eventListener, fsm);
359   tree.addEventListener("changeSelection", fsm.eventListener, fsm);
360
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");
364
365   // Add the tree to the vlayout.
366   vlayout.add(tree);
367
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();
371   hlayout.set({
372                height: "auto",
373                spacing: 10
374            });
375
376   // Add the "New" button
377   this._newb = new qx.ui.form.Button("New");
378   this._newb.addEventListener("execute", this._switchToNewrecord, this);
379
380   // Add the button to the hlayout
381   hlayout.add(this._newb);
382
383   // Add the "New" button
384   this._modb = new qx.ui.form.Button("Modify");
385   this._modb.addEventListener("execute", this._switchToModrecord, this);
386
387   // Add the button to the hlayout
388   hlayout.add(this._modb);
389
390   hlayout.add(new qx.ui.basic.HorizontalSpacer());
391
392   // Add the "Delete" button
393   this._delb = new qx.ui.form.Button("Delete");
394   this._delb.addEventListener("execute", this._confirmDeleteRecord, this);
395
396   // Add the button to the hlayout
397   hlayout.add(this._delb);
398   
399   // Add the hlayout to the vlayout.
400   vlayout.add(hlayout);
401
402   //Add the left vlayout to the splitpane
403   splitpane.addLeft(vlayout);
404
405   // Create a simple table model
406   var tableModel = new qx.ui.table.SimpleTableModel();
407   tableModel.setColumns([ "Attribute", "Value" ]);
408
409   tableModel.setColumnEditable(0, false);
410   tableModel.setColumnEditable(1, false);
411   fsm.addObject("tableModel:browse", tableModel);
412
413   // Create a table
414   this._table = new qx.ui.table.Table(tableModel);
415   this._table.set({
416                 top: 10,
417                 left: 0,
418                 right: 0,
419                 bottom: 10,
420                 statusBarVisible: false,
421                 columnVisibilityButtonVisible: false
422             });
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);
427
428   //table.setDisplay(false);
429
430   // Add the table to the bottom portion of the splitpane
431   splitpane.addRight(this._table);
432
433   // Build the create/modify widget
434   this._ldbmod = new swat.module.ldbbrowse.LdbModify(fsm);
435   this._ldbmod.set({
436                 top: 10,
437                 left: 0,
438                 right: 0,
439                 bottom: 10
440             });
441   // Not displayed by default
442   this._ldbmod.setDisplay(false);
443
444   fsm.addObject("ldbmod", this._ldbmod);
445
446   splitpane.addRight(this._ldbmod);
447
448   // Add the first splitpane to the page
449   page.add(splitpane);
450 };
451
452 qx.Proto._switchToNormal = function()
453 {
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);
459 };
460
461 qx.Proto._switchToNewrecord = function()
462 {
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);
469 };
470
471 qx.Proto._switchToModrecord = function()
472 {
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);
479 };
480
481 qx.Proto._confirmDeleteRecord = function()
482 {
483   
484   this._ldbmod.showConfirmDelete();
485 };
486
487 qx.Proto._displayModifyResults = function(module, rpcRequest)
488 {
489   var tree = module.fsm.getObject("tree");
490   tree.createDispatchDataEvent("changeSelection", tree.getSelectedNodes());
491
492   alert("Object successfully modified!");
493
494   this._switchToNormal();
495   //this._ldbmod.postCleanUp();
496 }
497
498 qx.Proto._displayAddResults = function(module, rpcRequest)
499 {
500   var result = rpcRequest.getUserData("result");
501
502   var tree = module.fsm.getObject("tree");
503   var node = tree.getSelectedNodes()[0];
504   
505   tree.getDataModel().prune(node.nodeId, false);
506   node.bOpened = false;
507   tree.toggleOpened(node);
508
509   alert("Object successfully added!");
510
511   this._switchToNormal();
512   //this._ldbmod.postCleanUp();
513 };
514
515 qx.Proto._displayDeleteResults = function(module, rpcRequest, type)
516 {
517   var result = rpcRequest.getUserData("result");
518
519   var tree = module.fsm.getObject("tree");
520   var dataModel = tree.getDataModel();
521   var node = dataModel.getData()[tree.getSelectedNodes()[0].parentNodeId];
522   
523   dataModel.prune(node.nodeId, false);
524   node.bOpened = false;
525   tree.toggleOpened(node);
526
527   alert("Object Successfully deleted!");
528
529   this._ldbmod.setBase("");
530
531   // just clear the attribute/value table.
532   var tableModel = module.fsm.getObject("tableModel:browse");
533   tableModel.setData([]);
534 };
535
536 qx.Proto._displaySearchResults = function(module, rpcRequest)
537 {
538   var fsm = module.fsm;
539
540   // Obtain the ldif object
541   var ldifview = fsm.getObject("LdifView");
542
543   ldifview.reset();
544
545   // Obtain the result object
546   result = rpcRequest.getUserData("result").data;
547
548   if (result && result["length"])
549   {
550     len = result["length"];
551     for (var i = 0; i < result["length"]; i++)
552     {
553       var obj = result[i];
554       if (typeof(obj) != "object")
555       {
556         alert("Found unexpected result, type " +
557               typeof(obj) +
558               ", " +
559               obj +
560               "\n");
561         continue;
562       }
563       ldifview.appendObject(obj);
564     }
565   }
566   else
567   {
568     alert("No results.");
569   }
570 };
571
572
573 qx.Proto._displayTreeOpenResults = function(module, rpcRequest)
574 {
575   var t;
576   var child;
577   var fsm = module.fsm;
578
579   // Get the tree object
580   var tree = fsm.getObject("tree");
581   var dataModel = tree.getDataModel();
582
583   // Obtain the result object
584   var result = rpcRequest.getUserData("result").data;
585
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");
589
590   // Remove any existing children, they will be replaced by the result of this call (refresh)
591   dataModel.setData();
592
593   // Any children?
594   if (! result || result["length"] == 0)
595   {
596     // Nope.  Remove parent's expand/contract button.
597     dataModel.setState(parentNode.nodeId, { bHideOpenClose : true });
598     return;
599   }
600
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")))
605   {
606     defnc = result[0]["defaultNamingContext"];
607
608     // Build a tree row for the defaultNamingContext
609     if (defnc)
610     {
611       dataModel.addBranch(parentNode.nodeId, defnc, false);
612     }
613
614     var ncs = result[0]["namingContexts"];
615
616     // If it's multi-valued (type is an array) we have other naming contexts
617     // to show
618     if (typeof(ncs) == "object")
619     {
620       for (var i = 0; i < ncs.length; i++)
621       {
622         if (ncs[i] != defnc)
623         {
624           //skip default naming context
625           dataModel.addBranch(parentNode.nodeId, ncs[i], false);
626         }
627       }
628     }
629   }
630   else
631   {
632     for (var i = 0; i < result.length; i++)
633     {
634       var name;
635   
636       child = result[i];
637   
638       name = child["dn"].split(",")[0];
639   
640       // Build a standard tree row
641       dataModel.addBranch(parentNode.nodeId, name, false);
642     }
643   }
644
645   // Cause the tree changes to be rendered
646   dataModel.setData();
647 };
648
649 qx.Proto._displayLdbmodBaseChanged = function(module, rpcRequest)
650 {
651   var fsm = module.fsm;
652
653   // Obtain the result object
654   var result = rpcRequest.getUserData("result").data;
655
656   // If we received an empty list, ...
657   if (result == null)
658   {
659     // ... then ??
660     return;
661   }
662
663   this._ldbmod.setBase(result[0]["dn"]);
664 };
665
666 qx.Proto._displayTreeSelectionChangedResults = function(module, rpcRequest)
667 {
668   var fsm = module.fsm;
669
670   // Obtain the result object
671   var result = rpcRequest.getUserData("result").data;
672
673   var tree = fsm.getObject("tree");
674   var dataModel = tree.getDataModel();
675
676   // If we received an empty list, ...
677   if (result == null)
678   {
679     // ... then just clear the attribute/value table.
680     dataModel.setData([ ]);
681     return;
682   }
683
684   // Start with an empty table dataset
685   var rowData = [ ];
686
687   // The result contains a single object: attributes
688   var attributes = result[0];
689
690   // Track the maximum length of the attribute values
691   var maxLen = 0;
692
693   // For each attribute we received...
694   for (var attr in attributes)
695   {
696     // If it's multi-valued (type is an array)...
697     if (typeof(attributes[attr]) == "object")
698     {
699       // ... then add each value with same name
700       var a = attributes[attr];
701       for (var i = 0; i < a.length; i++)
702       {
703         if (a[i].length > maxLen)
704         {
705           maxLen = a[i].length;
706         }
707         rowData.push([ attr, a[i] ]);
708       }
709     }
710     else    // single-valued
711     {
712       // ... add its name and value to the table dataset
713       if (attributes[attr].length > maxLen)
714       {
715         maxLen = attributes[attr].length;
716       }
717       rowData.push([ attr, attributes[attr] ]);
718     }
719   }
720
721   // Obtain the table and tableModel objects
722   var table = fsm.getObject("table:browse");
723   var tableModel = fsm.getObject("tableModel:browse");
724
725   // Adjust the width of the value column based on maxLen
726   table.setColumnWidth(1, maxLen * 7);
727
728   // Add the dataset to the table
729   tableModel.setData(rowData);
730 };
731
732
733 qx.Proto._clearAllFields = function(module, rpcRequest)
734 {
735   // Obtain the result object
736   var result = rpcRequest.getUserData("result").data;
737
738   // Retrieve the database handle
739   module.dbHandle = result;
740
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.
743 };
744
745
746
747 /**
748  * Singleton Instance Getter
749  */
750 qx.Class.getInstance = qx.lang.Function.returnInstance;