]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/vandelay/vandelay.js
forward porting 10975 10977, vandelay goodies
[working/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("dijit.layout.LayoutContainer");
23 dojo.require('dijit.form.Button');
24 dojo.require('dijit.Toolbar');
25 dojo.require('dijit.Tooltip');
26 dojo.require('dijit.Menu');
27 dojo.require("dijit.Dialog");
28 dojo.require("dojo.cookie");
29 dojo.require("dojox.grid.Grid");
30 dojo.require("dojo.data.ItemFileReadStore");
31 dojo.require('dojo.date.locale');
32 dojo.require('dojo.date.stamp');
33 dojo.require("fieldmapper.Fieldmapper");
34 dojo.require("fieldmapper.dojoData");
35 dojo.require('openils.CGI');
36 dojo.require('openils.User');
37 dojo.require('openils.Event');
38 dojo.require('openils.Util');
39 dojo.require('openils.MarcXPathParser');
40 dojo.require('openils.GridColumnPicker');
41
42
43 var globalDivs = [
44     'vl-generic-progress',
45     'vl-generic-progress-with-total',
46     'vl-marc-upload-div',
47     'vl-queue-div',
48     'vl-match-div',
49     'vl-marc-html-div',
50     'vl-queue-select-div',
51     'vl-marc-upload-status-div',
52     'vl-attr-editor-div',
53     'vl-marc-export-div'
54 ];
55
56 var authtoken;
57 var VANDELAY_URL = '/vandelay-upload';
58 var bibAttrDefs = [];
59 var authAttrDefs = [];
60 var queuedRecords = [];
61 var queuedRecordsMap = {};
62 var bibAttrsFetched = false;
63 var authAttrsFetched = false;
64 var attrDefMap = {}; // maps attr def code names to attr def ids
65 var currentType;
66 var currentQueueId = null;
67 var userCache = {};
68 var currentMatchedRecords; // set of loaded matched bib records
69 var currentOverlayRecordsMap; // map of import record to overlay record
70 var currentOverlayRecordsMapGid; // map of import record to overlay record grid id
71 var currentImportRecId; // when analyzing matches, this is the current import record
72 var userBibQueues = []; // only non-complete queues
73 var userAuthQueues = []; // only non-complete queues
74 var allUserBibQueues;
75 var allUserAuthQueues;
76 var selectableGridRecords;
77 var cgi = new openils.CGI();
78 var vlQueueGridColumePicker = {};
79
80 /**
81   * Grab initial data
82   */
83 function vlInit() {
84     authtoken = dojo.cookie('ses') || cgi.param('ses');
85     var initNeeded = 4; // how many async responses do we need before we're init'd 
86     var initCount = 0; // how many async reponses we've received
87
88     openils.Util.registerEnterHandler(
89         vlQueueDisplayPage.domNode, function(){retrieveQueuedRecords();});
90
91     function checkInitDone() {
92         initCount++;
93         if(initCount == initNeeded)
94             runStartupCommands();
95     }
96
97     // Fetch the bib and authority attribute definitions 
98     vlFetchBibAttrDefs(function () { checkInitDone(); });
99     vlFetchAuthAttrDefs(function () { checkInitDone(); });
100
101     vlRetrieveQueueList('bib', null, 
102         function(list) {
103             allUserBibQueues = list;
104             for(var i = 0; i < allUserBibQueues.length; i++) {
105                 if(allUserBibQueues[i].complete() == 'f')
106                     userBibQueues.push(allUserBibQueues[i]);
107             }
108             checkInitDone();
109         }
110     );
111
112     vlRetrieveQueueList('auth', null, 
113         function(list) {
114             allUserAuthQueues = list;
115             for(var i = 0; i < allUserAuthQueues.length; i++) {
116                 if(allUserAuthQueues[i].complete() == 'f')
117                     userAuthQueues.push(allUserAuthQueues[i]);
118             }
119             checkInitDone();
120         }
121     );
122
123     vlAttrEditorInit();
124 }
125
126
127 dojo.addOnLoad(vlInit);
128
129
130 // fetch the bib and authority attribute definitions
131
132 function vlFetchBibAttrDefs(postcomplete) {
133     bibAttrDefs = [];
134     fieldmapper.standardRequest(
135         ['open-ils.permacrud', 'open-ils.permacrud.search.vqbrad'],
136         {   async: true,
137             params: [authtoken, {id:{'!=':null}}],
138             onresponse: function(r) {
139                 var def = r.recv().content(); 
140                 if(e = openils.Event.parse(def[0])) 
141                     return alert(e);
142                 bibAttrDefs.push(def);
143             },
144             oncomplete: function() {
145                 bibAttrDefs = bibAttrDefs.sort(
146                     function(a, b) {
147                         if(a.id() > b.id()) return 1;
148                         if(a.id() < b.id()) return -1;
149                         return 0;
150                     }
151                 );
152                 postcomplete();
153             }
154         }
155     );
156 }
157
158 function vlFetchAuthAttrDefs(postcomplete) {
159     authAttrDefs = [];
160     fieldmapper.standardRequest(
161         ['open-ils.permacrud', 'open-ils.permacrud.search.vqarad'],
162         {   async: true,
163             params: [authtoken, {id:{'!=':null}}],
164             onresponse: function(r) {
165                 var def = r.recv().content(); 
166                 if(e = openils.Event.parse(def[0])) 
167                     return alert(e);
168                 authAttrDefs.push(def);
169             },
170             oncomplete: function() {
171                 authAttrDefs = authAttrDefs.sort(
172                     function(a, b) {
173                         if(a.id() > b.id()) return 1;
174                         if(a.id() < b.id()) return -1;
175                         return 0;
176                     }
177                 );
178                 postcomplete();
179             }
180         }
181     );
182 }
183
184 function vlRetrieveQueueList(type, filter, onload) {
185     type = (type == 'bib') ? type : 'authority';
186     fieldmapper.standardRequest(
187         ['open-ils.vandelay', 'open-ils.vandelay.'+type+'_queue.owner.retrieve.atomic'],
188         {   async: true,
189             params: [authtoken, null, filter],
190             oncomplete: function(r) {
191                 var list = r.recv().content();
192                 if(e = openils.Event.parse(list[0]))
193                     return alert(e);
194                 onload(list);
195             }
196         }
197     );
198
199 }
200
201 function displayGlobalDiv(id) {
202     for(var i = 0; i < globalDivs.length; i++) {
203         try {
204             dojo.style(dojo.byId(globalDivs[i]), 'display', 'none');
205         } catch(e) {
206             alert('please define div ' + globalDivs[i]);
207         }
208     }
209     dojo.style(dojo.byId(id),'display','block');
210 }
211
212 function runStartupCommands() {
213     currentQueueId = cgi.param('qid');
214     currentType = cgi.param('qtype');
215     dojo.style('vl-nav-bar', 'visibility', 'visible');
216     if(currentQueueId)
217         return retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
218     vlShowUploadForm();
219 }
220
221 /**
222   * asynchronously upload a file of MARC records
223   */
224 function uploadMARC(onload){
225     dojo.byId('vl-upload-status-count').innerHTML = '0';
226     dojo.byId('vl-ses-input').value = authtoken;
227     displayGlobalDiv('vl-marc-upload-status-div');
228     dojo.io.iframe.send({
229         url: VANDELAY_URL,
230         method: "post",
231         handleAs: "html",
232         form: dojo.byId('vl-marc-upload-form'),
233         handle: function(data,ioArgs){
234             var content = data.documentElement.textContent;
235             onload(content);
236         }
237     });
238 }       
239
240 /**
241   * Creates a new vandelay queue
242   */
243 function createQueue(queueName, type, onload) {
244     var name = (type=='bib') ? 'bib' : 'authority';
245     var method = 'open-ils.vandelay.'+ name +'_queue.create'
246     fieldmapper.standardRequest(
247         ['open-ils.vandelay', method],
248         {   async: true,
249             params: [authtoken, queueName, null, name],
250             oncomplete : function(r) {
251                 var queue = r.recv().content();
252                 if(e = openils.Event.parse(queue)) 
253                     return alert(e);
254                 onload(queue);
255             }
256         }
257     );
258 }
259
260 /**
261   * Tells vandelay to pull a batch of records from the cache and explode them
262   * out into the vandelay tables
263   */
264 function processSpool(key, queueId, type, onload) {
265     fieldmapper.standardRequest(
266         ['open-ils.vandelay', 'open-ils.vandelay.'+type+'.process_spool'],
267         {   async: true,
268             params: [authtoken, key, queueId],
269             onresponse : function(r) {
270                 var resp = r.recv().content();
271                 if(e = openils.Event.parse(resp)) 
272                     return alert(e);
273                 dojo.byId('vl-upload-status-count').innerHTML = resp;
274             },
275             oncomplete : function(r) {onload();}
276         }
277     );
278 }
279
280 function retrieveQueuedRecords(type, queueId, onload) {
281     displayGlobalDiv('vl-generic-progress');
282     queuedRecords = [];
283     queuedRecordsMap = {};
284     currentOverlayRecordsMap = {};
285     currentOverlayRecordsMapGid = {};
286     selectableGridRecords = {};
287     resetVlQueueGridLayout();
288
289     if(!type) type = currentType;
290     if(!queueId) queueId = currentQueueId;
291     if(!onload) onload = handleRetrieveRecords;
292
293     var method = 'open-ils.vandelay.'+type+'_queue.records.retrieve.atomic';
294     if(vlQueueGridShowMatches.checked)
295         method = method.replace('records', 'records.matches');
296
297     //var limit = parseInt(vlQueueDisplayLimit.getValue());
298     var sel = dojo.byId('vl-queue-display-limit-selector');
299     var limit = parseInt(sel.options[sel.selectedIndex].value);
300     var offset = limit * parseInt(vlQueueDisplayPage.getValue()-1);
301
302     var params =  [authtoken, queueId, {clear_marc: 1, offset: offset, limit: limit}];
303     if(vlQueueGridShowNonImport.checked)
304         params[2].non_imported = 1;
305
306     fieldmapper.standardRequest(
307         ['open-ils.vandelay', method],
308         {   async: true,
309             params: params,
310             /*
311             onresponse: function(r) {
312                 console.log("ONREPONSE");
313                 var rec = r.recv().content();
314                 if(e = openils.Event.parse(rec))
315                     return alert(e);
316                 console.log("got record " + rec.id());
317                 queuedRecords.push(rec);
318                 queuedRecordsMap[rec.id()] = rec;
319             },
320             */
321             oncomplete: function(r){
322                 var recs = r.recv().content();
323                 if(e = openils.Event.parse(recs[0]))
324                     return alert(e);
325                 for(var i = 0; i < recs.length; i++) {
326                     var rec = recs[i];
327                     queuedRecords.push(rec);
328                     queuedRecordsMap[rec.id()] = rec;
329                 }
330                 onload();
331             }
332         }
333     );
334 }
335
336 function vlLoadMatchUI(recId) {
337     displayGlobalDiv('vl-generic-progress');
338     var matches = queuedRecordsMap[recId].matches();
339     var records = [];
340     currentImportRecId = recId;
341     for(var i = 0; i < matches.length; i++)
342         records.push(matches[i].eg_record());
343
344     var retrieve = ['open-ils.search', 'open-ils.search.biblio.record_entry.slim.retrieve'];
345     var params = [records];
346     if(currentType == 'auth') {
347         retrieve = ['open-ils.cat', 'open-ils.cat.authority.record.retrieve'];
348         parmas = [authtoken, records, {clear_marc:1}];
349     }
350
351     fieldmapper.standardRequest(
352         retrieve,
353         {   async: true,
354             params:params,
355             oncomplete: function(r) {
356                 var recs = r.recv().content();
357                 if(e = openils.Event.parse(recs))
358                     return alert(e);
359
360                 /* ui mangling */
361                 displayGlobalDiv('vl-match-div');
362                 resetVlMatchGridLayout();
363                 currentMatchedRecords = recs;
364                 if(!vlMatchGrid.structure)
365                     vlMatchGrid.setStructure(vlMatchGridLayout);
366
367                 // build the data store of records with match information
368                 var dataStore = bre.toStoreData(recs, null, 
369                     {virtualFields:['dest_matchpoint', 'src_matchpoint', '_id']});
370                 dataStore.identifier = '_id';
371
372                 var matchSeenMap = {};
373
374                 for(var i = 0; i < dataStore.items.length; i++) {
375                     var item = dataStore.items[i];
376                     item._id = i; // just need something unique
377                     for(var j = 0; j < matches.length; j++) {
378                         var match = matches[j];
379                         if(match.eg_record() == item.id && !matchSeenMap[match.id()]) {
380                             item.dest_matchpoint = match.field_type();
381                             var attr = getRecAttrFromMatch(queuedRecordsMap[recId], match);
382                             item.src_matchpoint = getRecAttrDefFromAttr(attr, currentType).code();
383                             matchSeenMap[match.id()] = 1;
384                             break;
385                         }
386                     }
387                 }
388
389                 // now populate the grid
390                 vlPopulateMatchGrid(vlMatchGrid, dataStore);
391             }
392         }
393     );
394 }
395
396 function vlPopulateMatchGrid(grid, data) {
397     var store = new dojo.data.ItemFileReadStore({data:data});
398     var model = new dojox.grid.data.DojoData(
399         null, store, {rowsPerPage: 100, clientSort: true, query:{id:'*'}});
400     grid.setModel(model);
401     grid.update();
402 }
403
404 function showMe(id) {
405     dojo.style(dojo.byId(id), 'display', 'block');
406 }
407 function hideMe(id) {
408     dojo.style(dojo.byId(id), 'display', 'none');
409 }
410
411
412 function vlLoadMARCHtml(recId, inCat, oncomplete) {
413     dijit.byId('vl-marc-html-done-button').onClick = oncomplete;
414     displayGlobalDiv('vl-generic-progress');
415     var api;
416     var params = [recId, 1];
417
418     if(inCat) {
419         hideMe('vl-marc-html-edit-button'); // don't show marc editor button
420         dijit.byId('vl-marc-html-edit-button').onClick = function(){}
421         api = ['open-ils.search', 'open-ils.search.biblio.record.html'];
422         if(currentType == 'auth')
423             api = ['open-ils.search', 'open-ils.search.authority.to_html'];
424     } else {
425         showMe('vl-marc-html-edit-button'); // plug in the marc editor button
426         dijit.byId('vl-marc-html-edit-button').onClick = 
427             function() {vlLoadMarcEditor(currentType, recId, oncomplete);};
428         params = [authtoken, recId];
429         api = ['open-ils.vandelay', 'open-ils.vandelay.queued_bib_record.html'];
430         if(currentType == 'auth')
431             api = ['open-ils.vandelay', 'open-ils.vandelay.queued_authority_record.html'];
432     }
433
434     fieldmapper.standardRequest(
435         api, 
436         {   async: true,
437             params: params,
438             oncomplete: function(r) {
439             displayGlobalDiv('vl-marc-html-div');
440                 var html = r.recv().content();
441                 dojo.byId('vl-marc-record-html').innerHTML = html;
442             }
443         }
444     );
445 }
446
447
448 /*
449 function getRecMatchesFromAttrCode(rec, attrCode) {
450     var matches = [];
451     var attr = getRecAttrFromCode(rec, attrCode);
452     for(var j = 0; j < rec.matches().length; j++) {
453         var match = rec.matches()[j];
454         if(match.matched_attr() == attr.id()) 
455             matches.push(match);
456     }
457     return matches;
458 }
459 */
460
461 function getRecAttrFromMatch(rec, match) {
462     for(var i = 0; i < rec.attributes().length; i++) {
463         var attr = rec.attributes()[i];
464         if(attr.id() == match.matched_attr())
465             return attr;
466     }
467 }
468
469 function getRecAttrDefFromAttr(attr, type) {
470     var defs = (type == 'bib') ? bibAttrDefs : authAttrDefs;
471     for(var i = 0; i < defs.length; i++) {
472         var def = defs[i];
473         if(def.id() == attr.field())
474             return def;
475     }
476 }
477
478 function getRecAttrFromCode(rec, attrCode) {
479     var defId = attrDefMap[currentType][attrCode];
480     var attrs = rec.attributes();
481     for(var i = 0; i < attrs.length; i++) {
482         var attr = attrs[i];
483         if(attr.field() == defId) 
484             return attr;
485     }
486     return null;
487 }
488
489 function vlGetViewMatches(rowIdx) {
490     var data = this.grid.model.getRow(rowIdx);
491     if(!data) return '';
492     var rec = queuedRecordsMap[data.id];
493     if(rec.matches().length > 0)
494         return this.value.replace('RECID', data.id);
495     return '';
496 }
497
498 function getAttrValue(rowIdx) {
499     var data = this.grid.model.getRow(rowIdx);
500     if(!data) return '';
501     var attrCode = this.field.split('.')[1];
502     var rec = queuedRecordsMap[data.id];
503     var attr = getRecAttrFromCode(rec, attrCode);
504     return (attr) ? attr.attr_value() : '';
505 }
506
507 function vlGetDateTimeField(rowIdx) {
508     data = this.grid.model.getRow(rowIdx);
509     if(!data) return '';
510     if(!data[this.field]) return '';
511     var date = dojo.date.stamp.fromISOString(data[this.field]);
512     return dojo.date.locale.format(date, {selector:'date'});
513 }
514
515 function vlGetCreator(rowIdx) {
516     data = this.grid.model.getRow(rowIdx);
517     if(!data) return '';
518     var id = data.creator;
519     if(userCache[id])
520         return userCache[id].usrname();
521     var user = fieldmapper.standardRequest(
522         ['open-ils.actor', 'open-ils.actor.user.retrieve'], [authtoken, id]);
523     if(e = openils.Event.parse(user))
524         return alert(e);
525     userCache[id] = user;
526     return user.usrname();
527 }
528
529 function vlGetViewMARC(rowIdx) {
530     data = this.grid.model.getRow(rowIdx);
531     if(data) 
532         return this.value.replace('RECID', data.id);
533 }
534
535 function vlGetOverlayTargetSelector(rowIdx) {
536     data = this.grid.model.getRow(rowIdx);
537     if(data) {
538         var value = this.value.replace(/GRIDID/g, data._id);
539         value = value.replace(/RECID/g, currentImportRecId);
540         value = value.replace(/ID/g, data.id);
541         if(data._id == currentOverlayRecordsMapGid[currentImportRecId])
542             return value.replace('/>', 'checked="checked"/>');
543         return value;
544     }
545 }
546
547 /**
548   * see if the user has enabled overlays for the current match set and, 
549   * if so, map the current import record to the overlay target.
550   */
551 function vlHandleOverlayTargetSelected(recId, gridId) {
552     var noneSelected = true;
553     var checkboxes = dojo.query('[name=vl-overlay-target-'+currentImportRecId+']');
554     for(var i = 0; i < checkboxes.length; i++) {
555         var checkbox = checkboxes[i];
556         var matchRecId = checkbox.getAttribute('match');
557         var gid = checkbox.getAttribute('gridid');
558         if(checkbox.checked) {
559             if(matchRecId == recId && gid == gridId) {
560                 noneSelected = false;
561                 currentOverlayRecordsMap[currentImportRecId] = matchRecId;
562                 currentOverlayRecordsMapGid[currentImportRecId] = gid;
563                 dojo.byId('vl-record-list-selected-' + currentImportRecId).checked = true;
564                 dojo.byId('vl-record-list-selected-' + currentImportRecId).parentNode.className = 'overlay_selected';
565             } else {
566                 checkbox.checked = false;
567             }
568         }
569     }
570
571     if(noneSelected) {
572         delete currentOverlayRecordsMap[currentImportRecId];
573         delete currentOverlayRecordsMapGid[currentImportRecId];
574         dojo.byId('vl-record-list-selected-' + currentImportRecId).checked = false;
575         dojo.byId('vl-record-list-selected-' + currentImportRecId).parentNode.className = '';
576     }
577 }
578
579 var valLastQueueType = null;
580 function buildRecordGrid(type) {
581     displayGlobalDiv('vl-queue-div');
582
583     if(valLastQueueType != type) {
584         valLastQueueType = type;
585         resetVlQueueGridLayout();
586         var defs = (type == 'bib') ? bibAttrDefs : authAttrDefs;
587         attrDefMap[type] = {};
588         for(var i = 0; i < defs.length; i++) {
589             var def = defs[i]
590             attrDefMap[type][def.code()] = def.id();
591             var col = {
592                 name:def.description(), 
593                 field:'attr.' + def.code(),
594                 get: getAttrValue,
595                 selectableColumn:true
596             };
597             vlQueueGridLayout[0].cells[0].push(col);
598         }
599     }
600
601     var storeData;
602     if(type == 'bib')
603         storeData = vqbr.toStoreData(queuedRecords);
604     else
605         storeData = vqar.toStoreData(queuedRecords);
606
607     var store = new dojo.data.ItemFileReadStore({data:storeData});
608     var model = new dojox.grid.data.DojoData(
609         null, store, {rowsPerPage: 100, clientSort: true, query:{id:'*'}});
610     vlQueueGrid.setModel(model);
611
612
613     if(vlQueueGridColumePicker[type]) {
614         vlQueueGrid.update();
615     } else {
616         vlQueueGridColumePicker[type] = 
617             new openils.GridColumnPicker(vlQueueGridColumePickerDialog, 
618                 vlQueueGrid, vlQueueGridLayout, authtoken, 'vandelay.queue.'+type);
619         vlQueueGridColumePicker[type].load();
620     }
621 }
622
623 function vlQueueGridPrevPage() {
624     var page = parseInt(vlQueueDisplayPage.getValue());
625     if(page < 2) return;
626     vlQueueDisplayPage.setValue(page - 1);
627     retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
628 }
629
630 function vlQueueGridNextPage() {
631     vlQueueDisplayPage.setValue(parseInt(vlQueueDisplayPage.getValue())+1);
632     retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
633 }
634
635 function vlDeleteQueue(type, queueId, onload) {
636     fieldmapper.standardRequest(
637         ['open-ils.vandelay', 'open-ils.vandelay.'+type+'_queue.delete'],
638         {   async: true,
639             params: [authtoken, queueId],
640             oncomplete: function(r) {
641                 var resp = r.recv().content();
642                 if(e = openils.Event.parse(resp))
643                     return alert(e);
644                 onload();
645             }
646         }
647     );
648 }
649
650
651 function vlQueueGridDrawSelectBox(rowIdx) {
652     var data = this.grid.model.getRow(rowIdx);
653     if(!data) return '';
654     var domId = 'vl-record-list-selected-' +data.id;
655     selectableGridRecords[domId] = data.id;
656     return "<div><input type='checkbox' id='"+domId+"'/></div>";
657 }
658
659 function vlSelectAllQueueGridRecords() {
660     for(var id in selectableGridRecords) 
661         dojo.byId(id).checked = true;
662 }
663 function vlSelectNoQueueGridRecords() {
664     for(var id in selectableGridRecords) 
665         dojo.byId(id).checked = false;
666 }
667 function vlToggleQueueGridSelect() {
668     if(dojo.byId('vl-queue-grid-row-selector').checked)
669         vlSelectAllQueueGridRecords();
670     else
671         vlSelectNoQueueGridRecords();
672 }
673
674 var handleRetrieveRecords = function() {
675     buildRecordGrid(currentType);
676     vlFetchQueueSummary(currentQueueId, currentType, 
677         function(summary) {
678             dojo.byId('vl-queue-summary-name').innerHTML = summary.queue.name();
679             dojo.byId('vl-queue-summary-total-count').innerHTML = summary.total +'';
680             dojo.byId('vl-queue-summary-import-count').innerHTML = summary.imported + '';
681         }
682     );
683 }
684
685 function vlFetchQueueSummary(qId, type, onload) {
686     fieldmapper.standardRequest(
687         ['open-ils.vandelay', 'open-ils.vandelay.'+type+'_queue.summary.retrieve'],
688         {   async: true,
689             params: [authtoken, qId],
690             oncomplete : function(r) {
691                 var summary = r.recv().content();
692                 if(e = openils.Event.parse(summary))
693                     return alert(e);
694                 return onload(summary);
695             }
696         }
697     );
698 }
699     
700
701 function vlImportSelectedRecords() {
702     displayGlobalDiv('vl-generic-progress-with-total');
703     var records = [];
704
705     for(var id in selectableGridRecords) {
706         if(dojo.byId(id).checked) {
707             var recId = selectableGridRecords[id];
708             var rec = queuedRecordsMap[recId];
709             if(!rec.import_time()) 
710                 records.push(recId);
711         }
712     }
713
714     fieldmapper.standardRequest(
715         ['open-ils.vandelay', 'open-ils.vandelay.'+currentType+'_record.list.import'],
716         {   async: true,
717             params: [authtoken, records, {overlay_map:currentOverlayRecordsMap}],
718             onresponse: function(r) {
719                 var resp = r.recv().content();
720                 if(e = openils.Event.parse(resp))
721                     return alert(e);
722                 if(resp.complete) {
723                     return retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
724                 } else {
725                     vlControlledProgressBar.update({maximum:resp.total, progress:resp.progress});
726                 }
727             }, 
728         }
729     );
730 }
731
732 function vlImportAllRecords() {
733     vlImportRecordQueue(currentType, currentQueueId, false,
734         function(){displayGlobalDiv('vl-queue-div');});
735 }
736
737 function vlImportRecordQueue(type, queueId, noMatchOnly, onload) {
738     displayGlobalDiv('vl-generic-progress-with-total');
739     var method = 'open-ils.vandelay.bib_queue.import';
740     if(noMatchOnly)
741         method = method.replace('import', 'nomatch.import');
742     if(type == 'auth')
743         method = method.replace('bib', 'auth');
744
745     fieldmapper.standardRequest(
746         ['open-ils.vandelay', method],
747         {   async: true,
748             params: [authtoken, queueId],
749             onresponse: function(r) {
750                 var resp = r.recv().content();
751                 if(e = openils.Event.parse(resp))
752                     return alert(e);
753                 vlControlledProgressBar.update({maximum:resp.total, progress:resp.progress});
754             },
755             oncomplete: function() {onload();}
756         }
757     );
758 }
759
760
761 /**
762   * Create queue, upload MARC, process spool, load the newly created queue 
763   */
764 function batchUpload() {
765     var queueName = dijit.byId('vl-queue-name').getValue();
766     currentType = dijit.byId('vl-record-type').getValue();
767
768     var handleProcessSpool = function() {
769         console.log('records uploaded and spooled');
770         if(vlUploadQueueAutoImport.checked) {
771             vlImportRecordQueue(currentType, currentQueueId, true,  
772                 function() {
773                     retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
774                 }
775             );
776         } else {
777             retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
778         }
779     }
780
781     var handleUploadMARC = function(key) {
782         console.log('marc uploaded');
783         dojo.style(dojo.byId('vl-upload-status-processing'), 'display', 'block');
784         processSpool(key, currentQueueId, currentType, handleProcessSpool);
785     };
786
787     var handleCreateQueue = function(queue) {
788         console.log('queue created ' + queue.name());
789         currentQueueId = queue.id();
790         uploadMARC(handleUploadMARC);
791     };
792     
793     if(vlUploadQueueSelector.getValue() && !queueName) {
794         currentQueueId = vlUploadQueueSelector.getValue();
795         console.log('adding records to existing queue ' + currentQueueId);
796         uploadMARC(handleUploadMARC);
797     } else {
798         createQueue(queueName, currentType, handleCreateQueue);
799     }
800 }
801
802
803 function vlFleshQueueSelect(selector, type) {
804     var data = (type == 'bib') ? vbq.toStoreData(allUserBibQueues) : vaq.toStoreData(allUserAuthQueues);
805     selector.store = new dojo.data.ItemFileReadStore({data:data});
806     selector.setValue(null);
807     selector.setDisplayedValue('');
808     if(data[0])
809         selector.setValue(data[0].id());
810 }
811
812 function vlShowUploadForm() {
813     displayGlobalDiv('vl-marc-upload-div');
814     vlFleshQueueSelect(vlUploadQueueSelector, vlUploadRecordType.getValue());
815 }
816
817 function vlShowQueueSelect() {
818     displayGlobalDiv('vl-queue-select-div');
819     vlFleshQueueSelect(vlQueueSelectQueueList, vlQueueSelectType.getValue());
820 }
821
822 function vlFetchQueueFromForm() {
823     currentType = vlQueueSelectType.getValue();
824     currentQueueId = vlQueueSelectQueueList.getValue();
825     retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
826 }
827
828 function vlOpenMarcEditWindow(rec, postReloadHTMLHandler) {
829     /*
830         To run in Firefox directly, must set signed.applets.codebase_principal_support
831         to true in about:config
832     */
833     netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
834     win = window.open('/xul/server/cat/marcedit.xul'); // XXX version?
835
836     function onsave(r) {
837         // after the record is saved, reload the HTML display
838         var stat = r.recv().content();
839         if(e = openils.Event.parse(stat))
840             return alert(e);
841         alert(dojo.byId('vl-marc-edit-complete-label').innerHTML);
842         win.close();
843         vlLoadMARCHtml(rec.id(), false, postReloadHTMLHandler);
844     }
845
846     win.xulG = {
847         record : {marc : rec.marc()},
848         save : {
849             label: dojo.byId('vl-marc-edit-save-label').innerHTML,
850             func: function(xmlString) {
851                 var method = 'open-ils.permacrud.update.' + rec.classname;
852                 rec.marc(xmlString);
853                 fieldmapper.standardRequest(
854                     ['open-ils.permacrud', method],
855                     {   async: true,
856                         params: [authtoken, rec],
857                         oncomplete: onsave
858                     }
859                 );
860             },
861         }
862     };
863 }
864
865 function vlLoadMarcEditor(type, recId, postReloadHTMLHandler) {
866     var method = 'open-ils.permacrud.search.vqbr';
867     if(currentType != 'bib')
868         method = method.replace(/vqbr/,'vqar');
869
870     fieldmapper.standardRequest(
871         ['open-ils.permacrud', method],
872         {   async: true, 
873             params: [authtoken, {id : recId}],
874             oncomplete: function(r) {
875                 var rec = r.recv().content();
876                 if(e = openils.Event.parse(rec))
877                     return alert(e);
878                 vlOpenMarcEditWindow(rec, postReloadHTMLHandler);
879             }
880         }
881     );
882 }
883
884
885
886 //------------------------------------------------------------
887 // attribute editors
888
889 // attribute-editor global variables
890
891 var ATTR_EDITOR_IN_UPDATE_MODE = false; // true on 'edit', false on 'create'
892 var ATTR_EDIT_ID = null;                // id of current 'edit' attribute
893 var ATTR_EDIT_GROUP = 'bib';            // bib-attrs or auth-attrs
894
895 function vlAttrEditorInit() {
896     // set up tooltips on the edit form
897     connectTooltip('attr-editor-tags'); 
898     connectTooltip('attr-editor-subfields'); 
899 }
900
901 function vlShowAttrEditor() {
902     displayGlobalDiv('vl-attr-editor-div');
903     loadAttrEditorGrid();
904     idHide('vl-generic-progress');
905 }
906
907 function setAttrEditorGroup(groupName) {
908     // put us into 'bib'-attr or 'auth'-attr mode.
909     if (ATTR_EDIT_GROUP != groupName) {
910         ATTR_EDIT_GROUP = groupName;
911         loadAttrEditorGrid();
912     }
913 }
914
915 function onAttrEditorOpen() {
916     // the "bars" have the create/update/cancel/etc. buttons.
917     var create_bar = document.getElementById('attr-editor-create-bar');
918     var update_bar = document.getElementById('attr-editor-update-bar');
919     if (ATTR_EDITOR_IN_UPDATE_MODE) {
920         update_bar.style.display='table-row';
921         create_bar.style.display='none';
922         // hide the dropdown-button
923         idStyle('vl-create-attr-editor-button', 'visibility', 'hidden');
924     } else {
925         dijit.byId('attr-editor-dialog').reset();
926         create_bar.style.display='table-row';
927         update_bar.style.display='none';
928     }
929 }
930
931 function onAttrEditorClose() {
932     // reset the form to a "create" form. (We may have borrowed it for editing.)
933     ATTR_EDITOR_IN_UPDATE_MODE = false;
934     // show the dropdown-button
935     idStyle('vl-create-attr-editor-button', 'visibility', 'visible');
936 }
937
938 function loadAttrEditorGrid() {
939     var _data = (ATTR_EDIT_GROUP == 'auth') ? 
940         vqarad.toStoreData(authAttrDefs) : vqbrad.toStoreData(bibAttrDefs) ;
941                  
942     var store = new dojo.data.ItemFileReadStore({data:_data});
943     var model = new dojox.grid.data.DojoData(
944         null, store, {rowsPerPage: 100, clientSort: true, query:{id:'*'}});
945     attrEditorGrid.setModel(model);
946     attrEditorGrid.setStructure(vlAttrGridLayout);
947     attrEditorGrid.onRowClick = onAttrEditorClick;
948     attrEditorGrid.update();
949 }
950
951 function attrGridGetTag(n) {
952     // grid helper: return the tags from the row's xpath column.
953     var xp = this.grid.model.getRow(n);
954     return xp && xpathParser.parse(xp.xpath).tags;
955 }
956
957 function attrGridGetSubfield(n) {
958     // grid helper: return the subfields from the row's xpath column.
959     var xp = this.grid.model.getRow(n);
960     return xp && xpathParser.parse(xp.xpath).subfields;
961 }
962
963 function onAttrEditorClick(evt) {
964     var row = attrEditorGrid.model.getRow(evt.rowIndex);
965     ATTR_EDIT_ID = row.id;
966     ATTR_EDITOR_IN_UPDATE_MODE = true;
967
968     // populate the popup editor.
969     dojo.byId('attr-editor-code').value = row.code;
970     dojo.byId('attr-editor-description').value = row.description;
971     var parsed_xpath = xpathParser.parse(row.xpath);
972     dojo.byId('attr-editor-tags').value = parsed_xpath.tags;
973     dojo.byId('attr-editor-subfields').value = parsed_xpath.subfields;
974     dojo.byId('attr-editor-identifier').value = (row.ident ? 'True':'False');
975     dojo.byId('attr-editor-xpath').value = row.xpath;
976     dojo.byId('attr-editor-remove').value = row.remove;
977
978     // set up UI for editing
979     dojo.byId('vl-create-attr-editor-button').click();
980 }
981
982 function vlSaveAttrDefinition(data) {
983     idHide('vl-attr-editor-div');
984     idShow('vl-generic-progress');
985
986     data.id = ATTR_EDIT_ID;
987
988     // this ought to honour custom xpaths, but overwrite xpaths
989     // derived from tags/subfields.
990     if (data.xpath == '' || looksLikeDerivedXpath(data.xpath)) {
991         var _xpath = tagAndSubFieldsToXpath(data.tag, data.subfield);
992         data.xpath = _xpath;
993     }
994
995     // build up our permacrud params. Key variables here are
996     // "create or update" and "bib or auth".
997
998     var isAuth   = (ATTR_EDIT_GROUP == 'auth');
999     var isCreate = (ATTR_EDIT_ID == null);
1000     var rad      = isAuth ? new vqarad() : new vqbrad() ;
1001     var method   = 'open-ils.permacrud' + (isCreate ? '.create.' : '.update.') 
1002         + (isAuth ? 'vqarad' : 'vqbrad');
1003     var _data    = rad.fromStoreItem(data);
1004
1005     _data.ischanged(1);
1006
1007     fieldmapper.standardRequest(
1008         ['open-ils.permacrud', method],
1009         {   async: true,
1010             params: [authtoken, _data ],
1011             onresponse: function(r) { },
1012             oncomplete: function(r) {
1013                 attrEditorFetchAttrDefs(vlShowAttrEditor);
1014                 ATTR_EDIT_ID = null;
1015             },
1016             onerror: function(r) {
1017                 alert('vlSaveAttrDefinition comms error: ' + r);
1018             }
1019         }
1020     );
1021 }
1022
1023 function attrEditorFetchAttrDefs(callback) {
1024     var fn = (ATTR_EDIT_GROUP == 'auth') ? vlFetchAuthAttrDefs : vlFetchBibAttrDefs;
1025     return fn(callback);
1026 }
1027
1028 function vlAttrDelete() {
1029     idHide('vl-attr-editor-div');
1030     idShow('vl-generic-progress');
1031
1032     var isAuth = (ATTR_EDIT_GROUP == 'auth');
1033     var method = 'open-ils.permacrud.delete.' + (isAuth ? 'vqarad' : 'vqbrad');
1034     var rad    = isAuth ? new vqarad() : new vqbrad() ;
1035     fieldmapper.standardRequest(
1036         ['open-ils.permacrud', method],
1037         {   async: true,
1038             params: [authtoken, rad.fromHash({ id : ATTR_EDIT_ID }), ],
1039             oncomplete: function() {
1040                 dijit.byId('attr-editor-dialog').onCancel(); // close the dialog
1041                 attrEditorFetchAttrDefs(vlShowAttrEditor);
1042                 ATTR_EDIT_ID = null;
1043             },
1044             onerror: function(r) {
1045                 alert('vlAttrDelete comms error: ' + r);
1046             }
1047         }
1048     );
1049 }
1050
1051 // ------------------------------------------------------------
1052 // utilities for attribute editors
1053
1054 // dom utilities (maybe dojo does these, and these should be replaced)
1055
1056 function idStyle(obId, k, v)    { document.getElementById(obId).style[k] = v;   }
1057 function idShow(obId)           { idStyle(obId, 'display', 'block');            }
1058 function idHide(obId)           { idStyle(obId, 'display' , 'none');            }
1059
1060 function connectTooltip(fieldId) {
1061     // Given an element id, look up a tooltip element in the doc (same
1062     // id with a '-tip' suffix) and associate the two. Maybe dojo has
1063     // a better way to do this?
1064     var fld = dojo.byId(fieldId);
1065     var tip = dojo.byId(fieldId + '-tip');
1066     dojo.connect(fld, 'onfocus', function(evt) {
1067                      dijit.showTooltip(tip.innerHTML, fld, ['below', 'after']); });
1068     dojo.connect(fld, 'onblur', function(evt) { dijit.hideTooltip(fld); });
1069 }
1070
1071 // xpath utilities
1072
1073 var xpathParser = new openils.MarcXPathParser();
1074
1075 function tagAndSubFieldsToXpath(tags, subfields) {
1076     // given tags, and subfields, build up an XPath.
1077     try {
1078         var parts = {
1079             'tags':tags.match(/[\d]+/g), 
1080             'subfields':subfields.match(/[a-zA-z]/g) };
1081         return xpathParser.compile(parts);
1082     } catch (err) {
1083         return {'parts':null, 'tags':null, 'error':err};
1084     }
1085 }
1086
1087 function looksLikeDerivedXpath(path) {
1088     // Does this path look like it was derived from tags and subfields?
1089     var parsed = xpathParser.parse(path);
1090     if (parsed.tags == null) 
1091         return false;
1092     var compiled = xpathParser.compile(parsed);
1093     return (path == compiled);
1094 }
1095
1096 // amazing xpath-util unit-tests
1097 if (!looksLikeDerivedXpath('//*[@tag="901"]/*[@code="c"]'))     alert('vandelay xpath-utility error');
1098 if ( looksLikeDerivedXpath('ba-boo-ba-boo!'))                   alert('vandelay xpath-utility error');