plugged in new authority calls
[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-upload';
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 div ' + 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
272     var retrieve = ['open-ils.search', 'open-ils.search.biblio.record_entry.slim.retrieve'];
273     var params = [records];
274     if(currentType == 'auth') {
275         retrieve = ['open-ils.cat', 'open-ils.cat.authority.record.retrieve'];
276         parmas = [authtoken, records, {clear_marc:1}];
277     }
278
279     fieldmapper.standardRequest(
280         retrieve,
281         {   async: true,
282             params:params,
283             oncomplete: function(r) {
284                 var recs = r.recv().content();
285                 if(e = openils.Event.parse(recs))
286                     return alert(e);
287
288                 /* ui mangling */
289                 displayGlobalDiv('vl-match-div');
290                 resetVlMatchGridLayout();
291                 currentMatchedRecords = recs;
292                 vlMatchGrid.setStructure(vlMatchGridLayout);
293
294                 // build the data store or records with match information
295                 var dataStore = bre.toStoreData(recs, null, {virtualFields:['field_type']});
296                 for(var i = 0; i < dataStore.items.length; i++) {
297                     var item = dataStore.items[i];
298                     for(var j = 0; j < matches.length; j++) {
299                         var match = matches[j];
300                         if(match.eg_record() == item.id)
301                             item.field_type = match.field_type();
302                     }
303                 }
304                 // now populate the grid
305                 vlPopulateGrid(vlMatchGrid, dataStore);
306             }
307         }
308     );
309 }
310
311 function vlPopulateGrid(grid, data) {
312     var store = new dojo.data.ItemFileReadStore({data:data});
313     var model = new dojox.grid.data.DojoData(
314         null, store, {rowsPerPage: 100, clientSort: true, query:{id:'*'}});
315     grid.setModel(model);
316     grid.update();
317 }
318
319
320 function vlLoadMARCHtml(recId) {
321     displayGlobalDiv('vl-generic-progress');
322     var api = ['open-ils.search', 'open-ils.search.biblio.record.html'];
323     if(currentType == 'auth')
324         api = ['open-ils.search', 'open-ils.search.authority.to_html'];
325     fieldmapper.standardRequest(
326         api, 
327         {   async: true,
328             params: [recId, 1],
329             oncomplete: function(r) {
330             displayGlobalDiv('vl-match-html-div');
331                 var html = r.recv().content();
332                 dojo.byId('vl-match-record-html').innerHTML = html;
333             }
334         }
335     );
336 }
337
338
339 /**
340   * Given a record, an attribute definition code, and a matching record attribute,
341   * this will determine if there are any import matches and build the UI to
342   * represent those matches.  If no matches exist, simply returns the attribute value
343   */
344 function buildAttrColumnUI(rec, attrCode, attr) {
345     var matches = getRecMatchesFromAttrCode(rec, attrCode);
346     if(matches.length > 0) { // found some matches
347         return '<div class="match_div">' +
348             '<a href="javascript:void(0);" onclick="vlLoadMatchUI('+
349             rec.id()+',\''+attrCode+'\');">'+ 
350             attr.attr_value() + '&nbsp;('+matches.length+')</a></div>';
351     }
352
353     return attr.attr_value();
354 }
355
356 function getRecMatchesFromAttrCode(rec, attrCode) {
357     var matches = [];
358     var attr = getRecAttrFromCode(rec, attrCode);
359     for(var j = 0; j < rec.matches().length; j++) {
360         var match = rec.matches()[j];
361         if(match.matched_attr() == attr.id()) 
362             matches.push(match);
363     }
364     return matches;
365 }
366
367 function getRecAttrFromCode(rec, attrCode) {
368     var defId = attrDefMap[attrCode];
369     var attrs = rec.attributes();
370     for(var i = 0; i < attrs.length; i++) {
371         var attr = attrs[i];
372         if(attr.field() == defId) 
373             return attr;
374     }
375     return null;
376 }
377
378 function getAttrValue(rowIdx) {
379     var data = this.grid.model.getRow(rowIdx);
380     if(!data) return '';
381     var attrCode = this.field.split('.')[1];
382     var rec = queuedRecordsMap[data.id];
383     var attr = getRecAttrFromCode(rec, attrCode);
384     if(attr)
385         return buildAttrColumnUI(rec, attrCode, attr);
386     return '';
387 }
388
389 function vlGetDateTimeField(rowIdx) {
390     data = this.grid.model.getRow(rowIdx);
391     if(!data) return '';
392     if(!data[this.field]) return '';
393     var date = dojo.date.stamp.fromISOString(data[this.field]);
394     return dojo.date.locale.format(date, {selector:'date'});
395 }
396
397 function vlGetCreator(rowIdx) {
398     data = this.grid.model.getRow(rowIdx);
399     if(!data) return '';
400     var id = data.creator;
401     if(userCache[id])
402         return userCache[id].usrname();
403     var user = fieldmapper.standardRequest(
404         ['open-ils.actor', 'open-ils.actor.user.retrieve'], [authtoken, id]);
405     if(e = openils.Event.parse(user))
406         return alert(e);
407     userCache[id] = user;
408     return user.usrname();
409 }
410
411 function vlGetViewMARC(rowIdx) {
412     data = this.grid.model.getRow(rowIdx);
413     if(data) 
414         return this.value.replace('RECID', data.id);
415 }
416
417 function vlGetOverlayTargetSelector(rowIdx) {
418     data = this.grid.model.getRow(rowIdx);
419     if(data) {
420         var value = this.value.replace('ID', data.id);
421         var overlay = currentOverlayRecordsMap[currentImportRecId];
422         if(overlay && overlay == data.id) 
423             value = value.replace('/>', 'checked="checked"/>');
424         return value;
425     }
426 }
427
428 /**
429   * see if the user has enabled overlays for the current match set and, 
430   * if so, map the current import record to the overlay target.
431   */
432 function vlHandleOverlayTargetSelected() {
433     if(vlOverlayTargetEnable.checked) {
434         for(var i = 0; i < currentMatchedRecords.length; i++) {
435             var matchRecId = currentMatchedRecords[i].id();
436             if(dojo.byId('vl-overlay-target-'+matchRecId).checked) {
437                 console.log("found overlay target " + matchRecId);
438                 currentOverlayRecordsMap[currentImportRecId] = matchRecId;
439                 dojo.byId('vl-record-list-selected-' + currentImportRecId).checked = true;
440                 dojo.byId('vl-record-list-selected-' + currentImportRecId).parentNode.className = 'overlay_selected';
441                 return;
442             }
443         }
444     } else {
445         delete currentOverlayRecordsMap[currentImportRecId];
446         dojo.byId('vl-record-list-selected-' + currentImportRecId).checked = false;
447     }
448 }
449
450 function buildRecordGrid(type) {
451     displayGlobalDiv('vl-queue-div');
452
453     currentOverlayRecordsMap = {};
454
455     if(queuedRecords.length == 0) {
456         dojo.style(dojo.byId('vl-queue-no-records'), 'display', 'block');
457         dojo.style(dojo.byId('vl-queue-div-grid'), 'display', 'none');
458         return;
459     } else {
460         dojo.style(dojo.byId('vl-queue-no-records'), 'display', 'none');
461         dojo.style(dojo.byId('vl-queue-div-grid'), 'display', 'block');
462     }
463
464     var defs = (type == 'bib') ? bibAttrDefs : authAttrDefs;
465     for(var i = 0; i < defs.length; i++) {
466         var def = defs[i]
467         attrDefMap[def.code()] = def.id();
468         var col = {
469             name:def.description(), 
470             field:'attr.' + def.code(),
471             get: getAttrValue
472         };
473         //if(def.code().match(/title/i)) col.width = 'auto'; // this is hack.
474         vlQueueGridLayout[0].cells[0].push(col);
475     }
476
477     var storeData;
478     if(type == 'bib')
479         storeData = vqbr.toStoreData(queuedRecords);
480     else
481         storeData = vqar.toStoreData(queuedRecords);
482
483     var store = new dojo.data.ItemFileReadStore({data:storeData});
484     var model = new dojox.grid.data.DojoData(
485         null, store, {rowsPerPage: 100, clientSort: true, query:{id:'*'}});
486
487     vlQueueGrid.setModel(model);
488     vlQueueGrid.setStructure(vlQueueGridLayout);
489     vlQueueGrid.update();
490 }
491
492 /*
493 function test() {
494     alert(vlQueueGridLayout.picker);
495     vlQueueGridLayout.oils = {};
496     vlQueueGridLayout[0].cells[0].pop();
497     vlQueueGridLayout[0].cells[0].pop();
498     vlQueueGridLayout[0].cells[0].pop();
499     vlQueueGridLayout[0].cells[0].pop();
500     vlQueueGridLayout[0].cells[0].pop();
501     vlQueueGrid.setStructure(vlQueueGridLayout);
502     vlQueueGrid.update();
503 }
504 */
505
506 function vlQueueGridDrawSelectBox(rowIdx) {
507     var data = this.grid.model.getRow(rowIdx);
508     if(!data) return '';
509     var domId = 'vl-record-list-selected-' +data.id;
510     selectableGridRecords[domId] = data.id;
511     return "<div><input type='checkbox' id='"+domId+"'/></div>";
512 }
513
514 function vlSelectAllGridRecords() {
515     for(var id in selectableGridRecords) 
516         dojo.byId(id).checked = true;
517 }
518 function vlSelectNoGridRecords() {
519     for(var id in selectableGridRecords) 
520         dojo.byId(id).checked = false;
521 }
522
523 var handleRetrieveRecords = function() {
524     buildRecordGrid(currentType);
525 }
526
527 function vlImportSelectedRecords() {
528     displayGlobalDiv('vl-generic-progress-with-total');
529     var records = [];
530
531     for(var id in selectableGridRecords) {
532         if(dojo.byId(id).checked) {
533             var recId = selectableGridRecords[id];
534             var rec = queuedRecordsMap[recId];
535             if(!rec.import_time()) 
536                 records.push(recId);
537         }
538     }
539
540     fieldmapper.standardRequest(
541         ['open-ils.vandelay', 'open-ils.vandelay.'+currentType+'_record.list.import'],
542         {   async: true,
543             params: [authtoken, records, {overlay_map:currentOverlayRecordsMap}],
544             onresponse: function(r) {
545                 var resp = r.recv().content();
546                 if(e = openils.Event.parse(resp))
547                     return alert(e);
548                 vlControlledProgressBar.update({maximum:resp.total, progress:resp.progress});
549             },
550             oncomplete: function() {
551                 return retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
552             }
553         }
554     );
555 }
556
557
558 /**
559   * Create queue, upload MARC, process spool, load the newly created queue 
560   */
561 function batchUpload() {
562     var queueName = dijit.byId('vl-queue-name').getValue();
563     currentType = dijit.byId('vl-record-type').getValue();
564
565     var handleProcessSpool = function() {
566         console.log('records uploaded and spooled');
567         retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
568     }
569
570     var handleUploadMARC = function(key) {
571         console.log('marc uploaded');
572         dojo.style(dojo.byId('vl-upload-status-processing'), 'display', 'block');
573         processSpool(key, currentQueueId, currentType, handleProcessSpool);
574     };
575
576     var handleCreateQueue = function(queue) {
577         console.log('queue created ' + queue.name());
578         currentQueueId = queue.id();
579         uploadMARC(handleUploadMARC);
580     };
581     
582     if(vlUploadQueueSelector.getValue() && !queueName) {
583         currentQueueId = vlUploadQueueSelector.getValue();
584         console.log('adding records to existing queue ' + currentQueueId);
585         uploadMARC(handleUploadMARC);
586     } else {
587         createQueue(queueName, currentType, handleCreateQueue);
588     }
589 }
590
591
592 function vlFleshQueueSelect(selector, type) {
593     var data = (type == 'bib') ? vbq.toStoreData(userBibQueues) : vaq.toStoreData(userAuthQueues);
594     selector.store = new dojo.data.ItemFileReadStore({data:data});
595     selector.setValue(null);
596     selector.setDisplayedValue('');
597     if(data[0])
598         selector.setValue(data[0].id());
599 }
600
601 function vlShowUploadForm() {
602     displayGlobalDiv('vl-marc-upload-div');
603     vlFleshQueueSelect(vlUploadQueueSelector, vlUploadRecordType.getValue());
604 }
605
606 function vlShowQueueSelect() {
607     displayGlobalDiv('vl-queue-select-div');
608     vlFleshQueueSelect(vlQueueSelectQueueList, vlQueueSelectType.getValue());
609 }
610
611 function vlFetchQueueFromForm() {
612     currentType = vlQueueSelectType.getValue();
613     currentQueueId = vlQueueSelectQueueList.getValue();
614     retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
615 }
616
617 dojo.addOnLoad(vlInit);