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