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