Import src directory from OpenRico 2.1
[misc/kostenrechnung] / lib / rico / ricoTree.js
1 //  Rico Tree Control
2 //  by Matt Brown
3 //  Oct 2006, rewritten Jan 2008
4 //  email: dowdybrown@yahoo.com
5 //  leafIcon logic added by Marco Catunda
6 //  Requires prototype.js and ricoCommon.js
7
8 //  Data for the tree is obtained via AJAX requests
9 //  Each record in the AJAX response should contain 5 or 6 cells:
10 //   cells[0]=parent node id
11 //   cells[1]=node id
12 //   cells[2]=description
13 //   cells[3]=L/zero (leaf), C/non-zero (container)
14 //   cells[4]=0->not selectable, 1->selectable (use default action), otherwise the node is selectable and cells[4] contains the action
15 //   cells[5]=leafIcon (optional)
16
17
18 Rico.TreeControl = Class.create(
19 /** @lends Rico.TreeControl# */
20 {
21 /**
22  * @class Implements a pop-up tree control.
23  * @extends Rico.Popup
24  * @constructs
25  * @param id unique identifier
26  * @param url data source
27  * @param options object may contain any of the following:<dl>
28  *   <dt>nodeIdDisplay</dt><dd> first, last, tooltip, or none? default=none</dd>
29  *   <dt>showCheckBox </dt><dd> show checkbox next to each item? default=false</dd>
30  *   <dt>showFolders  </dt><dd> show folder icons? default=false</dd>
31  *   <dt>showPlusMinus</dt><dd> show +/- icons to open/close branches? default=true</dd>
32  *   <dt>showLines    </dt><dd> show vertical lines connecting each level? default=true</dd>
33  *   <dt>defaultAction</dt><dd> function to call when user clicks on an item, default is to call returnValue method</dd>
34  *   <dt>height       </dt><dd> control height? default=300px</dd>
35  *   <dt>width        </dt><dd> control width? default=300px</dd>
36  *   <dt>leafIcon     </dt><dd> url to img, default=doc.gif ('none'=no leaf icon)</dd>
37  *</dl>
38  */
39   initialize: function(id,url,options) {
40     Object.extend(this, new Rico.Popup({ignoreClicks:true}));
41     Object.extend(this.options, {
42       nodeIdDisplay:'none',
43       showCheckBox: false,
44       showFolders: false,
45       showPlusMinus: true,
46       showLines: true,
47       defaultAction: this.nodeClick.bindAsEventListener(this),
48       height: '300px',
49       width: '300px',
50       leafIcon: Rico.imgDir+'doc.gif'
51     });
52     Object.extend(this.options, options || {});
53     this.id=id;
54     this.dataSource=url;
55     this.close=this.closePopup;
56   },
57
58   atLoad : function() {
59     var imgsrc = ["node.gif","nodelast.gif","folderopen.gif","folderclosed.gif"];
60     // preload images
61     for (var i=0;i<imgsrc.length;i++) {
62       var img=new Image();
63       img.src = Rico.imgDir+imgsrc[i];
64     }
65     this.treeDiv=document.createElement("div");
66     this.treeDiv.id=this.id;
67     this.treeDiv.className='ricoTree';
68     this.treeDiv.style.height=this.options.height;
69     this.treeDiv.style.width=this.options.width;
70     this.container=document.createElement("div");
71     this.container.style.display="none";
72     this.container.className='ricoTreeContainer';
73     this.container.appendChild(this.treeDiv);
74     document.body.appendChild(this.container);
75     if (this.options.showCheckBox) {
76       this.buttonDiv=document.createElement("div");
77       this.buttonDiv.style.width=this.options.width;
78       this.buttonDiv.className='ricoTreeButtons';
79       if (Element.getStyle(this.container,'position')=='absolute') {
80         var span=document.createElement("span");
81         span.innerHTML=RicoTranslate.getPhraseById('treeSave');
82         Element.setStyle(span,{'float':'left',cursor:'pointer'});
83         this.buttonDiv.appendChild(span);
84         Event.observe(span,'click',this.saveSelection.bindAsEventListener(this));
85       }
86       var span=document.createElement("span");
87       span.innerHTML=RicoTranslate.getPhraseById('treeClear');
88       Element.setStyle(span,{'float':'right',cursor:'pointer'});
89       this.buttonDiv.appendChild(span);
90       this.container.appendChild(this.buttonDiv);
91       Event.observe(span,'click',this.clrCheckBoxEvent.bindAsEventListener(this));
92     }
93     this.setDiv(this.container);
94     this.close();
95   },
96
97   setTreeDiv: function(divId) {
98     this.treeDiv = $(divId);
99     this.openPopup = function() {};
100   },
101
102   open: function() {
103     this.openPopup();
104     if (this.treeDiv.childNodes.length == 0 && this.dataSource) {
105       this.loadXMLDoc();
106     }
107   },
108
109   loadXMLDoc: function(branchPin) {
110     var parms="id="+this.id;
111     if (branchPin) {
112       parms+="&Parent="+branchPin;
113     }
114     Rico.writeDebugMsg('Tree loadXMLDoc:\n'+parms+'\n'+this.dataSource);
115     var request=new Ajax.Request(this.dataSource, {parameters:parms,method:'get',onComplete:this.processResponse.bind(this)});
116   },
117
118   domID: function(nodeID,part) {
119     return 'RicoTree_'+part+'_'+this.id+'_'+nodeID;
120   },
121
122   processResponse: function(request) {
123     var response = request.responseXML.getElementsByTagName("ajax-response");
124     if (response == null || response.length != 1) return;
125     var rowsElement = response[0].getElementsByTagName('rows')[0];
126     var trs = rowsElement.getElementsByTagName("tr");
127     var rowdata=[];
128     for (var i=0; i < trs.length; i++) {
129       var cells = trs[i].getElementsByTagName("td");
130       if (cells.length < 5) continue;
131       var content=[];
132       content[5]=this.options.leafIcon;
133       for (var j=0; j<cells.length; j++) {
134         content[j]=RicoUtil.getContentAsString(cells[j],true);
135       }
136       content[3] = content[3].match(/^0|L$/i) ? 0 : 1;
137       content[4] = parseInt(content[4]);
138       rowdata.push(content);
139     }
140     for (var i=0; i < rowdata.length; i++) {
141       var moreChildren=(i < rowdata.length-1) && (rowdata[i][0]==rowdata[i+1][0]);
142       this.addNode(rowdata[i][0],rowdata[i][1],rowdata[i][2],rowdata[i][3],rowdata[i][4],rowdata[i][5],!moreChildren);
143     }
144   },
145
146   DisplayImages: function(row,arNames) {
147     var i,img,td;
148     for(i=0;i<arNames.length;i++) {
149       img = document.createElement("img");
150       img.src=Rico.imgDir+arNames[i] + ".gif";
151       td=row.insertCell(-1);
152       td.appendChild(img);
153     }
154   },
155
156   addNode: function(parentId, nodeId, nodeDesc, isContainer, isSelectable, leafIcon, isLast) {
157     var parentNode=$(this.domID(parentId,'Parent'));
158     var parentChildren=$(this.domID(parentId,'Children'));
159     var level=parentNode ? parentNode.TreeLevel+1 : 0;
160     //alert("addNode at level " + level + " (" + nodeId + ")")
161     var tab = document.createElement("table");
162     var div = document.createElement("div");
163     div.id=this.domID(nodeId,'Children');
164     div.className='ricoTreeBranch';
165     div.style.display=parentNode ? 'none' : '';
166     tab.border=0;
167     tab.cellSpacing=0;
168     tab.cellPadding=0;
169     tab.id=this.domID(nodeId,'Parent');
170     tab.TreeLevel=level;
171     tab.TreeContainer=isContainer;
172     tab.TreeFetchedChildren=this.dataSource ? false : true;
173     var row=tab.insertRow(0);
174     var td=[];
175     for (var i=0; i<level-1; i++) {
176       td[i]=row.insertCell(-1);
177     }
178     if (level>1) {
179       var tdParent=parentNode.getElementsByTagName('td');
180       for (var i=0; i<level-2; i++) {
181         td[i].innerHTML=tdParent[i].innerHTML;
182       }
183       var img = document.createElement("img");
184       img.src=Rico.imgDir+(parentChildren.nextSibling && this.options.showLines ? "nodeline" : "nodeblank")+".gif";
185       td[level-2].appendChild(img);
186     }
187     if (level>0) {
188       var suffix=isLast && this.options.showLines ? 'last' : '';
189       var prefix=this.options.showLines ? 'node' : '';
190       if (this.options.showPlusMinus && isContainer) {
191         var img = document.createElement("img");
192         img.name=nodeId;
193         img.style.cursor='pointer';
194         img.onclick=this.clickBranch.bindAsEventListener(this);
195         img.src=Rico.imgDir+prefix+"p"+suffix+".gif";
196         row.insertCell(-1).appendChild(img);
197       } else if (this.options.showLines) {
198         var img = document.createElement("img");
199         img.src=Rico.imgDir+"node"+suffix+".gif";
200         row.insertCell(-1).appendChild(img);
201       }
202       if (this.options.showFolders && (isContainer || (leafIcon && leafIcon!='none'))) {
203         var img = document.createElement("img");
204         if (!isContainer) {
205           img.src=leafIcon;
206         } else {
207           img.name=nodeId;
208           img.style.cursor='pointer';
209           img.onclick=this.clickBranch.bindAsEventListener(this);
210           img.src=Rico.imgDir+"folderclosed.gif";
211         }
212         row.insertCell(-1).appendChild(img);
213       }
214     }
215     if (isSelectable && this.options.showCheckBox) {
216       var chkbx=document.createElement("input");
217       chkbx.type="checkbox";
218       chkbx.value=nodeId;
219       row.insertCell(-1).appendChild(chkbx);
220     }
221
222     if (isSelectable && !this.options.showCheckBox) {
223       var span=document.createElement('a');
224       if (typeof isSelectable=='string') {
225         span.href=isSelectable;
226       } else {
227         span.href='javascript:void(0)';
228         span.onclick=this.options.defaultAction;
229       }
230     } else {
231       var span=document.createElement('p');
232     }
233     span.id=this.domID(nodeId,'Desc');
234     span.className='ricoTreeLevel'+level;
235     switch (this.options.nodeIdDisplay) {
236       case 'last': nodeDesc+=' ('+nodeId+')'; break;
237       case 'first': nodeDesc=nodeId+' - '+nodeDesc; break;
238       case 'tooltip': span.title=nodeId; break;
239     }
240         span.appendChild(document.createTextNode(nodeDesc));
241     row.insertCell(-1).appendChild(span);
242
243     var parent=parentChildren || this.treeDiv;
244     parent.appendChild(tab);
245     parent.appendChild(div);
246   },
247
248   nodeClick: function(e) {
249     var node=Event.element(e);
250     if (this.returnValue) {
251       var t=this.domID('','Desc');
252       this.returnValue(node.id.substr(t.length),node.innerHTML);
253     }
254     this.close();
255   },
256
257   saveSelection: function(e) {
258     if (this.returnValue) {
259       this.returnValue(this.getCheckedItems());
260     }
261     this.close();
262   },
263
264   getCheckedItems: function() {
265     var inp=this.treeDiv.getElementsByTagName('input');
266     var vals=[];
267     for (var i=0; i<inp.length; i++) {
268       if (inp[i].type=='checkbox' && inp[i].checked) {
269         vals.push(inp[i].value);
270       }
271     }
272     return vals;
273   },
274
275   setCheckBoxes: function(val) {
276     var inp=this.treeDiv.getElementsByTagName('input');
277     for (var i=0; i<inp.length; i++) {
278       if (inp[i].type=='checkbox') {
279         inp[i].checked=val;
280       }
281     }
282   },
283
284   clrCheckBoxEvent: function(e) {
285     Event.stop(e);
286     this.setCheckBoxes(false);
287   },
288
289   clickBranch: function(e) {
290     var node=Event.element(e);
291     var tab=RicoUtil.getParentByTagName(node,'table');
292     if (!tab || !tab.TreeContainer) return;
293     var a=tab.id.split('_');
294     a[1]='Children';
295     var childDiv=$(a.join('_'));
296     Element.toggle(childDiv);
297     if (node.tagName=='IMG') {
298       var v=Element.visible(childDiv);
299       if (node.src.match(/node(p|m)(last)?\.gif$/)) {
300         node.src=node.src.replace(/nodep|nodem/,'node'+(v ? 'm' : 'p'));
301       } else if (node.src.match(/folder(open|closed)\.gif$/)) {
302         node.src=node.src.replace(/folder(open|closed)/,'folder'+(v ? 'open' : 'closed'));
303       } else if (node.src.match(/\b(m|p)\.gif$/)) {
304         node.src=node.src.replace(/(p|m)\.gif/,v ? 'm\.gif' : 'p\.gif');
305       }
306     }
307     if (!tab.TreeFetchedChildren) {
308       tab.TreeFetchedChildren=1;
309       this.loadXMLDoc(node.name);
310     }
311   }
312
313 });
314
315 Rico.includeLoaded('ricoTree.js');