1 /**
  2  * @author Andrew
  3  */
  4 
  5 Monk.component.WorksetTreeComponent = function(args) {
  6     
  7     this.loadDialog = null;
  8     
  9     this.saveDialog = null;
 10     
 11     this.cachedData = null;
 12     
 13     // following variables are used by the updateTree method
 14     // they're properties of the component so that they're accessible
 15     
 16     // temp hash of nodes to use for checking off tree nodes
 17     this.tempHash = [];
 18     
 19     // temp storage for nodes that were checked
 20     this.tempNodes = [];
 21     
 22     // temp hash to keep track of what collections the workset contains
 23     this.tempCollectionsList = [];
 24     
 25     Monk.component.WorksetTreeComponent.superclass.constructor.call(this, args);
 26 }
 27 
 28 Workbench.extend(Monk.component.WorksetTreeComponent, Workbench.component.Component, {
 29 
 30     label : "Workset Tree Manager",
 31     description : "For managing worksets of text chunks in Monk.",
 32     "window" : this.window,
 33 
 34     handle : function(monkEvent, data) {
 35         if (monkEvent.instanceOf(Workbench.event.ComponentLoaded)) {
 36             if (monkEvent.component == this) {
 37                 var data = Monk.component.dataManager.getWorklist();
 38                 var chunks = [];
 39                 for (var chunkId in data) {
 40                     var chunk = data[chunkId];
 41                     chunk.id = chunkId;
 42                     chunks.push(chunk);
 43                 }
 44                 if (chunks.length > 0) {
 45                     this.cachedData = chunks;
 46                 }
 47             }
 48         } else if (monkEvent.instanceOf(Monk.event.workset.WorksetLoaded)) {
 49             var worklist = Monk.component.dataManager.getWorklist();
 50             this.updateTree(worklist);
 51         } else if (monkEvent.instanceOf(Monk.event.workset.WorksetCreated)) {
 52             this.saveDialog.hide();
 53         } else if (monkEvent.instanceOf(Monk.event.workset.WorksetReset)) {
 54             // clear checks
 55             this.updateTree([]);
 56         }
 57     },
 58 
 59     init : function() {
 60         if (this.tree == null) {
 61             var metaData = Monk.data.collection.getMetaDataHierarchy();
 62             // customize the nodes for this tree: add a uiProvider
 63             for (var i = 0; i < metaData.length; i++) {
 64                 var collection = metaData[i];
 65                 collection.uiProvider = Ext.tree.ColumnNodeUI;
 66                 for (var j = 0; j < collection.children.length; j++) {
 67                     var node = collection.children[j];
 68                     node.uiProvider = Ext.tree.ColumnNodeUI;
 69                     node.userRating = '';
 70                     node.systemRating = '';
 71                 }
 72             }
 73             this.initializeTree(metaData);
 74             if (this.cachedData != null) {
 75                 this.updateTree(this.cachedData);
 76                 this.cachedData = null;
 77             }
 78         }
 79     },
 80 
 81     /**
 82      * Update the tree with a set of nodes to have checked
 83      * (all others will be unchecked)
 84      * @param {Object} worklist The nodes that should be checked
 85      */
 86     updateTree : function(worklist) {
 87             var maskedElement = Ext.get("worksetTree");
 88             maskedElement.mask("working...");
 89 
 90             this.updating = true;
 91 
 92             this.tempCollectionsList = {};
 93             this.tempHash = {};
 94             this.tempNodes = [];
 95             
 96             for (var chunkId in worklist) {
 97                 var collectionId = worklist[chunkId].collectionId;
 98                 if (this.tempCollectionsList[collectionId] == undefined) {
 99                     this.tempCollectionsList[collectionId] = true;
100                 }
101             }
102             
103             this.tempHash = worklist;
104             
105             this.recursiveNodeUpdater = function(node){
106                 var collection = node.attributes.collectionId;
107                 var tokens = node.attributes.id.split('-');
108                 if (tokens.length >= 2) {
109                     // it's a work or workpart
110                     
111                     // check if there are workset workparts within this node 
112                     var expand = false;
113                     for (var chunk in this.tempHash) {
114                         // append a dash to the node ID to prevent false positives 
115                         // the test workparts against it
116                         if (chunk.indexOf(node.attributes.id+'-') != -1) {
117                             expand = true;
118                             break;
119                         }
120                     }
121                     if (expand) {
122                         if (!node.isExpanded()) {
123                             node.expand(false, false, function(node) {
124                                 node.eachChild(
125                                     this.worksetTreeComponent.recursiveNodeUpdater,
126                                     this.worksetTreeComponent
127                                 );
128                             });
129                         } else { 
130                         }
131                     }
132                     
133                     if (node.getUI().isChecked() && !this.tempHash[node.attributes.id]) {
134                         node.getUI().setColumnValue(0, '');
135                         node.getUI().setColumnValue(1, '');
136                         node.getUI().toggleCheck(false);
137                     } else if (node.getUI().isChecked() && this.tempHash[node.attributes.id]) {
138                         var data = this.tempHash[node.attributes.id];
139                         if (data.systemRating) node.getUI().setColumnValue(0, data.systemRating);
140                         if (data.userRating) node.getUI().setColumnValue(1, data.userRating);
141                         // node's already checked so remove it from the list
142                         delete this.tempHash[node.attributes.id];
143                     } else if (!node.getUI().isChecked() && this.tempHash[node.attributes.id]) {
144                         var data = this.tempHash[node.attributes.id];
145                         if (data.systemRating) node.getUI().setColumnValue(0, data.systemRating);
146                         if (data.userRating) node.getUI().setColumnValue(1, data.userRating);
147                         
148                         node.getUI().toggleCheck(true);
149                         this.tempNodes.push({
150                             id: node.attributes.id,
151                             collectionId: node.attributes.collectionId,
152                             text: node.text,
153                             chunkType: node.attributes.chunkType
154                         });
155                         // remove nodes we've taken care of
156                         delete this.tempHash[node.attributes.id];
157                     } else {
158                         node.getUI().setColumnValue(0, '');
159                         node.getUI().setColumnValue(1, '');
160                     }
161                     
162 					if (this.updating) {
163 	                    var itemCount = 0;
164 	                    for (var id in this.tempHash) {
165 	                        itemCount++;
166 	                    }
167 	                    if (itemCount == 0) {
168 	                        // then we're done
169 	                        this.notify(
170 	                            new Monk.event.chunk.ChunksChecked({
171 	                                label: "Text chunks checked"
172 	                            }),
173 	                            this.tempNodes
174 	                        );
175 	                        
176 	                        this.tempNodes = [];
177 	                        this.updating = false;
178 	                    }
179 					}
180                     
181                 } else {
182                     // it's a collection branch
183                     // only expand a branch if it belongs to a collection that is in our collectionsList
184                     if (this.tempCollectionsList[collection]) node.expand(false, false);
185                 }
186             }
187             
188             // cascade through nodes, updating them as needed
189             this.tree.root.expand(false, false, function() {
190                 this.worksetTreeComponent.tree.root.cascade(
191                     this.worksetTreeComponent.recursiveNodeUpdater,
192                     this.worksetTreeComponent
193                 );
194             });
195             
196             maskedElement.unmask();
197     },
198     
199     initializeLoadDialog : function() {
200         var projectId = Monk.component.dataManager.getProjectId();
201         
202         var xmlReader = new Ext.data.XmlReader({
203                 record: 'workset',
204                 id: '@id'
205             }, [
206                 {name: 'id', mapping: '@id'},
207                 'label',
208                 'trainingList',
209                 'trainingListRating',
210                 'workList',
211                 'workListRating'
212             ]
213         );
214         
215         var worksetsStore = new Ext.data.Store({
216             reader: xmlReader,
217             proxy: new Ext.data.HttpProxy({
218                 url: Monk.data.PROXY_URL + 'get/ProjectManager.getWorksets',
219                 method: 'GET'
220             }),
221             baseParams: {projectId: projectId}
222         });
223         
224         var worksetsCombo = new Ext.form.ComboBox({
225             fieldLabel: 'Workset',
226             name: 'workset',
227             width: 175,
228             store: worksetsStore,
229             triggerAction: 'all',
230             allowBlank: false,
231             editable: false,
232             forceSelection: true,
233             displayField: 'label',
234             hiddenName: 'worksetId',
235             valueField: 'id'
236         });
237         
238         var loadForm = new Ext.form.FormPanel({
239             labelAlign: 'right',
240             labelWidth: 70,
241             border: false
242         });
243         
244         loadForm.add(worksetsCombo);
245         
246         this.loadDialog = new Ext.Window({
247             title: 'Load Workset',
248             height: 120,
249             width: 300,
250             modal: true,
251             shadow: true,
252             plain: true,
253             border: false,
254             collapsible: false,
255             items: loadForm,
256             buttons: [
257                 {
258                     text: 'Load',
259                     handler: function(){
260                         if (loadForm.getForm().isValid()) {
261                             var values = loadForm.getForm().getValues();
262                             var worksetId = values.worksetId;
263                             var record = worksetsStore.getById(worksetId);
264                             var label = record.get('label');
265                             var trainingList = record.get('trainingList');
266                             var trainingListRating = record.get('trainingListRating');
267                             var workList = record.get('workList');
268                             var workListRating = record.get('workListRating');
269                             
270                             var workset = {
271                                 id: worksetId,
272                                 label: label,
273                                 projectId: projectId,
274                                 trainingList: trainingList,
275                                 trainingListRating: trainingListRating,
276                                 workList: workList,
277                                 workListRating: workListRating
278                             }
279                             
280                             Monk.component.dataManager.setWorkset(workset);
281                             
282                             loadForm.getForm().reset();
283                             this.loadDialog.hide();
284                         }
285                     },
286                     scope: this
287                 },
288                 {
289                     text: 'Cancel',
290                     handler: function(){
291                         this.loadDialog.hide();
292                     },
293                     scope: this
294                 }
295             ]
296         });
297         
298         this.loadDialog.show();
299     },
300     
301     initializeSaveDialog : function() {
302         var worksetName = new Ext.form.TextField({
303             fieldLabel: 'Name',
304             name: 'name',
305             width: 175,
306             allowBlank: false
307         });
308         
309         var saveForm = new Ext.form.FormPanel({
310             labelAlign: 'right',
311             labelWidth: 70,
312             border: false
313         });
314         
315         saveForm.add(worksetName);
316         
317         this.saveDialog = new Ext.Window({
318             title: 'Save Workset',
319             height: 120,
320             width: 300,
321             modal: true,
322             shadow: true,
323             plain: true,
324             border: false,
325             collapsible: false,
326             items: saveForm,
327             buttons: [
328                 {
329                     text: 'Save',
330                     handler: function(){
331                         if (saveForm.getForm().isValid()) {
332                             var values = saveForm.getForm().getValues();
333                             var worksetName = values.name;
334                             this.createNewWorkset(worksetName);
335                             
336                             saveForm.getForm().reset();
337                         }
338                     },
339                     scope: this
340                 },
341                 {
342                     text: 'Cancel',
343                     handler: function(){
344                         this.saveDialog.hide();
345                     },
346                     scope: this
347                 }
348             ]
349         });
350         
351         this.saveDialog.show();
352     },
353     
354     /**
355      * Compiles the workset and saves it.
356      * @param {Boolean} saveAs If true, save workset as a copy
357      */
358     saveWorkset : function(saveAs) {
359         var worksetId = Monk.component.dataManager.getWorksetId();
360         if (worksetId == null || saveAs == true) {
361             // create new workset (or save a new copy)
362             if (this.saveDialog == null){
363                 this.initializeSaveDialog();
364             } else {
365                 this.saveDialog.show();
366             }
367         } else {
368             // save the workset
369             var workset = Monk.component.dataManager.getWorkset();
370             if (workset.trainingSet == null) {
371                 // dummy data
372                 workset.trainingSet = workset.worklist.split(',')[0];
373                 workset.trainingSetRating = 3;
374             }
375             if (workset.worklistRating == null) {
376                 var count = workset.worklist.split(',').length;
377                 var rating = "3";
378                 for (var i = 0; i < count - 1; i++){
379                     rating += ",3"
380                 }
381                 workset.worklistRating = rating;
382             }
383             Monk.data.workset.saveWorkset(workset);
384         }
385     },
386     
387     createNewWorkset : function(worksetName) {
388         Monk.component.dataManager.setWorksetName(worksetName); 
389         
390         // TODO: make sure training list is being sent with training list ratings
391         var workset = Monk.component.dataManager.getWorkset();
392         Monk.data.workset.createWorkset(workset);
393     },
394 
395     initializeTree : function(collectionsHierarchy) {
396        
397         // custom treeloader for handling XML
398         Ext.ux.CollectionTreeLoaderXML = function(args) {
399             Ext.ux.CollectionTreeLoaderXML.superclass.constructor.call(this, args);
400         }
401         
402         Workbench.extend(Ext.ux.CollectionTreeLoaderXML, Ext.tree.TreeLoader, {
403             processResponse : function(response, node, callback){
404                 try {
405                     var children = response.responseXML.getElementsByTagName('workpart');
406                     var collectionId = null;
407                     for (var i = 0; i < children.length; i++) {
408                         var child = children[i];
409                         if (child.nodeType == 1) {
410                             var label = child.getElementsByTagName('label')[0].childNodes[0].data;
411                             var id = child.getAttribute("id");
412                             var numChildren = parseInt(child.getAttribute("numChildren"));
413                             // parse the ID to get the collection ID
414                             if (collectionId == null) {
415                                 collectionId = id.match(/^(\w+)\-(.+)$/)[1];
416                             }
417                             
418                             var treeNode = null;
419                             if (numChildren > 0) {
420                                 var treeLoader = new Ext.ux.CollectionTreeLoaderXML({
421                                     requestMethod:'GET',
422                                     dataUrl:Monk.data.PROXY_URL + 'get/CorpusManager.getWorkInfo?corpus='+collectionId+'&id='+id,
423                                     baseAttrs: {checked: false, iconCls:'chunk-tree-node'},
424                                     uiProviders: {
425                                         'col': Ext.tree.ColumnNodeUI
426                                     }
427                                 });
428                                 treeNode = new Ext.tree.AsyncTreeNode({
429                                     workLabel: label,
430                                     uiProvider: Ext.tree.ColumnNodeUI,
431                                     id: id,
432                                     collectionId: collectionId,
433                                     userRating: '',
434                                     systemRating: '',
435                                     loader: treeLoader,
436                                     leaf: false,
437                                     checked: false,
438                                     iconCls:'chunk-tree-node'
439                                 });
440                             } else {
441                                 treeNode = new Ext.tree.TreeNode({
442                                     workLabel: label,
443                                     uiProvider: Ext.tree.ColumnNodeUI,
444                                     id: id,
445                                     collectionId: collectionId,
446                                     userRating: '',
447                                     systemRating: '',
448                                     leaf: true,
449                                     checked: false,
450                                     iconCls: 'chunk-tree-node'
451                                 });
452                             }
453                             node.appendChild(treeNode);
454                         }
455                     }
456                     
457                     if(typeof callback == "function"){
458                         callback(this, node);
459                     }
460                 }catch(e){
461                     alert(e);
462                     this.handleFailure(response);
463                 }
464             }
465         });
466         
467         var treeLoader = new Ext.ux.CollectionTreeLoaderXML({
468             requestMethod: 'GET',
469             dataUrl: Monk.data.PROXY_URL + 'get/CorpusManager.getWorkInfo',
470             baseAttrs: {checked: false, iconCls:'chunk-tree-node'},
471             uiProviders:{
472                 'col': Ext.tree.ColumnNodeUI
473             }
474         });
475         
476         treeLoader.on('beforeload', function(loader, node) {
477             loader.baseParams.corpus = node.attributes.collectionId;
478             loader.baseParams.id = node.attributes.id;
479         }, this);
480             
481         this.tree = new Ext.tree.ColumnTree({
482             el: 'worksetTree',
483             width: 535,
484             height: 500,
485             rootVisible: true,
486             autoScroll: true,
487             enableDD: false,
488             loader: treeLoader,
489             title: 'Workset',
490             tbar: [{
491                     text:'Load Workset',
492                     iconCls:'load-icon',
493                     handler: 
494                         function(){
495                             if (this.loadDialog == null) {
496                                 this.initializeLoadDialog();
497                             } else {
498                                 this.loadDialog.show();
499                             }
500                         },
501                     scope: this
502                 },{
503                     xtype:'tbseparator'
504                 },{
505                     text:'Save Workset',
506                     tooltip: 'Save Workset',
507                     iconCls:'save-icon',
508                     handler: 
509                         function() {
510 /*                        
511                             var json = this.tree.toJsonString(null,
512                                 function(key, val) {
513                                     return (key == 'id' || key =='userRating'|| key == 'systemRating');
514                                 }, {
515                                     userRating: 'userRating',
516                                     systemRating: 'systemRating'
517                                 }
518                             );
519                             alert(json);
520 */                            
521                             this.saveWorkset();
522                         },
523                         scope: this
524                 },{
525                     xtype:'tbseparator'
526                 },{
527                     text:'Save Workset As',
528                     iconCls:'save-as-icon',
529                     handler:
530                         function(){
531                             this.saveWorkset(true);
532                         },
533                         scope: this
534                 },{
535                     xtype:'tbseparator'
536                 }
537             ],
538             
539             columns:[{
540                 header: 'Workparts',
541                 width: 375,
542                 dataIndex: 'workLabel'
543             },{
544                 header: 'SysRating',
545                 width: 70,
546                 editable: false,
547                 dataIndex: 'systemRating'
548             },{
549                 header: 'UserRating',
550                 width: 70,
551                 dataIndex: 'userRating'
552             }],
553             
554             root : new Ext.tree.AsyncTreeNode({
555                 text: 'Monk Collections', 
556                 draggable: false, 
557                 id: 'collections-root',
558                 children: collectionsHierarchy,
559                 checked: false,
560                 expanded: true,
561                 iconCls: 'chunk-tree-node',
562                 uiProvider: Ext.tree.ColumnNodeUI
563             })
564         });
565         this.tree.render();
566         
567         this.tree.on("click", function(node, ev) {
568             if (node.hasChildNodes()) {
569                 if (this.tree.getTarget(ev.target) == 'tree') {
570                     node.toggle();
571                 }
572             } else {
573                 // only load text from leaf nodes
574                 this.handleNodeSelect(node);
575             }
576         }, this);
577         
578         this.tree.on("checkchange", function(node,isChecked) {
579             this.handleNodeChange(node, isChecked);
580         }, this);
581         
582         var te = new Ext.tree.ColumnTreeEditor(this.tree, {
583             editTree: false,
584             events: {
585                 completeOnEnter: true,
586                 ignoreNoChange: true
587             }
588         });
589     },
590     
591     handleNodeSelect : function(node) {
592         this.notify(
593             new Monk.event.chunk.ChunkSelected({
594                 label: 'Text chunk selected: '+'"'+node.text+'"',
595                 displayText: true
596             }),
597             {
598                 id: node.attributes.id,
599                 corpus: node.attributes.collectionId,
600                 text: node.text,
601                 chunkType: node.attributes.chunkType,
602                 displayText: true
603             }
604         );
605     },
606     
607     handleNodeChange : function(node, isChecked){
608         this.notify(
609             new Monk.event.chunk.ChunkChecked({
610                 label: "Text chunk "+(isChecked ? "" : "un")+'checked: "'+node.text+'"',
611                 checked: isChecked
612             }),
613             {
614                 id: node.attributes.id,
615                 corpus: node.attributes.collectionId,
616                 text: node.text,
617                 chunkType: node.attributes.chunkType,
618                 checked: isChecked
619             }
620         );
621     }
622 });
623