1 /**
  2  * @class Monk.component.CollectionsTreeBrowserComponent
  3  * @description A {@link Worbench.component.Component} for browsing text chunks.
  4  * @extends Workbench.component.Component
  5  * @author Stéfan Sinclair
  6  * @version 0.1
  7  * @since Monk 0.1
  8  */
  9 Monk.component.CollectionsTreeBrowserComponent = function(args) {
 10     
 11     // this is used when creating the tree, set it to a local file
 12     Ext.BLANK_IMAGE_URL = '../../../lib/ext-2.0/resources/images/default/s.gif';
 13     
 14     this.tree = null;
 15     
 16     this.dataMap = {};
 17     
 18     this.delayUpdatingLabel = false;
 19     
 20     this.updateLabels = {};
 21     
 22     this.higlightedNodeIds = {};
 23     
 24     this.lastWorksetLoadedTimestamp = 0;
 25     
 26     Monk.component.CollectionsTreeBrowserComponent.superclass.constructor.call(this, args);
 27 }
 28 
 29 Workbench.extend(Monk.component.CollectionsTreeBrowserComponent, Workbench.component.Component, {
 30 
 31     label : "Collections Tree Browser",
 32     description : "For Browsing Monk Text Collections.",
 33     "window" : this.window,
 34     isValid: function(){return true;},
 35     handle : function(monkEvent, data){
 36         if (monkEvent.instanceOf(Monk.event.chunk.ChunkChecked)) {
 37             this.reconcileData(data.id, data.checked);
 38         } else if (monkEvent.instanceOf(Workbench.event.ComponentLoaded) && monkEvent.component == this) {
 39 //			this.lastWorksetLoadedTimestamp = Monk.component.dataManager.getLastWorksetLoadedTimestamp();
 40 //			this.tree.setTitle(Monk.component.dataManager.getWorksetLabel())
 41 //			this.loadData(Monk.component.dataManager.getWorklist());
 42         }
 43         else if (monkEvent.instanceOf(Monk.event.workset.WorksetLoaded)) {
 44             // with bigger worksets this sometimes gets triggered after load, so check to ensure it's new
 45             if (data.timestamp > this.lastWorksetLoadedTimestamp) { // with bigger worksets this sometimes gets triggered after load
 46                 this.tree.setTitle(Monk.component.dataManager.getWorksetLabel());
 47                 this.resetAll();
 48                 this.loadData(Monk.component.dataManager.getWorklist());
 49             }
 50         }
 51         else if (monkEvent.instanceOf(Monk.event.workset.WorksetReset)) {
 52             this.tree.setTitle(Monk.component.dataManager.getWorksetLabel());
 53             this.resetAll();
 54         }
 55         else if (monkEvent.instanceOf(Monk.event.chunk.ChunksChecked)) {
 56             this.resetAll();
 57             var hashData = {};
 58             for (var i=0;i<data.length;i++) {hashData[data[i].id] = data[i];}
 59             this.loadData(hashData);
 60         }
 61         else if (monkEvent.instanceOf(Monk.event.chunk.ChunksRated)) {
 62             // note that this doesn't effect other chunks checked
 63             var hashData = {};
 64             for (var i=0;i<data.length;i++) {hashData[data[i].id] = data[i];}
 65             this.loadData(hashData);
 66         } else if (monkEvent.instanceOf(Monk.event.workbench.SimpleSearchQuery)) {
 67             Monk.data.chunk.getChunksContainingFeature({
 68                 lemmaPatternCriterion: data.featureValue
 69             });
 70         } else if (monkEvent.instanceOf(Monk.event.workbench.AdvancedSearchQuery)) {
 71             Monk.data.chunk.getChunksContainingFeature(data[0]);
 72         } else if (monkEvent.instanceOf(Monk.event.chunk.ChunksContainingFeatureRetrieved)) {
 73             this.clearHighlighting();
 74             var workElements = data.responseXML.getElementsByTagName('work');
 75             for (var i=0;i<workElements.length;i++) {
 76                 var workId = workElements[i].getAttribute('id');
 77                 this.highlight(workId, 1);
 78             }
 79 			var clearSearch = document.getElementById("clearSearch");
 80 			if (clearSearch) {clearSearch.style.display="";}
 81         }
 82 		else if (monkEvent.instanceOf(Monk.event.chunk.ChunkSelected) && monkEvent.component.window!=this.window) {
 83             this.clearHighlighting();
 84 			this.highlight(data.id, 1)
 85 			var work = this.tree.getNodeById(data.id);
 86 			if (work) {work.getUI().getAnchor().scrollIntoView();}
 87 		}
 88     },
 89     
 90     clearHighlighting : function() {
 91         for (var key in this.higlightedNodeIds) {
 92             if (this.dataMap[key].highlight) {
 93                 this.dataMap[key].highlight = 0;
 94                 this.updateDataNodeLabel(this.dataMap[key]);
 95             }
 96         }
 97         this.higlightedNodeIds = {};
 98     },
 99     
100     highlight : function(nodeId, hits) {
101         var dataNode = this.dataMap[nodeId];
102         if (dataNode) {
103             if (!dataNode.highlight) {
104                 dataNode.highlight = 0;
105             }
106             dataNode.highlight+=hits;
107             
108             if (dataNode.treeNode) {
109                 this.updateDataNodeLabel(dataNode);
110             }
111         }
112         else {
113             this.dataMap[nodeId] = {id : nodeId, containsCheckedCount : 0, highlight : hits};
114         }
115         this.higlightedNodeIds[nodeId] = true;
116         
117         if (nodeId != "monk") {
118             nodeId = nodeId.substring(0,nodeId.lastIndexOf("-"));
119             if (nodeId == "") {nodeId="monk";} // we're at the top, so let's update monk
120             this.highlight(nodeId, hits);
121         }
122 
123     },
124     
125     resetAll : function() {
126         for (var key in this.dataMap) {
127             this.dataMap[key].checked = false;
128             if (this.dataMap[key].treeNode) {
129                 if (this.dataMap[key].treeNode.ui.isChecked()) {
130                     this.dataMap[key].treeNode.ui.toggleCheck(false);
131                 }
132                 if (this.dataMap[key].containsCheckedCount>0 || this.dataMap[key].highlight) {
133                     this.dataMap[key].containsCheckedCount = 0;
134                     this.dataMap[key].highlight = 0;
135                     this.updateDataNodeLabel(this.dataMap[key]);				
136                 }
137             } 
138             else {
139                 this.dataMap[key].containsCheckedCount = 0;
140             }
141         }
142     },
143     
144     init : function(config) {
145         Ext.QuickTips.init();
146 		var clearSearch = document.getElementById("clearSearch");
147 		if (clearSearch) {clearSearch.style.display="none";}
148         if (this.tree == null) {
149             var metaData = Monk.data.collection.getMetaDataHierarchy();
150 
151             // add listeners to the collection nodes
152             var listeners = {
153                 'uberbeforeexpand': {
154                     fn: function(node) {
155                         node.beforeloading = true;
156                         if (!this.dataMap[node.id]) {
157                             this.dataMap[node.id] = {id : node.id, treeNode : node, containsCheckedCount : 0};
158                         }
159                         this.updateDataNodeLabel(this.dataMap[node.id]);
160                     },
161                     scope: this
162                 },
163                 'expand': {
164                     fn: function(node, deep, anim) {
165                         node.beforeloading = false;
166                         this.updateDataNodeLabel(this.dataMap[node.id]);
167                     },
168                     scope: this
169                 }
170             };
171             for (var i = 0, length = metaData.length; i < length; i++) {
172                 var collection = metaData[i];
173                 collection.listeners = listeners;
174             }
175             
176             this.initializeTree(metaData, config);
177         }
178     },
179     
180     loadData : function(data) {
181         this.delayUpdatingLabel = true;
182         this.updateLabels = {};
183         for (key in data) {
184             this.reconcileData(key, true);
185         }
186         this.updateNodeLabels();
187         this.delayUpdatingLabel = false;
188     },
189     
190     reconcileNode : function(node) {
191         node.originalText = node.text; // make a copy for temporary labels
192         if (this.dataMap[node.id]) {
193             var dataNode = this.dataMap[node.id];
194             if (dataNode.checked) {
195                 if (!node.ui.rendered) {
196                     node.ui.render(); // make sure it's rendered
197                 }
198                 node.ui.toggleCheck(true); // we don't want this to fire an event
199             }
200             dataNode.treeNode = node;
201         }
202         else {
203             this.dataMap[node.id] = {id : node.id, treeNode : node, containsCheckedCount : 0};
204         }
205         this.updateDataNodeLabel(this.dataMap[node.id]);
206     },
207     
208     reconcileData : function(key, isChecked) {
209         if (this.dataMap[key]) {
210             var dataNode = this.dataMap[key];
211             dataNode.checked = isChecked;
212             if (dataNode.treeNode && dataNode.treeNode.ui.isChecked()!=isChecked) {
213                 dataNode.treeNode.ui.toggleCheck(isChecked)
214             }
215 //			else {return}
216         }
217         else {
218             this.dataMap[key] = {id : key, checked : isChecked, containsCheckedCount : 0};
219         }
220         this.checkParentsContains(key, isChecked);
221     },
222     
223     checkParentsContains : function(nodeId, isChecked) {
224         nodeId = nodeId.substring(0,nodeId.lastIndexOf("-"));
225         if (nodeId == "") {nodeId="monk";} // we're at the top, so let's update monk
226         
227         var dataNode = this.dataMap[nodeId];
228         if (dataNode) {
229             dataNode.containsCheckedCount += isChecked ? 1 : -1;
230             if (dataNode.treeNode) {
231                 if (this.delayUpdatingLabel) {this.updateLabels[nodeId]=true;}
232                 else {this.updateDataNodeLabel(dataNode);}
233             }
234         } else {
235             this.dataMap[nodeId] = {id : nodeId, containsCheckedCount : isChecked ? 1 : 0};
236         }
237         if (nodeId != "monk") {
238             this.checkParentsContains(nodeId, isChecked);
239         }
240     },
241     
242     updateNodeLabels : function() {
243         for (key in this.updateLabels) {
244             this.updateDataNodeLabel(this.dataMap[key]);
245         }
246     },
247 
248     updateDataNodeLabel : function(node) {
249         if (node && node.treeNode && node.id) {
250             var text = node.treeNode.originalText;
251             var dataNode = this.dataMap[node.id]
252             if (!node.treeNode.rendered) {
253                 node.treeNode.render();
254             }
255             if (node.highlight) {
256                 var hit = node.highlight > 1 ? 'hits' : 'hit';
257                 text = "<span class='highlight'>("+node.highlight+" "+hit+") " +text+"</span>";
258             }
259             if (node.containsCheckedCount>0) {
260                 text = "<span class='contains'>"+text+"</span> ("+node.containsCheckedCount+")";
261             }
262             if (!node.treeNode.isLeaf() && node.treeNode.parentNode.isRoot == null) {
263                 text += " <img src='ok.png' onclick='checkChildren(\""+node.id+"\", true); return false;' ext:qtip='Click here to check all child chunks (may take awhile).' /> <img src='cancel.png' onclick='checkChildren(\""+node.id+"\", false); return false;' ext:qtip='Click here to uncheckk all child chunks (may take awhile).' />"
264             }
265             if (node.treeNode.beforeloading) {
266                 text += " <b>Loading...</b>";
267             }
268             node.treeNode.setText(text);
269         }
270     },
271     
272     checkChildren : function(id, isChecked) {
273         var node = this.dataMap[id];
274         var children = [];
275         if (node && node.treeNode) {
276             function recursiveCheck(treeNode) {
277                 for (var i=0;i<treeNode.childNodes.length;i++) {
278                     var childNode = treeNode.childNodes[i];
279                     if (childNode.isLeaf() == false) {
280                         this.checkChildren(childNode.id, isChecked);
281                     } else if (childNode.ui.isChecked()==isChecked) {
282                         // commented out because it messes up the count when check all/check none is clicked multiple times
283                         //this.checkParentsContains(childNode.id, !isChecked);
284                     } else if (childNode.ui.isChecked()!=isChecked) {
285                         this.notify(
286                             new Monk.event.chunk.ChunkChecked({
287                                 label: "Text chunk "+(isChecked ? "" : "un")+'checked: "'+childNode.originalText+'"',
288                                 checked: isChecked
289                             }), {
290                                 id: childNode.attributes.id,
291                                 corpus: childNode.attributes.collectionId,
292                                 text: childNode.originalText,
293                                 chunkType: childNode.attributes.chunkType,
294                                 checked: isChecked
295                             }
296                         );
297                     }
298                 }
299             }
300             if (node.treeNode.isExpanded()) {
301                 var r = recursiveCheck.createDelegate(this);
302                 r(node.treeNode);
303             } else {
304                 node.treeNode.on('expand', recursiveCheck, this, {single: true});
305                 node.treeNode.expand();
306             }
307         }
308     },
309 
310     
311     initializeTree : function(collectionsHierarchy, config) {
312         
313         // custom treeloader for handling XML
314         Ext.ux.CollectionTreeLoaderXML = function(args) {
315             Ext.ux.CollectionTreeLoaderXML.superclass.constructor.call(this, args);
316         }
317         
318         Workbench.extend(Ext.ux.CollectionTreeLoaderXML, Ext.tree.TreeLoader, {
319             createNode : function(attr){
320                 var collection = attr.chunkType == 'collection' ? true : false;
321                 if(this.baseAttrs){
322                     Ext.applyIf(attr, this.baseAttrs);
323                 }
324                 if(this.applyLoader !== false){
325                     attr.loader = this;
326                 }
327                 if(typeof attr.uiProvider == 'string'){
328                    attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);
329                 }
330                 return(collection ?
331                                 new Ext.ux.DelayedAsyncTreeNode(attr) :
332                                 new Ext.tree.AsyncTreeNode(attr));
333             },
334             processResponse : function(response, node, callback){
335                 try {
336                     var children = response.responseXML.getElementsByTagName('workpart');
337                     var collectionId = null;
338                     
339                     for (var i = 0; i < children.length; i++) {
340                         var child = children[i];
341                         if (child.nodeType == 1) {
342                             var label = child.getElementsByTagName('label')[0].childNodes[0].data;
343                             var id = child.getAttribute("id");
344                             var numChildren = parseInt(child.getAttribute("numChildren"));
345                             
346                             // parse the ID to get the collection ID
347                             if (collectionId == null) {
348                                 collectionId = id.match(/^(\w+)\-(.+)$/)[1];
349                             }
350                             
351                             var treeNode = null;
352                             if (numChildren > 0) {
353                                 var treeLoader = new Ext.ux.CollectionTreeLoaderXML({
354                                     requestMethod:'GET',
355                                     dataUrl:Monk.data.PROXY_URL + 'get/CorpusManager.getWorkInfo?corpus='+collectionId+'&id='+id,
356                                     baseAttrs: {checked: false, iconCls:'chunk-tree-node'}
357                                 });
358                                 treeNode = new Ext.tree.AsyncTreeNode({
359                                     text: label,
360                                     id: id,
361                                     collectionId: collectionId,
362                                     loader: treeLoader,
363                                     leaf: false,
364                                     iconCls:'chunk-tree-node'
365                                 });
366                             } else {
367                                 treeNode = new Ext.tree.TreeNode({
368                                     text: label,
369                                     id: id,
370                                     collectionId: collectionId,
371                                     leaf: true,
372                                     checked: false,
373                                     iconCls: 'chunk-tree-node'
374                                 });
375                             }
376                             node.appendChild(treeNode);
377                         }
378                     }
379                     
380                     if(typeof callback == "function"){
381                         callback(this, node);
382                     }
383                 }catch(e){
384                     this.handleFailure(response);
385                 }
386             }
387         });
388         
389         var treeLoader = new Ext.ux.CollectionTreeLoaderXML({
390             requestMethod: 'GET',
391             dataUrl: Monk.data.PROXY_URL + 'get/CorpusManager.getWorkInfo',
392             baseAttrs: {iconCls:'chunk-tree-node'}
393         });
394         
395         treeLoader.on('beforeload', function(loader, node) {
396             loader.baseParams.corpus = node.attributes.collectionId;
397             loader.baseParams.id = node.attributes.id;
398         }, this);
399         
400         var tree = {
401             id: 'collections-tree',
402             xtype: 'treepanel',
403             animate: true,
404             border: false,
405             loader: treeLoader,
406             enableDD: false,
407             autoScroll: true,
408             title : "Workset",
409             root: new Ext.tree.AsyncTreeNode({
410                 text: 'Monk Collections',
411                 originalText: 'Monk Collections',
412                 draggable: false, 
413                 expanded: true,
414                 id: 'collections-root',
415                 chunkType: 'root',
416                 children: collectionsHierarchy,
417                 iconCls: 'chunk-tree-node'
418             }),
419             listeners: {
420                 render: {
421                     fn: function(tree){
422                         this.lastWorksetLoadedTimestamp = Monk.component.dataManager.getLastWorksetLoadedTimestamp();
423                         tree.setTitle(Monk.component.dataManager.getWorksetLabel())
424                         this.loadData(Monk.component.dataManager.getWorklist());
425                     }, scope: this
426                 },
427                 append: {
428                     fn: function(tree, parentNode, node) {
429                         this.reconcileNode(node);
430 						if (node.ui.checkbox) {
431 							Ext.QuickTips.register({
432 								target: node.ui.checkbox,
433 								title: 'Select the work part and add it to the workset',
434 								trackMouse : true,
435 								showDelay : 0
436 							})
437 						}
438                     }, scope: this
439                 },
440                 checkchange: {
441                     fn: function(node, isChecked) {
442                         this.notify(
443                             new Monk.event.chunk.ChunkChecked({
444                                 label: "Text chunk "+(isChecked ? "" : "un")+'checked: "'+node.text+'"',
445                                 checked: isChecked
446                             }),
447                             {
448                                 id: node.attributes.id,
449                                 corpus: node.attributes.collectionId,
450                                 text: node.originalText,
451                                 chunkType: node.attributes.chunkType,
452                                 checked: isChecked
453                             }
454                         );
455                     }, scope: this
456                 },
457                 click: {
458                     fn: function(node, ev) {
459                         if (ev.target.nodeName.toUpperCase() != 'IMG') {
460                             this.notify(
461                                 new Monk.event.chunk.ChunkSelected({
462                                     label: 'Text chunk selected: '+'"'+node.text+'"'
463                                 }),
464                                 {
465                                     id: node.attributes.id,
466                                     corpus: node.attributes.collectionId,
467                                     text: node.text,
468                                     chunkType: node.attributes.chunkType
469                                 }
470                             );
471                         }
472                     }, scope: this
473                 }
474             }
475         };
476 
477         // render the tree
478         var viewport = new Ext.Viewport({
479             layout: 'fit',
480             renderTo: 'treeParent',
481             items: tree
482         });
483         
484         this.tree = Ext.getCmp('collections-tree');
485         this.dataMap["monk"] = {checked : false, containsCheckedCount : 0, treeNode : this.tree.root};
486     }
487 });
488