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