cleaned up some of the status display handling
[Evergreen.git] / Open-ILS / web / vandelay / vandelay.js
1 /* ---------------------------------------------------------------------------
2 # Copyright (C) 2008  Georgia Public Library Service
3 # Bill Erickson <erickson@esilibrary.com>
4
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 # --------------------------------------------------------------------------- */
15 dojo.require("dojo.parser");
16 dojo.require("dojo.io.iframe"); 
17 dojo.require("dijit.ProgressBar"); 
18 dojo.require("dijit.form.Button"); 
19 dojo.require("dijit.form.FilteringSelect"); 
20 dojo.require("dijit.layout.ContentPane");
21 dojo.require("dijit.layout.TabContainer");
22 dojo.require("dojo.cookie");
23 dojo.require("dojox.grid.Grid");
24 dojo.require("dojo.data.ItemFileReadStore");
25 dojo.require('dojo.date.locale');
26 dojo.require('dojo.date.stamp');
27 dojo.require("fieldmapper.Fieldmapper");
28 dojo.require("fieldmapper.dojoData");
29 dojo.require('openils.CGI');
30 dojo.require('openils.User');
31 dojo.require('openils.Event');
32
33 var globalDivs = [
34     'vl-generic-progress',
35     'vl-generic-progress-with-total',
36     'vl-marc-upload-div',
37     'vl-queue-div',
38     'vl-match-div',
39     'vl-match-html-div',
40     'vl-queue-select-div',
41     'vl-marc-upload-status-div'
42 ];
43
44 var authtoken;
45 var VANDELAY_URL = '/vandelay';
46 var bibAttrDefs = [];
47 var authAttrDefs = [];
48 var queuedRecords = [];
49 var queuedRecordsMap = {};
50 var bibAttrsFetched = false;
51 var authAttrsFetched = false;
52 var attrDefMap = {}; // maps attr def code names to attr def ids
53 var currentType;
54 var currentQueueId = null;
55 var userCache = {};
56 var currentMatchedRecords; // set of loaded matched bib records
57 var currentOverlayRecordsMap; // map of import record to overlay record
58 var currentImportRecId; // when analyzing matches, this is the current import record
59 var userBibQueues;
60 var userAuthQueues;
61 var selectableGridRecords;
62 var cgi = new openils.CGI();
63
64 /**
65   * Grab initial data
66   */
67 function vlInit() {
68     authtoken = dojo.cookie('ses') || cgi.param('ses');
69     var initNeeded = 4; // how many async responses do we need before we're init'd 
70     var initCount = 0; // how many async reponses we've received
71
72     function checkInitDone() {
73         initCount++;
74         if(initCount == initNeeded)
75             runStartupCommands();
76     }
77
78     // Fetch the bib and authority attribute definitions
79     fieldmapper.standardRequest(
80         ['open-ils.permacrud', 'open-ils.permacrud.search.vqbrad'],
81         {   async: true,
82             params: [authtoken, {id:{'!=':null}}],
83             onresponse: function(r) {
84                 var def = r.recv().content(); 
85                 if(e = openils.Event.parse(def[0])) 
86                     return alert(e);
87                 bibAttrDefs.push(def);
88             },
89             oncomplete: function() {
90                 bibAttrDefs = bibAttrDefs.sort(
91                     function(a, b) {
92                         if(a.id() > b.id()) return 1;
93                         if(a.id() < b.id()) return -1;
94                         return 0;
95                     }
96                 );
97                 checkInitDone();
98             }
99         }
100     );
101
102     fieldmapper.standardRequest(
103         ['open-ils.permacrud', 'open-ils.permacrud.search.vqarad'],
104         {   async: true,
105             params: [authtoken, {id:{'!=':null}}],
106             onresponse: function(r) {
107                 var def = r.recv().content(); 
108                 if(e = openils.Event.parse(def[0])) 
109                     return alert(e);
110                 authAttrDefs.push(def);
111             },
112             oncomplete: function() {
113                 authAttrDefs = authAttrDefs.sort(
114                     function(a, b) {
115                         if(a.id() > b.id()) return 1;
116                         if(a.id() < b.id()) return -1;
117                         return 0;
118                     }
119                 );
120                 checkInitDone();
121             }
122         }
123     );
124
125     fieldmapper.standardRequest(
126         ['open-ils.vandelay', 'open-ils.vandelay.bib_queue.owner.retrieve.atomic'],
127         {   async: true,
128             params: [authtoken],
129             oncomplete: function(r) {
130                 var list = r.recv().content();
131                 if(e = openils.Event.parse(list[0]))
132                     return alert(e);
133                 userBibQueues = list;
134                 checkInitDone();
135             }
136         }
137     );
138
139     fieldmapper.standardRequest(
140         ['open-ils.vandelay', 'open-ils.vandelay.authority_queue.owner.retrieve.atomic'],
141         {   async: true,
142             params: [authtoken],
143             oncomplete: function(r) {
144                 var list = r.recv().content();
145                 if(e = openils.Event.parse(list[0]))
146                     return alert(e);
147                 userAuthQueues = list;
148                 checkInitDone();
149             }
150         }
151     );
152 }
153
154 function displayGlobalDiv(id) {
155     for(var i = 0; i < globalDivs.length; i++) {
156         try {
157             dojo.style(dojo.byId(globalDivs[i]), 'display', 'none');
158         } catch(e) {
159             alert('please define ' + globalDivs[i]);
160         }
161     }
162     dojo.style(dojo.byId(id),'display','block');
163 }
164
165 function runStartupCommands() {
166     currentQueueId = cgi.param('qid');
167     currentType = cgi.param('qtype');
168     if(currentQueueId)
169         return retrievenueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
170     vlShowUploadForm();
171 }
172
173 /**
174   * asynchronously upload a file of MARC records
175   */
176 function uploadMARC(onload){
177     dojo.byId('vl-ses-input').value = authtoken;
178     displayGlobalDiv('vl-marc-upload-status-div');
179     dojo.io.iframe.send({
180         url: VANDELAY_URL,
181         method: "post",
182         handleAs: "html",
183         form: dojo.byId('vl-marc-upload-form'),
184         handle: function(data,ioArgs){
185             var content = data.documentElement.textContent;
186             onload(content);
187         }
188     });
189 }       
190
191 /**
192   * Creates a new vandelay queue
193   */
194 function createQueue(queueName, type, onload) {
195     fieldmapper.standardRequest(
196         ['open-ils.vandelay', 'open-ils.vandelay.'+type+'_queue.create'],
197         {   async: true,
198             params: [authtoken, queueName, null, type],
199             oncomplete : function(r) {
200                 var queue = r.recv().content();
201                 if(e = openils.Event.parse(queue)) 
202                     return alert(e);
203                 onload(queue);
204             }
205         }
206     );
207 }
208
209 /**
210   * Tells vendelay to pull a batch of records from the cache and explode them
211   * out into the vandelay tables
212   */
213 function processSpool(key, queueId, type, onload) {
214     fieldmapper.standardRequest(
215         ['open-ils.vandelay', 'open-ils.vandelay.'+type+'.process_spool'],
216         {   async: true,
217             params: [authtoken, key, queueId],
218             oncomplete : function(r) {
219                 var resp = r.recv().content();
220                 if(e = openils.Event.parse(resp)) 
221                     return alert(e);
222                 onload();
223             }
224         }
225     );
226 }
227
228 function retrieveQueuedRecords(type, queueId, onload) {
229     displayGlobalDiv('vl-generic-progress');
230     queuedRecords = [];
231     queuedRecordsMap = {};
232     currentOverlayRecordsMap = {};
233     selectableGridRecords = {};
234     resetVlQueueGridLayout();
235
236     fieldmapper.standardRequest(
237         ['open-ils.vandelay', 'open-ils.vandelay.'+type+'_queue.records.retrieve.atomic'],
238         {   async: true,
239             params: [authtoken, queueId, {clear_marc:1}],
240             /* intermittent bug in streaming, multipart requests prevents use of onreponse for now...
241             onresponse: function(r) {
242                 var rec = r.recv().content();
243                 if(e = openils.Event.parse(rec))
244                     return alert(e);
245                 queuedRecords.push(rec);
246                 queuedRecordsMap[rec.id()] = rec;
247             },
248             */
249             oncomplete: function(r){
250                 var recs = r.recv().content();
251                 if(e = openils.Event.parse(recs[0]))
252                     return alert(e);
253                 for(var i = 0; i < recs.length; i++) {
254                     var rec = recs[i];
255                     queuedRecords.push(rec);
256                     queuedRecordsMap[rec.id()] = rec;
257                 }
258                 onload();
259             }
260         }
261     );
262 }
263
264 function vlLoadMatchUI(recId, attrCode) {
265     displayGlobalDiv('vl-generic-progress');
266     var matches = getRecMatchesFromAttrCode(queuedRecordsMap[recId], attrCode);
267     var records = [];
268     currentImportRecId = recId;
269     for(var i = 0; i < matches.length; i++)
270         records.push(matches[i].eg_record());
271     fieldmapper.standardRequest(
272         ['open-ils.search', 'open-ils.search.biblio.record_entry.slim.retrieve'],
273         {   async: true,
274             params:[records],
275             oncomplete: function(r) {
276                 var recs = r.recv().content();
277                 if(e = openils.Event.parse(recs))
278                     return alert(e);
279                 displayGlobalDiv('vl-match-div');
280                 resetVlMatchGridLayout();
281                 currentMatchedRecords = recs;
282                 vlMatchGrid.setStructure(vlMatchGridLayout);
283                 var dataStore = bre.toStoreData(recs, null, {virtualFields:['field_type']});
284                 for(var i = 0; i < dataStore.items.length; i++) {
285                     var item = dataStore.items[i];
286                     for(var j = 0; j < matches.length; j++) {
287                         var match = matches[j];
288                         if(match.eg_record() == item.id)
289                             item.field_type = match.field_type();
290                     }
291                 }
292                 var store = new dojo.data.ItemFileReadStore({data:dataStore});
293                 var model = new dojox.grid.data.DojoData(
294                     null, store, {rowsPerPage: 100, clientSort: true, query:{id:'*'}});
295                 vlMatchGrid.setModel(model);
296                 vlMatchGrid.update();
297             }
298         }
299     );
300 }
301
302
303 function vlLoadMARCHtml(recId) {
304     displayGlobalDiv('vl-generic-progress');
305     fieldmapper.standardRequest(
306         ['open-ils.search', 'open-ils.search.biblio.record.html'],
307         {   async: true,
308             params: [recId, 1],
309             oncomplete: function(r) {
310             displayGlobalDiv('vl-match-html-div');
311                 var html = r.recv().content();
312                 dojo.byId('vl-match-record-html').innerHTML = html;
313             }
314         }
315     );
316 }
317
318
319 /**
320   * Given a record, an attribute definition code, and a matching record attribute,
321   * this will determine if there are any import matches and build the UI to
322   * represent those matches.  If no matches exist, simply returns the attribute value
323   */
324 function buildAttrColumnUI(rec, attrCode, attr) {
325     var matches = getRecMatchesFromAttrCode(rec, attrCode);
326     if(matches.length > 0) { // found some matches
327         return '<div class="match_div">' +
328             '<a href="javascript:void(0);" onclick="vlLoadMatchUI('+
329             rec.id()+',\''+attrCode+'\');">'+ 
330             attr.attr_value() + '&nbsp;('+matches.length+')</a></div>';
331     }
332
333     return attr.attr_value();
334 }
335
336 function getRecMatchesFromAttrCode(rec, attrCode) {
337     var matches = [];
338     var attr = getRecAttrFromCode(rec, attrCode);
339     for(var j = 0; j < rec.matches().length; j++) {
340         var match = rec.matches()[j];
341         if(match.matched_attr() == attr.id()) 
342             matches.push(match);
343     }
344     return matches;
345 }
346
347 function getRecAttrFromCode(rec, attrCode) {
348     var defId = attrDefMap[attrCode];
349     var attrs = rec.attributes();
350     for(var i = 0; i < attrs.length; i++) {
351         var attr = attrs[i];
352         if(attr.field() == defId) 
353             return attr;
354     }
355     return null;
356 }
357
358 function getAttrValue(rowIdx) {
359     var data = this.grid.model.getRow(rowIdx);
360     if(!data) return '';
361     var attrCode = this.field.split('.')[1];
362     console.log(attrCode + " : " + data.id + ' : ' + queuedRecordsMap[data.id] + " : count = " + queuedRecords.length);
363     var rec = queuedRecordsMap[data.id];
364     var attr = getRecAttrFromCode(rec, attrCode);
365     if(attr)
366         return buildAttrColumnUI(rec, attrCode, attr);
367     return '';
368 }
369
370 function vlGetDateTimeField(rowIdx) {
371     data = this.grid.model.getRow(rowIdx);
372     if(!data) return '';
373     if(!data[this.field]) return '';
374     var date = dojo.date.stamp.fromISOString(data[this.field]);
375     return dojo.date.locale.format(date, {selector:'date'});
376 }
377
378 function vlGetCreator(rowIdx) {
379     data = this.grid.model.getRow(rowIdx);
380     if(!data) return '';
381     var id = data.creator;
382     if(userCache[id])
383         return userCache[id].usrname();
384     var user = fieldmapper.standardRequest(['open-ils.actor', 'open-ils.actor.user.retrieve'], [authtoken, id]);
385     if(e = openils.Event.parse(user))
386         return alert(e);
387     userCache[id] = user;
388     return user.usrname();
389 }
390
391 function vlGetViewMARC(rowIdx) {
392     data = this.grid.model.getRow(rowIdx);
393     if(data) 
394         return this.value.replace('RECID', data.id);
395 }
396
397 function vlGetOverlayTargetSelector(rowIdx) {
398     data = this.grid.model.getRow(rowIdx);
399     if(data) {
400         var value = this.value.replace('ID', data.id);
401         var overlay = currentOverlayRecordsMap[currentImportRecId];
402         if(overlay && overlay == data.id) 
403             value = value.replace('/>', 'checked="checked"/>');
404         return value;
405     }
406 }
407
408 /**
409   * see if the user has enabled overlays for the current match set and, 
410   * if so, map the current import record to the overlay target.
411   */
412 function vlHandleOverlayTargetSelected() {
413     if(vlOverlayTargetEnable.checked) {
414         for(var i = 0; i < currentMatchedRecords.length; i++) {
415             var matchRecId = currentMatchedRecords[i].id();
416             if(dojo.byId('vl-overlay-target-'+matchRecId).checked) {
417                 console.log("found overlay target " + matchRecId);
418                 currentOverlayRecordsMap[currentImportRecId] = matchRecId;
419                 dojo.byId('vl-record-list-selected-' + currentImportRecId).checked = true;
420                 dojo.byId('vl-record-list-selected-' + currentImportRecId).parentNode.className = 'overlay_selected';
421                 return;
422             }
423         }
424     } else {
425         delete currentOverlayRecordsMap[currentImportRecId];
426         dojo.byId('vl-record-list-selected-' + currentImportRecId).checked = false;
427     }
428 }
429
430 function buildRecordGrid(type) {
431     displayGlobalDiv('vl-queue-div');
432
433     currentOverlayRecordsMap = {};
434
435     if(queuedRecords.length == 0) {
436         dojo.style(dojo.byId('vl-queue-no-records'), 'display', 'block');
437         dojo.style(dojo.byId('vl-queue-div-grid'), 'display', 'none');
438         return;
439     } else {
440         dojo.style(dojo.byId('vl-queue-no-records'), 'display', 'none');
441         dojo.style(dojo.byId('vl-queue-div-grid'), 'display', 'block');
442     }
443
444     var defs = (type == 'bib') ? bibAttrDefs : authAttrDefs;
445     for(var i = 0; i < defs.length; i++) {
446         var def = defs[i]
447         attrDefMap[def.code()] = def.id();
448         var col = {
449             name:def.description(), 
450             field:'attr.' + def.code(),
451             get: getAttrValue
452         };
453         //if(def.code().match(/title/i)) col.width = 'auto'; // this is hack.
454         vlQueueGridLayout[0].cells[0].push(col);
455     }
456
457     var storeData;
458     if(type == 'bib')
459         storeData = vqbr.toStoreData(queuedRecords);
460     else
461         storeData = vqar.toStoreData(queuedRecords);
462
463     var store = new dojo.data.ItemFileReadStore({data:storeData});
464     var model = new dojox.grid.data.DojoData(
465         null, store, {rowsPerPage: 100, clientSort: true, query:{id:'*'}});
466
467     vlQueueGrid.setModel(model);
468     vlQueueGrid.setStructure(vlQueueGridLayout);
469     vlQueueGrid.update();
470 }
471
472 /*
473 function test() {
474     alert(vlQueueGridLayout.picker);
475     vlQueueGridLayout.oils = {};
476     vlQueueGridLayout[0].cells[0].pop();
477     vlQueueGridLayout[0].cells[0].pop();
478     vlQueueGridLayout[0].cells[0].pop();
479     vlQueueGridLayout[0].cells[0].pop();
480     vlQueueGridLayout[0].cells[0].pop();
481     vlQueueGrid.setStructure(vlQueueGridLayout);
482     vlQueueGrid.update();
483 }
484 */
485
486 function vlQueueGridDrawSelectBox(rowIdx) {
487     var data = this.grid.model.getRow(rowIdx);
488     if(!data) return '';
489     var domId = 'vl-record-list-selected-' +data.id;
490     selectableGridRecords[domId] = data.id;
491     return "<div><input type='checkbox' id='"+domId+"'/></div>";
492 }
493
494 function vlSelectAllGridRecords() {
495     for(var id in selectableGridRecords) 
496         dojo.byId(id).checked = true;
497 }
498 function vlSelectNoGridRecords() {
499     for(var id in selectableGridRecords) 
500         dojo.byId(id).checked = false;
501 }
502
503 var handleRetrieveRecords = function() {
504     buildRecordGrid(currentType);
505 }
506
507 function vlImportSelectedRecords() {
508     displayGlobalDiv('vl-generic-progress-with-total');
509     var records = [];
510
511     for(var id in selectableGridRecords) {
512         if(dojo.byId(id).checked) {
513             var recId = selectableGridRecords[id];
514             var rec = queuedRecordsMap[recId];
515             if(!rec.import_time()) 
516                 records.push(recId);
517         }
518     }
519
520     fieldmapper.standardRequest(
521         ['open-ils.vandelay', 'open-ils.vandelay.'+currentType+'_record.list.import'],
522         {   async: true,
523             params: [authtoken, records, {overlay_map:currentOverlayRecordsMap}],
524             onresponse: function(r) {
525                 var resp = r.recv().content();
526                 if(e = openils.Event.parse(resp))
527                     return alert(e);
528                 vlControlledProgressBar.update({maximum:resp.total, progress:resp.progress});
529             },
530             oncomplete: function() {
531                 return retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
532             }
533         }
534     );
535 }
536
537
538 /**
539   * Create queue, upload MARC, process spool, load the newly created queue 
540   */
541 function batchUpload() {
542     var queueName = dijit.byId('vl-queue-name').getValue();
543     currentType = dijit.byId('vl-record-type').getValue();
544
545     var handleProcessSpool = function() {
546         console.log('records uploaded and spooled');
547         retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
548     }
549
550     var handleUploadMARC = function(key) {
551         console.log('marc uploaded');
552         dojo.style(dojo.byId('vl-upload-status-processing'), 'display', 'block');
553         processSpool(key, currentQueueId, currentType, handleProcessSpool);
554     };
555
556     var handleCreateQueue = function(queue) {
557         console.log('queue created ' + queue.name());
558         currentQueueId = queue.id();
559         uploadMARC(handleUploadMARC);
560     };
561     
562     if(vlUploadQueueSelector.getValue() && !queueName) {
563         currentQueueId = vlUploadQueueSelector.getValue();
564         console.log('adding records to existing queue ' + currentQueueId);
565         uploadMARC(handleUploadMARC);
566     } else {
567         createQueue(queueName, currentType, handleCreateQueue);
568     }
569 }
570
571
572 function vlFleshQueueSelect(selector, type) {
573     var data = (type == 'bib') ? vbq.toStoreData(userBibQueues) : vaq.toStoreData(userAuthQueues);
574     selector.store = new dojo.data.ItemFileReadStore({data:data});
575     selector.setValue(null);
576     selector.setDisplayedValue('');
577     if(data[0])
578         selector.setValue(data[0].id());
579 }
580
581 function vlShowUploadForm() {
582     displayGlobalDiv('vl-marc-upload-div');
583     vlFleshQueueSelect(vlUploadQueueSelector, vlUploadRecordType.getValue());
584 }
585
586 function vlShowQueueSelect() {
587     displayGlobalDiv('vl-queue-select-div');
588     vlFleshQueueSelect(vlQueueSelectQueueList, vlQueueSelectType.getValue());
589 }
590
591 function vlFetchQueueFromForm() {
592     currentType = vlQueueSelectType.getValue();
593     currentQueueId = vlQueueSelectQueueList.getValue();
594     retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
595 }
596
597 dojo.addOnLoad(vlInit);