]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/vandelay/vandelay.js
Vandelay: default match set
[working/Evergreen.git] / Open-ILS / web / js / ui / default / 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.FilteringSelect"); 
19 dojo.require("dijit.layout.ContentPane");
20 dojo.require("dijit.layout.TabContainer");
21 dojo.require("dijit.layout.LayoutContainer");
22 dojo.require('dijit.form.Button');
23 dojo.require('dijit.form.CheckBox');
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.DataGrid');
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("fieldmapper.OrgUtils");
36 dojo.require('openils.CGI');
37 dojo.require('openils.User');
38 dojo.require('openils.Event');
39 dojo.require('openils.Util');
40 dojo.require('openils.MarcXPathParser');
41 dojo.require('openils.widget.GridColumnPicker');
42 dojo.require('openils.PermaCrud');
43 dojo.require('openils.widget.OrgUnitFilteringSelect');
44 dojo.require('openils.widget.AutoGrid');
45 dojo.require('openils.widget.AutoFieldWidget');
46 dojo.require('openils.widget.ProgressDialog');
47
48
49 var globalDivs = [
50     'vl-generic-progress',
51     'vl-generic-progress-with-total',
52     'vl-marc-upload-div',
53     'vl-queue-div',
54     'vl-match-div',
55     'vl-marc-html-div',
56     'vl-queue-select-div',
57     'vl-marc-upload-status-div',
58     'vl-attr-editor-div',
59     'vl-marc-export-div',
60     'vl-profile-editor-div',
61     'vl-item-attr-editor-div',
62     'vl-import-error-div'
63 ];
64
65 var authtoken;
66 var VANDELAY_URL = '/vandelay-upload';
67 var bibAttrDefs = [];
68 var authAttrDefs = [];
69 var queuedRecords = [];
70 var queuedRecordsMap = {};
71 var bibAttrsFetched = false;
72 var authAttrsFetched = false;
73 var attrDefMap = {}; // maps attr def code names to attr def ids
74 var currentType;
75 var currentQueueId = null;
76 var userCache = {};
77 var currentMatchedRecords; // set of loaded matched bib records
78 var currentOverlayRecordsMap; // map of import record to overlay record
79 var currentOverlayRecordsMapGid; // map of import record to overlay record grid id
80 var currentImportRecId; // when analyzing matches, this is the current import record
81 var userBibQueues = []; // only non-complete queues
82 var userAuthQueues = []; // only non-complete queues
83 var allUserBibQueues;
84 var allUserAuthQueues;
85 var selectableGridRecords;
86 var cgi = new openils.CGI();
87 var vlQueueGridColumePicker = {};
88 var vlBibSources = [];
89 var importItemDefs = [];
90 var matchSets = {};
91 var mergeProfiles = [];
92 var copyStatusCache = {};
93 var copyLocationCache = {};
94 var localeStrings;
95
96 // org settings
97 var orgSettings = {};
98 const DEFAULT_MATCH_SET = 'vandelay.default_match_set';
99
100 /**
101   * Grab initial data
102   */
103 function vlInit() {
104
105     dojo.requireLocalization("openils.vandelay", "vandelay");
106     localeStrings = dojo.i18n.getLocalization("openils.vandelay", "vandelay");
107
108     authtoken = openils.User.authtoken;
109     var initNeeded = 8; // how many async responses do we need before we're init'd 
110     var initCount = 0; // how many async reponses we've received
111
112     openils.Util.registerEnterHandler(
113         vlQueueDisplayPage.domNode, function(){retrieveQueuedRecords();});
114     openils.Util.addCSSClass(dojo.byId('vl-menu-marc-upload'), 'toolbar_selected');
115
116     function checkInitDone() {
117         initCount++;
118         if(initCount == initNeeded)
119             runStartupCommands();
120     }
121
122     mergeProfiles = new openils.PermaCrud().retrieveAll('vmp');
123     vlUploadMergeProfile.store = new dojo.data.ItemFileReadStore({data:fieldmapper.vmp.toStoreData(mergeProfiles)});
124     vlUploadMergeProfile.labelAttr = 'name';
125     vlUploadMergeProfile.searchAttr = 'name';
126     vlUploadMergeProfile.startup();
127
128     vlUploadMergeProfile2.store = new dojo.data.ItemFileReadStore({data:fieldmapper.vmp.toStoreData(mergeProfiles)});
129     vlUploadMergeProfile2.labelAttr = 'name';
130     vlUploadMergeProfile2.searchAttr = 'name';
131     vlUploadMergeProfile2.startup();
132
133     vlUploadFtMergeProfile.store = new dojo.data.ItemFileReadStore({data:fieldmapper.vmp.toStoreData(mergeProfiles)});
134     vlUploadFtMergeProfile.labelAttr = 'name';
135     vlUploadFtMergeProfile.searchAttr = 'name';
136     vlUploadFtMergeProfile.startup();
137
138     vlUploadFtMergeProfile2.store = new dojo.data.ItemFileReadStore({data:fieldmapper.vmp.toStoreData(mergeProfiles)});
139     vlUploadFtMergeProfile2.labelAttr = 'name';
140     vlUploadFtMergeProfile2.searchAttr = 'name';
141     vlUploadFtMergeProfile2.startup();
142
143
144     // Fetch the bib and authority attribute definitions 
145     vlFetchBibAttrDefs(function () { checkInitDone(); });
146     vlFetchAuthAttrDefs(function () { checkInitDone(); });
147
148     vlRetrieveQueueList('bib', null, 
149         function(list) {
150             allUserBibQueues = list;
151             for(var i = 0; i < allUserBibQueues.length; i++) {
152                 if(allUserBibQueues[i].complete() == 'f')
153                     userBibQueues.push(allUserBibQueues[i]);
154             }
155             checkInitDone();
156         }
157     );
158
159     vlRetrieveQueueList('auth', null, 
160         function(list) {
161             allUserAuthQueues = list;
162             for(var i = 0; i < allUserAuthQueues.length; i++) {
163                 if(allUserAuthQueues[i].complete() == 'f')
164                     userAuthQueues.push(allUserAuthQueues[i]);
165             }
166             checkInitDone();
167         }
168     );
169
170     fieldmapper.standardRequest(
171         ['open-ils.permacrud', 'open-ils.permacrud.search.cbs.atomic'],
172         {   async: true,
173             params: [authtoken, {id:{"!=":null}}, {order_by:{cbs:'id'}}],
174             oncomplete : function(r) {
175                 vlBibSources = openils.Util.readResponse(r, false, true);
176                 checkInitDone();
177             }
178         }
179     );
180
181     var owner = fieldmapper.aou.orgNodeTrail(fieldmapper.aou.findOrgUnit(new openils.User().user.ws_ou()));
182     new openils.PermaCrud().search('viiad', 
183         {owner: owner.map(function(org) { return org.id(); })},
184         {   async: true,
185             oncomplete: function(r) {
186                 importItemDefs = openils.Util.readResponse(r);
187                 checkInitDone();
188             }
189         }
190     );
191
192     new openils.PermaCrud().search('vms',
193         {owner: owner.map(function(org) { return org.id(); })},
194         {   async: true,
195             oncomplete: function(r) {
196                 var sets = openils.Util.readResponse(r);
197                 dojo.forEach(sets, 
198                     function(set) {
199                         if(!matchSets[set.mtype()])
200                             matchSets[set.mtype()] = [];
201                         matchSets[set.mtype()].push(set);
202                     }
203                 );
204                 checkInitDone();
205             }
206         }
207     );
208
209     new openils.PermaCrud().retrieveAll('ccs',
210         {   async: true,
211             oncomplete: function(r) {
212                 var stats = openils.Util.readResponse(r);
213                 dojo.forEach(stats, function(stat){copyStatusCache[stat.id()] = stat});
214                 checkInitDone();
215             }
216         }
217     );
218
219     orgSettings = fieldmapper.aou.fetchOrgSettingBatch(openils.User.user.ws_ou(), [
220         DEFAULT_MATCH_SET
221     ]);
222     for(k in orgSettings)
223         if(orgSettings[k])
224             orgSettings[k] = orgSettings[k].value;
225
226     vlAttrEditorInit();
227     vlExportInit();
228 }
229
230
231 openils.Util.addOnLoad(vlInit);
232
233
234 // fetch the bib and authority attribute definitions
235
236 function vlFetchBibAttrDefs(postcomplete) {
237     bibAttrDefs = [];
238     fieldmapper.standardRequest(
239         ['open-ils.permacrud', 'open-ils.permacrud.search.vqbrad'],
240         {   async: true,
241             params: [authtoken, {id:{'!=':null}}],
242             onresponse: function(r) {
243                 var def = r.recv().content(); 
244                 if(e = openils.Event.parse(def[0])) 
245                     return alert(e);
246                 bibAttrDefs.push(def);
247             },
248             oncomplete: function() {
249                 bibAttrDefs = bibAttrDefs.sort(
250                     function(a, b) {
251                         if(a.id() > b.id()) return 1;
252                         if(a.id() < b.id()) return -1;
253                         return 0;
254                     }
255                 );
256                 postcomplete();
257             }
258         }
259     );
260 }
261
262 function vlFetchAuthAttrDefs(postcomplete) {
263     authAttrDefs = [];
264     fieldmapper.standardRequest(
265         ['open-ils.permacrud', 'open-ils.permacrud.search.vqarad'],
266         {   async: true,
267             params: [authtoken, {id:{'!=':null}}],
268             onresponse: function(r) {
269                 var def = r.recv().content(); 
270                 if(e = openils.Event.parse(def[0])) 
271                     return alert(e);
272                 authAttrDefs.push(def);
273             },
274             oncomplete: function() {
275                 authAttrDefs = authAttrDefs.sort(
276                     function(a, b) {
277                         if(a.id() > b.id()) return 1;
278                         if(a.id() < b.id()) return -1;
279                         return 0;
280                     }
281                 );
282                 postcomplete();
283             }
284         }
285     );
286 }
287
288 function vlRetrieveQueueList(type, filter, onload) {
289     type = (type == 'bib') ? type : 'authority';
290     fieldmapper.standardRequest(
291         ['open-ils.vandelay', 'open-ils.vandelay.'+type+'_queue.owner.retrieve.atomic'],
292         {   async: true,
293             params: [authtoken, null, filter],
294             oncomplete: function(r) {
295                 var list = r.recv().content();
296                 if(e = openils.Event.parse(list[0]))
297                     return alert(e);
298                 onload(list);
299             }
300         }
301     );
302
303 }
304
305 function displayGlobalDiv(id) {
306     for(var i = 0; i < globalDivs.length; i++) {
307         try {
308             dojo.style(dojo.byId(globalDivs[i]), 'display', 'none');
309         } catch(e) {
310             alert('please define div ' + globalDivs[i]);
311         }
312     }
313     dojo.style(dojo.byId(id),'display','block');
314
315     openils.Util.removeCSSClass(dojo.byId('vl-menu-marc-export'), 'toolbar_selected');
316     openils.Util.removeCSSClass(dojo.byId('vl-menu-marc-upload'), 'toolbar_selected');
317     openils.Util.removeCSSClass(dojo.byId('vl-menu-queue-select'), 'toolbar_selected');
318     openils.Util.removeCSSClass(dojo.byId('vl-menu-attr-editor'), 'toolbar_selected');
319     openils.Util.removeCSSClass(dojo.byId('vl-menu-profile-editor'), 'toolbar_selected');
320     openils.Util.removeCSSClass(dojo.byId('vl-menu-match-set-editor'), 'toolbar_selected');
321
322     if(dojo.byId('vl-match-set-iframe'))
323         dojo.byId('vl-match-set-editor-div').removeChild(dojo.byId('vl-match-set-iframe'));
324
325     switch(id) {
326         case 'vl-marc-export-div':
327             openils.Util.addCSSClass(dojo.byId('vl-menu-marc-export'), 'toolbar_selected');
328             break;
329         case 'vl-marc-upload-div':
330             openils.Util.addCSSClass(dojo.byId('vl-menu-marc-upload'), 'toolbar_selected');
331             break;
332         case 'vl-queue-select-div':
333             openils.Util.addCSSClass(dojo.byId('vl-menu-queue-select'), 'toolbar_selected');
334             break;
335         case 'vl-attr-editor-div':
336             openils.Util.addCSSClass(dojo.byId('vl-menu-attr-editor'), 'toolbar_selected');
337             break;
338         case 'vl-profile-editor-div':
339             openils.Util.addCSSClass(dojo.byId('vl-menu-profile-editor'), 'toolbar_selected');
340             break;
341         case 'vl-item-attr-editor-div':
342             openils.Util.addCSSClass(dojo.byId('vl-menu-import-item-attr-editor'), 'toolbar_selected');
343             break;
344         case 'vl-match-set-editor-div':
345             openils.Util.addCSSClass(dojo.byId('vl-menu-match-set-editor'), 'toolbar_selected');
346             break;
347     }
348 }
349
350 function runStartupCommands() {
351     openils.Util.hide(dojo.byId('vl-page-loading'));
352     openils.Util.show(dojo.byId('vl-body-wrapper'));
353     currentQueueId = cgi.param('qid');
354     currentType = cgi.param('qtype');
355     dojo.style('vl-nav-bar', 'visibility', 'visible');
356     if(currentQueueId)
357         return retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
358     if (cgi.param('page') == 'inspectq')
359         return vlShowQueueSelect();
360         
361     vlShowUploadForm();
362 }
363
364 /**
365   * asynchronously upload a file of MARC records
366   */
367 function uploadMARC(onload){
368     dojo.byId('vl-upload-status-count').innerHTML = '0';
369     dojo.byId('vl-ses-input').value = authtoken;
370     displayGlobalDiv('vl-marc-upload-status-div');
371     dojo.io.iframe.send({
372         url: VANDELAY_URL,
373         method: "post",
374         handleAs: "html",
375         form: dojo.byId('vl-marc-upload-form'),
376         handle: function(data,ioArgs){
377             var content = data.documentElement.textContent;
378             onload(content);
379         }
380     });
381 }       
382
383 /**
384   * Creates a new vandelay queue
385   */
386 function createQueue(queueName, type, onload, importDefId, matchSet) {
387     var name = (type=='bib') ? 'bib' : 'authority';
388     var method = 'open-ils.vandelay.'+ name +'_queue.create'
389
390     var qType = name;
391     if (vlUploadRecordType.getValue().match(/acq/)) 
392         var qType = 'acq';
393
394     console.log('record type ' + vlUploadRecordType.getValue());
395     console.log('record type ' + vlUploadRecordType.getValue());
396
397     fieldmapper.standardRequest(
398         ['open-ils.vandelay', method],
399         {   async: true,
400             params: [authtoken, queueName, null, qType, matchSet, importDefId],
401             oncomplete : function(r) {
402                 var queue = r.recv().content();
403                 if(e = openils.Event.parse(queue)) 
404                     return alert(e);
405                 onload(queue);
406             }
407         }
408     );
409 }
410
411 /**
412   * Tells vandelay to pull a batch of records from the cache and explode them
413   * out into the vandelay tables
414   */
415 function processSpool(key, queueId, type, onload) {
416     fieldmapper.standardRequest(
417         ['open-ils.vandelay', 'open-ils.vandelay.'+type+'.process_spool'],
418         {   async: true,
419             params: [authtoken, key, queueId],
420             onresponse : function(r) {
421                 var resp = r.recv().content();
422                 if(e = openils.Event.parse(resp)) 
423                     return alert(e);
424                 dojo.byId('vl-upload-status-count').innerHTML = resp;
425             },
426             oncomplete : function(r) {onload();}
427         }
428     );
429 }
430
431 function vlExportInit() {
432
433     // queue export
434     var qsel = dojo.byId('vl-queue-export-options');
435     qsel.onchange = function(newVal) {
436         var value = qsel.options[qsel.selectedIndex].value;
437         qsel.selectedIndex = 0;
438         if(!value) return;
439         if(!confirm('Export as "' + value + '"?')) return; // TODO: i18n
440         retrieveQueuedRecords(
441             currentType, 
442             currentQueueId, 
443             function(r) { 
444                 exportHandler(value, r);
445                 displayGlobalDiv('vl-queue-div');
446             },
447             value
448         );
449     }
450
451     // item export
452     var isel = dojo.byId('vl-item-export-options');
453     isel.onchange = function(newVal) {
454         var value = isel.options[isel.selectedIndex].value;
455         isel.selectedIndex = 0;
456         if(!value) return;
457         if(!confirm('Export as "' + value + '"?')) return; // TODO: i18n
458
459         displayGlobalDiv('vl-generic-progress');
460         var method = 'open-ils.vandelay.import_item.queue.export.' + value + '.atomic';
461
462         fieldmapper.standardRequest(
463             ['open-ils.vandelay', method],
464             {
465                 params : [
466                     authtoken, 
467                     currentQueueId, 
468                     {with_import_error: (vlImportItemsShowErrors.checked) ? 1 : null}
469                 ],
470                 async : true,
471                 oncomplete : function(r) {exportHandler(value, r)}
472             }
473         );
474     }
475 }
476
477 function exportHandler(type, response) {
478     displayGlobalDiv('vl-import-error-div');
479     try {
480         var content = openils.Util.readResponse(response);
481         if (type=='email') {
482             if (content==1) { alert('Email sent.'); return; }
483             throw(content);
484         }
485         /* handle .atomic versus non-atomic method calls */
486         content = content.constructor == Array
487             ? content[0].template_output().data()
488             : content.template_output().data();
489         switch(type) {
490             case 'print':
491                 openils.Util.printHtmlString(content);
492             break;
493             case 'csv':
494                 //content = content.replace(/\\t/g,'\t'); // if we really wanted to do .tsv instead
495                 openils.XUL.contentToFileSaveDialog(content, null, {
496                     defaultString : 'VandelayExport.csv',
497                     defaultExtension : '.csv',
498                     filterName : 'CSV',
499                     filterExtension : '*.csv',
500                     filterAll : true
501                 } );
502             break;
503             default:
504                 alert('response = ' + response + '\tcontent:\n' + content);
505         }
506     } catch(E) {
507         alert('Error exporting data: ' + E);
508     }
509 }
510
511 function retrieveQueuedRecords(type, queueId, onload, doExport) {
512     displayGlobalDiv('vl-generic-progress');
513     queuedRecords = [];
514     queuedRecordsMap = {};
515     currentOverlayRecordsMap = {};
516     currentOverlayRecordsMapGid = {};
517     selectableGridRecords = {};
518
519     if(!type) type = currentType;
520     else currentType = type;
521     if(!queueId) queueId = currentQueueId;
522     else currentQueueId = queueId;
523     if(!onload) onload = handleRetrieveRecords;
524
525     var method = 'open-ils.vandelay.'+type+'_queue.records.retrieve';
526
527     if(doExport) method += '.export.' + doExport;
528     if(vlQueueGridShowMatches.checked)
529         method = method.replace('records', 'records.matches');
530
531     method += '.atomic';
532
533     var sel = dojo.byId('vl-queue-display-limit-selector');
534     var limit = parseInt(sel.options[sel.selectedIndex].value);
535     var offset = limit * parseInt(vlQueueDisplayPage.attr('value')-1);
536
537     var params =  [authtoken, queueId, {clear_marc: 1, offset: offset, limit: limit, flesh_import_items:1}];
538     if(vlQueueGridShowNonImport.checked)
539         params[2].non_imported = 1;
540
541     if(vlQueueGridShowImportErrors.checked)
542         params[2].with_import_error = 1;
543
544     fieldmapper.standardRequest(
545         ['open-ils.vandelay', method],
546         {   async: true,
547             params: params,
548             oncomplete: function(r){
549                 if(doExport) return onload(r);
550                 var recs = r.recv().content();
551                 if(e = openils.Event.parse(recs[0]))
552                     return alert(e);
553                 for(var i = 0; i < recs.length; i++) {
554                     var rec = recs[i];
555                     queuedRecords.push(rec);
556                     queuedRecordsMap[rec.id()] = rec;
557                 }
558                 onload();
559             }
560         }
561     );
562 }
563
564 function vlLoadMatchUI(recId) {
565     displayGlobalDiv('vl-generic-progress');
566     var queuedRec = queuedRecordsMap[recId];
567     var matches = queuedRec.matches();
568     var records = [];
569     currentImportRecId = recId;
570     for(var i = 0; i < matches.length; i++)
571         records.push(matches[i].eg_record());
572
573     var retrieve = ['open-ils.search', 'open-ils.search.biblio.record_entry.slim.retrieve'];
574     var params = [records];
575     if(currentType == 'auth') {
576         retrieve = ['open-ils.cat', 'open-ils.cat.authority.record.retrieve'];
577         params = [authtoken, records, {clear_marc:1}];
578     }
579
580     fieldmapper.standardRequest(
581         retrieve,
582         {   async: true,
583             params:params,
584             oncomplete: function(r) {
585                 var recs = r.recv().content();
586                 if(e = openils.Event.parse(recs))
587                     return alert(e);
588
589                 /* ui mangling */
590                 displayGlobalDiv('vl-match-div');
591                 resetVlMatchGridLayout();
592                 currentMatchedRecords = recs;
593                 vlMatchGrid.setStructure(vlMatchGridLayout);
594
595                 // build the data store of records with match information
596                 var dataStore = bre.toStoreData(recs, null, 
597                     {virtualFields:['_id', 'match_score', 'match_quality', 'rec_quality']});
598                 dataStore.identifier = '_id';
599
600                 var matchSeenMap = {};
601
602                 for(var i = 0; i < dataStore.items.length; i++) {
603                     var item = dataStore.items[i];
604                     item._id = i; // just need something unique
605                     for(var j = 0; j < matches.length; j++) {
606                         var match = matches[j];
607                         if(match.eg_record() == item.id && !matchSeenMap[match.id()]) {
608                             if(match.match_score)
609                                 item.match_score = match.match_score();
610                             item.match_quality = match.quality();
611                             item.rec_quality = queuedRec.quality();
612                             matchSeenMap[match.id()] = 1;
613                             break;
614                         }
615                     }
616                 }
617
618                 // now populate the grid
619                 vlPopulateMatchGrid(vlMatchGrid, dataStore);
620             }
621         }
622     );
623 }
624
625 function vlPopulateMatchGrid(grid, data) {
626     var store = new dojo.data.ItemFileReadStore({data:data});
627     grid.setStore(store);
628     grid.update();
629 }
630
631 function showMe(id) {
632     dojo.style(dojo.byId(id), 'display', 'block');
633 }
634 function hideMe(id) {
635     dojo.style(dojo.byId(id), 'display', 'none');
636 }
637
638
639 function vlLoadMARCHtml(recId, inCat, oncomplete) {
640     dijit.byId('vl-marc-html-done-button').onClick = oncomplete;
641     displayGlobalDiv('vl-generic-progress');
642     var api;
643     var params = [recId, 1];
644
645     if(inCat) {
646         hideMe('vl-marc-html-edit-button'); // don't show marc editor button
647         dijit.byId('vl-marc-html-edit-button').onClick = function(){}
648         api = ['open-ils.search', 'open-ils.search.biblio.record.html'];
649         if(currentType == 'auth')
650             api = ['open-ils.search', 'open-ils.search.authority.to_html'];
651     } else {
652         showMe('vl-marc-html-edit-button'); // plug in the marc editor button
653         dijit.byId('vl-marc-html-edit-button').onClick = 
654             function() {vlLoadMarcEditor(currentType, recId, oncomplete);};
655         params = [authtoken, recId];
656         api = ['open-ils.vandelay', 'open-ils.vandelay.queued_bib_record.html'];
657         if(currentType == 'auth')
658             api = ['open-ils.vandelay', 'open-ils.vandelay.queued_authority_record.html'];
659     }
660
661     fieldmapper.standardRequest(
662         api, 
663         {   async: true,
664             params: params,
665             oncomplete: function(r) {
666             displayGlobalDiv('vl-marc-html-div');
667                 var html = r.recv().content();
668                 dojo.byId('vl-marc-record-html').innerHTML = html;
669             }
670         }
671     );
672 }
673
674
675 /*
676 function getRecMatchesFromAttrCode(rec, attrCode) {
677     var matches = [];
678     var attr = getRecAttrFromCode(rec, attrCode);
679     for(var j = 0; j < rec.matches().length; j++) {
680         var match = rec.matches()[j];
681         if(match.matched_attr() == attr.id()) 
682             matches.push(match);
683     }
684     return matches;
685 }
686 */
687
688 /*
689 function getRecAttrFromMatch(rec, match) {
690     for(var i = 0; i < rec.attributes().length; i++) {
691         var attr = rec.attributes()[i];
692         if(attr.id() == match.matched_attr())
693             return attr;
694     }
695 }
696 */
697
698 function getRecAttrDefFromAttr(attr, type) {
699     var defs = (type == 'bib') ? bibAttrDefs : authAttrDefs;
700     for(var i = 0; i < defs.length; i++) {
701         var def = defs[i];
702         if(def.id() == attr.field())
703             return def;
704     }
705 }
706
707 function getRecAttrFromCode(rec, attrCode) {
708     var defId = attrDefMap[currentType][attrCode];
709     var attrs = rec.attributes();
710     for(var i = 0; i < attrs.length; i++) {
711         var attr = attrs[i];
712         if(attr.field() == defId) 
713             return attr;
714     }
715     return null;
716 }
717
718 function vlGetViewMatches(rowIdx, item) {
719     if(item) {
720         var id = this.grid.store.getValue(item, 'id');
721         var rec = queuedRecordsMap[id];
722         if(rec.matches().length > 0)
723             return id + ':' + rec.matches().length;
724     }
725     return -1
726 }
727
728 function vlFormatViewMatches(id) {
729     if(id == -1) return '';
730     var chunks = id.split(':');
731     id = chunks[0];
732     count = chunks[1];
733     return '<a href="javascript:void(0);" onclick="vlLoadMatchUI(' + id + ');">' + this.name + ' (' + count + ')</a>';
734 }
735
736 function vlGetViewErrors(rowIdx, item) {
737     if(item) {
738         var id = this.grid.store.getValue(item, 'id');
739         var rec = queuedRecordsMap[id];
740         // id:rec_error:item_import_error_count
741         return id + ':' + 
742             (rec.import_error() ? 1 : '') + ':' + 
743             (typeof rec.import_items == 'function'
744                 ? rec.import_items().filter(function(i) {return i.import_error()}).length
745                 :''
746             );
747     }
748     return -1
749 }
750
751 function vlFormatViewErrors(chunk) {
752     if(chunk == -1) return '';
753     var id = chunk.split(':')[0];
754     var rec = chunk.split(':')[1];
755     var count = chunk.split(':')[2];
756     var links = '';
757     if(rec) 
758         links += '<a href="javascript:void(0);" onclick="vlLoadErrorUI(' + id + ');">Record</a><br/>'; // TODO I18N
759     if(Number(count))
760         links += '<a href="javascript:void(0);" onclick="vlLoadErrorUI(' + id + ');">Items ('+count+')</a>'; // TODO I18N
761     return links;
762 }
763
764 //var vlItemErrorColumnPicker;
765 function vlLoadErrorUI(id) {
766
767     displayGlobalDiv('vl-import-error-div');
768     openils.Util.hide('vl-import-error-grid-all');
769     openils.Util.show('vl-import-error-record');
770
771     var rec = queuedRecordsMap[id];
772
773     dojo.byId('vl-error-id').innerHTML = rec.id();
774     dojo.forEach( // TODO sane authority rec. fields
775         ['title', 'author', 'isbn', 'issn', 'upc'],
776         function(field) {
777             var attr =  getRecAttrFromCode(rec, field);
778             var eid = 'vl-error-' + field;
779             if(attr) {
780                 openils.Util.show(dojo.byId(eid).parentNode, 'table-row');
781                 dojo.byId(eid).innerHTML = attr.attr_value();
782             } else {
783                 openils.Util.hide(dojo.byId(eid).parentNode);
784             }
785         }
786     );
787     var iediv = dojo.byId('vl-error-import-error');
788     var eddiv = dojo.byId('vl-error-error-detail');
789     if(rec.import_error()) {
790         openils.Util.show(iediv.parentNode, 'table-row');
791         openils.Util.show(eddiv.parentNode, 'table-row');
792         iediv.innerHTML = rec.import_error();
793         eddiv.innerHTML = rec.error_detail();
794     } else {
795         openils.Util.hide(iediv.parentNode);
796         openils.Util.hide(eddiv.parentNode);
797     }
798
799     var errorItems = rec.import_items().filter(function(i) {return i.import_error()});
800     if(errorItems.length) {
801         openils.Util.show('vl-import-error-grid-some');
802         storeData = vqbr.toStoreData(errorItems);
803         var store = new dojo.data.ItemFileReadStore({data:storeData});
804         vlImportErrorGrid.setStore(store);
805         vlImportErrorGrid.update();
806     } else {
807         openils.Util.hide('vl-import-error-grid-some');
808     }
809 }
810
811 function vlLoadErrorUIAll() {
812
813     displayGlobalDiv('vl-import-error-div');
814     openils.Util.hide('vl-import-error-grid-some');
815     openils.Util.hide('vl-import-error-record');
816     openils.Util.show('vl-import-error-grid-all');
817     vlAllImportErrorGrid.resetStore();
818
819     vlImportErrorGrid.displayOffset = 0;
820
821     vlAllImportErrorGrid.dataLoader = function() {
822
823         vlAllImportErrorGrid.showLoadProgressIndicator();
824
825         fieldmapper.standardRequest(
826             ['open-ils.vandelay', 'open-ils.vandelay.import_item.queue.retrieve'],
827             {
828                 async : true,
829                 params : [
830                     authtoken, currentQueueId, {   
831                         with_import_error: (vlImportItemsShowErrors.checked) ? 1 : null,
832                         offset : vlAllImportErrorGrid.displayOffset,
833                         limit : vlAllImportErrorGrid.displayLimit
834                     }
835                 ],
836                 onresponse : function(r) {
837                     var item = openils.Util.readResponse(r);
838                     if(!item) return;
839                     vlAllImportErrorGrid.store.newItem(vii.toStoreItem(item));
840                 },
841                 oncomplete : function() {
842                     vlAllImportErrorGrid.hideLoadProgressIndicator();
843                 }
844             }
845         );
846     };
847
848     vlAllImportErrorGrid.dataLoader();
849 }
850
851 function vlGetOrg(rowIdx, item) {
852     if(!item) return '';
853     var value = this.grid.store.getValue(item, this.field);
854     if(value) return fieldmapper.aou.findOrgUnit(value).shortname();
855     return '';
856 }
857
858 function vlCopyStatus(rowIdx, item) {
859     if(!item) return '';
860     var value = this.grid.store.getValue(item, this.field);
861     if(value) return copyStatusCache[value].name();
862     return '';
863 }
864
865 // Note, we don't pre-fetch all copy locations because there could be 
866 // a lot of them.  Instead, fetch-and-cache on demand.
867 function vlCopyLocation(rowIdx, item) {
868     if(item) {
869         var value = this.grid.store.getValue(item, this.field);
870         if(value) {
871             if(!copyLocationCache[value]) {
872                 copyLocationCache[value] = 
873                     new openils.PermaCrud().retrieve('acpl', value);
874             }
875             return copyLocationCache[value].name();
876         }
877     }
878     return '';
879 }
880
881 function vlFormatViewMatchMARC(id) {
882     return '<a href="javascript:void(0);" onclick="vlLoadMARCHtml(' + id + ', true, '+
883         'function(){displayGlobalDiv(\'vl-match-div\');});">' + this.name + '</a>';
884 }
885
886 function getAttrValue(rowIdx, item) {
887     if(!item) return '';
888     var attrCode = this.field.split('.')[1];
889     var rec = queuedRecordsMap[this.grid.store.getValue(item, 'id')];
890     var attr = getRecAttrFromCode(rec, attrCode);
891     return (attr) ? attr.attr_value() : '';
892 }
893
894 function vlGetDateTimeField(rowIdx, item) {
895     if(!item) return '';
896     var value = this.grid.store.getValue(item, this.field);
897     if(!value) return '';
898     var date = dojo.date.stamp.fromISOString(value);
899     return dojo.date.locale.format(date, {selector:'date'});
900 }
901
902 function vlGetCreator(rowIdx, item) {
903     if(!item) return '';
904     var id = this.grid.store.getValue(item, 'creator');
905     if(userCache[id])
906         return userCache[id].usrname();
907     var user = fieldmapper.standardRequest(
908         ['open-ils.actor', 'open-ils.actor.user.retrieve'], [authtoken, id]);
909     if(e = openils.Event.parse(user))
910         return alert(e);
911     userCache[id] = user;
912     return user.usrname();
913 }
914
915 function vlGetViewMARC(rowIdx, item) {
916     return item && this.grid.store.getValue(item, 'id');
917 }
918
919 function vlFormatViewMARC(id) {
920     return '<a href="javascript:void(0);" onclick="vlLoadMARCHtml(' + id + ', false, '+
921         'function(){displayGlobalDiv(\'vl-queue-div\');});">' + this.name + '</a>';
922 }
923
924 function vlGetOverlayTargetSelector(rowIdx, item) {
925     if(!item) return;
926     return this.grid.store.getValue(item, '_id') + ':' + this.grid.store.getValue(item, 'id');
927 }
928
929 function vlFormatOverlayTargetSelector(val) {
930     if(!val) return '';
931     var parts = val.split(':');
932     var _id = parts[0];
933     var id = parts[1];
934     var value = '<input type="checkbox" name="vl-overlay-target-RECID" '+
935         'onclick="vlHandleOverlayTargetSelected(ID, GRIDID);" gridid="GRIDID" match="ID"/>';
936     value = value.replace(/GRIDID/g, _id);
937     value = value.replace(/RECID/g, currentImportRecId);
938     value = value.replace(/ID/g, id);
939     if(_id == currentOverlayRecordsMapGid[currentImportRecId])
940         return value.replace('/>', 'checked="checked"/>');
941     return value;
942 }
943
944
945 /**
946   * see if the user has enabled overlays for the current match set and, 
947   * if so, map the current import record to the overlay target.
948   */
949 function vlHandleOverlayTargetSelected(recId, gridId) {
950     var noneSelected = true;
951     var checkboxes = dojo.query('[name=vl-overlay-target-'+currentImportRecId+']');
952     for(var i = 0; i < checkboxes.length; i++) {
953         var checkbox = checkboxes[i];
954         var matchRecId = checkbox.getAttribute('match');
955         var gid = checkbox.getAttribute('gridid');
956         if(checkbox.checked) {
957             if(matchRecId == recId && gid == gridId) {
958                 noneSelected = false;
959                 currentOverlayRecordsMap[currentImportRecId] = matchRecId;
960                 currentOverlayRecordsMapGid[currentImportRecId] = gid;
961                 dojo.byId('vl-record-list-selected-' + currentImportRecId).checked = true;
962                 dojo.byId('vl-record-list-selected-' + currentImportRecId).parentNode.className = 'overlay_selected';
963             } else {
964                 checkbox.checked = false;
965             }
966         }
967     }
968
969     if(noneSelected) {
970         delete currentOverlayRecordsMap[currentImportRecId];
971         delete currentOverlayRecordsMapGid[currentImportRecId];
972         dojo.byId('vl-record-list-selected-' + currentImportRecId).checked = false;
973         dojo.byId('vl-record-list-selected-' + currentImportRecId).parentNode.className = '';
974     }
975 }
976
977 var valLastQueueType = null;
978 var vlQueueGridLayout = null;
979 function buildRecordGrid(type) {
980     displayGlobalDiv('vl-queue-div');
981
982     vlBibQueueGrid.canSort = function(col){ if(Math.abs(col) == 1) { return false; } else { return true; } }; 
983     vlAuthQueueGrid.canSort = function(col){ if(Math.abs(col) == 1) { return false; } else { return true; } }; 
984
985     if(type == 'bib') {
986         openils.Util.show('vl-bib-queue-grid-wrapper');
987         openils.Util.hide('vl-auth-queue-grid-wrapper');
988         vlQueueGrid = vlBibQueueGrid;
989         openils.Util.show('add-to-bucket-action', 'table-row');
990     } else {
991         openils.Util.show('vl-auth-queue-grid-wrapper');
992         openils.Util.hide('vl-bib-queue-grid-wrapper');
993         vlQueueGrid = vlAuthQueueGrid;
994         openils.Util.hide('add-to-bucket-action');
995     }
996
997
998     if(valLastQueueType != type) {
999         valLastQueueType = type;
1000         vlQueueGridLayout = vlQueueGrid.attr('structure');
1001         var defs = (type == 'bib') ? bibAttrDefs : authAttrDefs;
1002         attrDefMap[type] = {};
1003         for(var i = 0; i < defs.length; i++) {
1004             var def = defs[i]
1005             attrDefMap[type][def.code()] = def.id();
1006             var col = {
1007                 name:def.description(), 
1008                 field:'attr.' + def.code(),
1009                 get: getAttrValue,
1010                 selectableColumn:true
1011             };
1012             vlQueueGridLayout[0].cells[0].push(col);
1013         }
1014     }
1015
1016     dojo.forEach(vlQueueGridLayout[0].cells[0], 
1017         function(cell) { 
1018             if(cell.field.match(/^\+/)) 
1019                 cell.nonSelectable=true;
1020         }
1021     );
1022
1023     var storeData;
1024     if(type == 'bib')
1025         storeData = vqbr.toStoreData(queuedRecords);
1026     else
1027         storeData = vqar.toStoreData(queuedRecords);
1028
1029     var store = new dojo.data.ItemFileReadStore({data:storeData});
1030     vlQueueGrid.setStore(store);
1031
1032     if(vlQueueGridColumePicker[type]) {
1033         vlQueueGrid.update();
1034     } else {
1035
1036         vlQueueGridColumePicker[type] =
1037             new openils.widget.GridColumnPicker(
1038                 authtoken, 'vandelay.queue.'+type, vlQueueGrid, vlQueueGridLayout);
1039         vlQueueGridColumePicker[type].load();
1040     }
1041 }
1042
1043 function vlQueueGridPrevPage() {
1044     var page = parseInt(vlQueueDisplayPage.getValue());
1045     if(page < 2) return;
1046     vlQueueDisplayPage.setValue(page - 1);
1047     retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
1048 }
1049
1050 function vlQueueGridNextPage() {
1051     vlQueueDisplayPage.setValue(parseInt(vlQueueDisplayPage.getValue())+1);
1052     retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
1053 }
1054
1055 function vlDeleteQueue(type, queueId, onload) {
1056     fieldmapper.standardRequest(
1057         ['open-ils.vandelay', 'open-ils.vandelay.'+type+'_queue.delete'],
1058         {   async: false,
1059             params: [authtoken, queueId],
1060             oncomplete: function(r) {
1061                 var resp = r.recv().content();
1062                 if(e = openils.Event.parse(resp))
1063                     return alert(e);
1064                 onload();
1065             }
1066         }
1067     );
1068 }
1069
1070
1071 function vlQueueGridDrawSelectBox(rowIdx, item) {
1072     return item &&  this.grid.store.getValue(item, 'id');
1073 }
1074
1075 function vlQueueGridFormatSelectBox(id) {
1076     var domId = 'vl-record-list-selected-' + id;
1077     if (id) { selectableGridRecords[domId] = id; }
1078     return "<div><input type='checkbox' id='"+domId+"'/></div>";
1079 }
1080
1081 function vlSelectAllQueueGridRecords() {
1082     for(var id in selectableGridRecords) 
1083         dojo.byId(id).checked = true;
1084 }
1085 function vlSelectNoQueueGridRecords() {
1086     for(var id in selectableGridRecords) 
1087         dojo.byId(id).checked = false;
1088 }
1089 function vlToggleQueueGridSelect() {
1090     if(dojo.byId('vl-queue-grid-row-selector').checked)
1091         vlSelectAllQueueGridRecords();
1092     else
1093         vlSelectNoQueueGridRecords();
1094 }
1095
1096 var handleRetrieveRecords = function() {
1097     buildRecordGrid(currentType);
1098     vlFetchQueueSummary(currentQueueId, currentType, 
1099         function(summary) {
1100             dojo.byId('vl-queue-summary-name').innerHTML = summary.queue.name();
1101             dojo.byId('vl-queue-summary-total-count').innerHTML = summary.total +'';
1102             dojo.byId('vl-queue-summary-import-count').innerHTML = summary.imported + '';
1103             dojo.byId('vl-queue-summary-import-item-count').innerHTML = summary.total_items + '';
1104             dojo.byId('vl-queue-summary-import-item-imported-count').innerHTML = summary.total_items_imported + '';
1105             dojo.byId('vl-queue-summary-rec-error-count').innerHTML = summary.rec_import_errors + '';
1106             dojo.byId('vl-queue-summary-item-error-count').innerHTML = summary.item_import_errors + '';
1107            
1108             if (dojo.byId('create-bucket-dialog-name')) {
1109                 dojo.byId('create-bucket-dialog-name').value = summary.queue.name();
1110             }
1111         }
1112     );
1113 }
1114
1115 function vlFetchQueueSummary(qId, type, onload) {
1116     fieldmapper.standardRequest(
1117         ['open-ils.vandelay', 'open-ils.vandelay.'+type+'_queue.summary.retrieve'],
1118         {   async: true,
1119             params: [authtoken, qId],
1120             oncomplete : function(r) {
1121                 var summary = r.recv().content();
1122                 if(e = openils.Event.parse(summary))
1123                     return alert(e);
1124                 return onload(summary);
1125             }
1126         }
1127     );
1128 }
1129
1130 function handleCreateBucket(args) {
1131     var bname = dojo.byId('create-bucket-dialog-name').value;
1132     if (!bname) return;
1133
1134     progressDialog.show(true);
1135     fieldmapper.standardRequest(
1136         ['open-ils.vandelay', 'open-ils.vandelay.bib_queue.to_bucket'],
1137         {   async : true,
1138             params : [authtoken, currentQueueId, bname],
1139             oncomplete : function(r) {
1140                 progressDialog.hide();
1141                 setTimeout(function() { 
1142                     var resp = openils.Util.readResponse(r);
1143                     if (resp.add_count == 0) {
1144                         alert(localeStrings.NO_BUCKET_ITEMS);
1145                     } else {
1146                         alert(
1147                             dojo.string.substitute(
1148                                 localeStrings.BUCKET_CREATE_SUCCESS,
1149                                 [resp.add_count, bname, resp.item_count]
1150                             )
1151                         );
1152                     }
1153                 }, 200); // give the dialog a chance to hide
1154             }
1155         }
1156     );
1157 }
1158     
1159
1160 var _importCancelHandler;
1161 var _importGoHandler;
1162 function vlHandleQueueItemsAction(action) {
1163
1164     if(_importCancelHandler) dojo.disconnect(_importCancelHandler);
1165
1166     _importCancelHandler = dojo.connect(
1167         queueItemsImportCancelButton, 
1168         'onClick', 
1169         function() {
1170             queueItemsImportDialog.hide();
1171         }
1172     );
1173
1174     if(_importGoHandler)
1175         dojo.disconnect(_importGoHandler);
1176
1177     _importGoHandler = dojo.connect(
1178         queueItemsImportGoButton,
1179         'onClick', 
1180         function() {
1181             queueItemsImportDialog.hide();
1182
1183             // hack to set the widgets the import funcs will be looking at.  Reset them below.
1184             vlUploadQueueImportNoMatch.attr('value',  vlUploadQueueImportNoMatch2.attr('value'));
1185             vlUploadQueueAutoOverlayExact.attr('value',  vlUploadQueueAutoOverlayExact2.attr('value'));
1186             vlUploadQueueAutoOverlay1Match.attr('value',  vlUploadQueueAutoOverlay1Match2.attr('value'));
1187             vlUploadMergeProfile.attr('value',  vlUploadMergeProfile2.attr('value'));
1188             vlUploadFtMergeProfile.attr('value',  vlUploadFtMergeProfile2.attr('value'));
1189             vlUploadQueueAutoOverlayBestMatch.attr('value',  vlUploadQueueAutoOverlayBestMatch2.attr('value'));
1190             vlUploadQueueAutoOverlayBestMatchRatio.attr('value',  vlUploadQueueAutoOverlayBestMatchRatio2.attr('value'));
1191
1192             if(action == 'import') {
1193                 vlImportSelectedRecords();
1194             } else if(action == 'import_all') {
1195                 vlImportAllRecords();
1196             }
1197             
1198             // reset the widgets to prevent accidental future actions
1199             vlUploadQueueImportNoMatch.attr('value',  false);
1200             vlUploadQueueImportNoMatch2.attr('value', false);
1201             vlUploadQueueAutoOverlayExact.attr('value', false);
1202             vlUploadQueueAutoOverlayExact2.attr('value', false);
1203             vlUploadQueueAutoOverlay1Match.attr('value', false);
1204             vlUploadQueueAutoOverlay1Match2.attr('value', false);
1205             vlUploadMergeProfile.attr('value', '');
1206             vlUploadMergeProfile2.attr('value', '');
1207             vlUploadFtMergeProfile.attr('value', '');
1208             vlUploadFtMergeProfile2.attr('value', '');
1209             vlUploadQueueAutoOverlayBestMatch.attr('value', false);
1210             vlUploadQueueAutoOverlayBestMatch2.attr('value', false);
1211             vlUploadQueueAutoOverlayBestMatchRatio.attr('value', '0.0');
1212             vlUploadQueueAutoOverlayBestMatchRatio2.attr('value', '0.0');
1213         }
1214     );
1215
1216     queueItemsImportDialog.show();
1217 }
1218
1219 function vlHandleCreateBucket() {
1220
1221     create-bucket-dialog-name
1222 }
1223     
1224
1225 /* import user-selected records */
1226 function vlImportSelectedRecords() {
1227     var records = [];
1228
1229     for(var id in selectableGridRecords) {
1230         if(dojo.byId(id).checked) {
1231             var recId = selectableGridRecords[id];
1232             var rec = queuedRecordsMap[recId];
1233             if(!rec.import_time()) 
1234                 records.push(recId);
1235         }
1236     }
1237
1238     vlImportRecordQueue(
1239         currentType, 
1240         currentQueueId, 
1241         records,
1242         function(){
1243             retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
1244         }
1245     );
1246 }
1247
1248 /* import all (non-imported) queue records */
1249 function vlImportAllRecords() {
1250     vlImportRecordQueue(
1251         currentType, 
1252         currentQueueId, 
1253         null,
1254         function(){
1255             retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
1256         }
1257     );
1258 }
1259
1260 /* if recList has values, import only those records */
1261 function vlImportRecordQueue(type, queueId, recList, onload) {
1262     displayGlobalDiv('vl-generic-progress-with-total');
1263
1264     /* set up options */
1265     var options = {overlay_map : currentOverlayRecordsMap};
1266
1267     if(vlUploadQueueImportNoMatch.checked) {
1268         options.import_no_match = true;
1269         vlUploadQueueImportNoMatch.checked = false;
1270     }
1271
1272     if(vlUploadQueueAutoOverlayExact.checked) {
1273         options.auto_overlay_exact = true;
1274         vlUploadQueueAutoOverlayExact.checked = false;
1275     }
1276
1277     if(vlUploadQueueAutoOverlayBestMatch.checked) {
1278         options.auto_overlay_best_match = true;
1279         vlUploadQueueAutoOverlayBestMatch.checked = false;
1280         options.match_quality_ratio = vlUploadQueueAutoOverlayBestMatchRatio.attr('value');
1281     }
1282
1283     if(vlUploadQueueAutoOverlay1Match.checked) {
1284         options.auto_overlay_1match = true;
1285         vlUploadQueueAutoOverlay1Match.checked = false;
1286         options.match_quality_ratio = vlUploadQueueAutoOverlayBestMatchRatio.attr('value');
1287     }
1288
1289     var profile = vlUploadMergeProfile.attr('value');
1290     if(profile != null && profile != '') {
1291         options.merge_profile = profile;
1292     }
1293
1294     var ftprofile = vlUploadFtMergeProfile.attr('value');
1295     if(ftprofile != null && ftprofile != '') {
1296         options.fall_through_merge_profile = ftprofile;
1297     }
1298
1299
1300     /* determine which method we're calling */
1301
1302     var method = 'open-ils.vandelay.bib_queue.import';
1303     if(type == 'auth')
1304         method = method.replace('bib', 'auth');
1305
1306     var params = [authtoken, queueId, options];
1307     if(recList) {
1308         method = 'open-ils.vandelay.'+currentType+'_record.list.import';
1309         params[1] = recList;
1310     }
1311
1312     fieldmapper.standardRequest(
1313         ['open-ils.vandelay', method],
1314         {   async: true,
1315             params: params,
1316             onresponse: function(r) {
1317                 var resp = r.recv().content();
1318                 if(e = openils.Event.parse(resp))
1319                     return alert(e);
1320                 vlControlledProgressBar.update({maximum:resp.total, progress:resp.progress});
1321             },
1322             oncomplete: function() {onload();}
1323         }
1324     );
1325 }
1326
1327
1328 /**
1329   * Create queue, upload MARC, process spool, load the newly created queue 
1330   */
1331 function batchUpload() {
1332     var queueName = dijit.byId('vl-queue-name').getValue();
1333     currentType = dijit.byId('vl-record-type').getValue();
1334
1335     // could be bib-acq, which makes no sense in most places
1336     if (currentType.match(/bib/)) currentType = 'bib';
1337
1338     var handleProcessSpool = function() {
1339         if( 
1340             vlUploadQueueImportNoMatch.checked || 
1341             vlUploadQueueAutoOverlayExact.checked || 
1342             vlUploadQueueAutoOverlay1Match.checked ||
1343             vlUploadQueueAutoOverlayBestMatch.checked ) {
1344
1345                 vlImportRecordQueue(
1346                     currentType, 
1347                     currentQueueId, 
1348                     null,
1349                     function() {
1350                         retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
1351                     }
1352                 );
1353         } else {
1354             retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
1355         }
1356     }
1357
1358     var handleUploadMARC = function(key) {
1359         dojo.style(dojo.byId('vl-upload-status-processing'), 'display', 'block');
1360         processSpool(key, currentQueueId, currentType, handleProcessSpool);
1361     };
1362
1363     var handleCreateQueue = function(queue) {
1364         currentQueueId = queue.id();
1365         uploadMARC(handleUploadMARC);
1366     };
1367     
1368     if(vlUploadQueueSelector.getValue() && !queueName) {
1369         currentQueueId = vlUploadQueueSelector.getValue();
1370         uploadMARC(handleUploadMARC);
1371     } else {
1372         createQueue(queueName, currentType, handleCreateQueue, 
1373             vlUploadQueueHoldingsImportProfile.attr('value'),
1374             vlUploadQueueMatchSet.attr('value')
1375         );
1376     }
1377 }
1378
1379 // Inspect Queue no longer uses stores, so put type matching here instead
1380 function vlGetQueueData(type, asStore) {
1381     var filter;
1382     switch(type) {
1383         case 'bib-acq':
1384             filter = 'acq';
1385         case 'bib':
1386             if(!filter) filter = 'bib';
1387             var bibList = allUserBibQueues.filter(
1388                 function(q) {
1389                     return (q.queue_type() == filter);
1390                 }
1391             );
1392             if (!asStore) return bibList;
1393             return vbq.toStoreData(bibList);
1394         case 'auth':
1395             if (!asStore) return allUserAuthQueues;
1396             return vaq.toStoreData(allUserAuthQueues);
1397     }
1398 }
1399
1400 function vlFleshQueueSelect(selector, type) {
1401     var data = vlGetQueueData(type, true);
1402     selector.store = new dojo.data.ItemFileReadStore({data:data});
1403     selector.setValue(null);
1404     selector.setDisplayedValue('');
1405     if(data[0])
1406         selector.setValue(data[0].id());
1407
1408     var qInput = dijit.byId('vl-queue-name');
1409
1410     var selChange = function(val) {
1411         console.log('selector onchange');
1412         // user selected a queue from the selector;  clear the input and 
1413         // set the item import profile already defined for the queue
1414         var queue = allUserBibQueues.filter(function(q) { return (q.id() == val) })[0];
1415         if(val) {
1416             vlUploadQueueHoldingsImportProfile.attr('value', queue.item_attr_def() || '');
1417             vlUploadQueueHoldingsImportProfile.attr('disabled', true);
1418             vlUploadQueueMatchSet.attr('value', queue.match_set() || '');
1419             vlUploadQueueMatchSet.attr('disabled', true);
1420         } else {
1421             vlUploadQueueHoldingsImportProfile.attr('value', '');
1422             vlUploadQueueHoldingsImportProfile.attr('disabled', false);
1423             vlUploadQueueMatchSet.attr('value', '');
1424             vlUploadQueueMatchSet.attr('disabled', false);
1425         }
1426         dojo.disconnect(qInput._onchange);
1427         qInput.attr('value', '');
1428         qInput._onchange = dojo.connect(qInput, 'onChange', inputChange);
1429     }
1430     
1431     var inputChange = function(val) {
1432         console.log('qinput onchange');
1433         // user entered a new queue name. clear the selector 
1434         vlUploadQueueHoldingsImportProfile.attr('disabled', false);
1435         vlUploadQueueMatchSet.attr('disabled', false);
1436         dojo.disconnect(selector._onchange);
1437         selector.attr('value', '');
1438         selector._onchange = dojo.connect(selector, 'onChange', selChange);
1439     }
1440
1441     selector._onchange = dojo.connect(selector, 'onChange', selChange);
1442     qInput._onchange = dojo.connect(qInput, 'onChange', inputChange);
1443 }
1444
1445 function vlUpdateMatchSetSelector(type) {
1446     type = (type.match(/bib/)) ? 'biblio' : 'authority';
1447     vlUploadQueueMatchSet.store = 
1448         new dojo.data.ItemFileReadStore({data:vms.toStoreData(matchSets[type])});
1449     // apply default match set
1450     for (var i = 0; i < matchSets[type].length; i++) {
1451         if (matchSets[type][i].id() == orgSettings[DEFAULT_MATCH_SET]) {
1452             vlUploadQueueMatchSet.setValue(matchSets[type][i].id());
1453             break;
1454         }
1455     }
1456 }
1457
1458 function vlShowUploadForm() {
1459     displayGlobalDiv('vl-marc-upload-div');
1460     vlFleshQueueSelect(vlUploadQueueSelector, vlUploadRecordType.getValue());
1461     vlUploadSourceSelector.store = 
1462         new dojo.data.ItemFileReadStore({data:cbs.toStoreData(vlBibSources, 'source')});
1463     vlUploadSourceSelector.setValue(vlBibSources[0].id());
1464     vlUploadQueueHoldingsImportProfile.store = 
1465         new dojo.data.ItemFileReadStore({data:viiad.toStoreData(importItemDefs)});
1466     vlUpdateMatchSetSelector(vlUploadRecordType.getValue());
1467
1468     // use ratio from the merge profile if it's set
1469     dojo.connect(
1470         vlUploadMergeProfile, 
1471         'onChange',
1472         function(val) {
1473             if(!val) return;
1474             var profile = mergeProfiles.filter(function(p) { return (p.id() == val); })[0];
1475             if(profile.lwm_ratio() != null)
1476                vlUploadQueueAutoOverlayBestMatchRatio.attr('value', profile.lwm_ratio()+''); 
1477         }
1478     );
1479     dojo.connect(
1480         vlUploadMergeProfile2, 
1481         'onChange',
1482         function(val) {
1483             if(!val) return;
1484             var profile = mergeProfiles.filter(function(p) { return (p.id() == val); })[0];
1485             if(profile.lwm_ratio() != null)
1486                vlUploadQueueAutoOverlayBestMatchRatio2.attr('value', profile.lwm_ratio()+''); 
1487         }
1488     );
1489
1490 }
1491
1492 function vlDeleteSelectedQueues() {
1493     var checkboxes = document.getElementById('vlQueueSelectList').getElementsByTagName('input');
1494     var type = vlQueueSelectType.attr('value').replace(/-.*/, '');
1495     for(var i = 0; i < checkboxes.length; i++) {
1496         if(checkboxes[i].getAttribute('name') == 'delete' && checkboxes[i].checked) {
1497             vlDeleteQueue(type, checkboxes[i].getAttribute('value'), function () {});
1498         }
1499     }
1500     window.location.reload();
1501 }
1502
1503 function vlShowQueueSelect() {
1504     displayGlobalDiv('vl-queue-select-div');
1505     var type = vlQueueSelectType.attr('value');
1506     var data = vlGetQueueData(type, false);
1507     type = type.replace(/-.*/, ''); // To remove any sub-typeish info.
1508     var tbody = document.getElementById('vlQueueSelectList');
1509     // Clear it out
1510     while(tbody.firstChild) {
1511         tbody.removeChild(tbody.firstChild);
1512     }
1513     // Add entries
1514     for(var entry in data) {
1515         var name = data[entry].name();
1516         if(!name.match(/\S/))
1517             name = '-';
1518         var tr = document.createElement('tr');
1519         var td = document.createElement('td');
1520         var checkbox = document.createElement('input');
1521         checkbox.setAttribute('type', 'checkbox');
1522         checkbox.setAttribute('name', 'delete');
1523         checkbox.setAttribute('value', data[entry].id());
1524         td.appendChild(checkbox);
1525         tr.appendChild(td);
1526         td = document.createElement('td');
1527         var a = document.createElement('a');
1528         a.textContent = name;
1529         a.setAttribute('href', "javascript:retrieveQueuedRecords('" + type + "', " + data[entry].id() + ");");
1530         td.appendChild(a);
1531         tr.appendChild(td);
1532         tbody.appendChild(tr);
1533     }
1534 }
1535
1536 function vlShowMatchSetEditor() {
1537     displayGlobalDiv('vl-match-set-editor-div');
1538     dojo.byId('vl-match-set-editor-div').appendChild(
1539         dojo.create('iframe', {
1540             id : 'vl-match-set-iframe',
1541             src : oilsBasePath + '/conify/global/vandelay/match_set',
1542             style : 'width:100%; height:500px; border:none; margin:0px;'
1543         })
1544     );
1545 }
1546
1547 function vlOpenMarcEditWindow(rec, postReloadHTMLHandler) {
1548     /*
1549         To run in Firefox directly, must set signed.applets.codebase_principal_support
1550         to true in about:config
1551     */
1552     win = window.open('/xul/server/cat/marcedit.xul','','chrome'); // XXX version?
1553
1554     var type;
1555     if (currentType == 'bib') {
1556         type = 'bre';
1557     } else {
1558         type = 'are';
1559     }
1560
1561     function onsave(r) {
1562         // after the record is saved, reload the HTML display
1563         var stat = r.recv().content();
1564         if(e = openils.Event.parse(stat))
1565             return alert(e);
1566         alert(dojo.byId('vl-marc-edit-complete-label').innerHTML);
1567         win.close();
1568         vlLoadMARCHtml(rec.id(), false, postReloadHTMLHandler);
1569     }
1570
1571     win.xulG = {
1572         record : {marc : rec.marc(), "rtype": type},
1573         save : {
1574             label: dojo.byId('vl-marc-edit-save-label').innerHTML,
1575             func: function(xmlString) {
1576                 var method = 'open-ils.permacrud.update.' + rec.classname;
1577                 rec.marc(xmlString);
1578                 fieldmapper.standardRequest(
1579                     ['open-ils.permacrud', method],
1580                     {   async: true,
1581                         params: [authtoken, rec],
1582                         oncomplete: onsave
1583                     }
1584                 );
1585             },
1586         },
1587         'lock_tab' : typeof xulG != 'undefined' ? (typeof xulG['lock_tab'] != 'undefined' ? xulG.lock_tab : undefined) : undefined,
1588         'unlock_tab' : typeof xulG != 'undefined' ? (typeof xulG['unlock_tab'] != 'undefined' ? xulG.unlock_tab : undefined) : undefined
1589     };
1590 }
1591
1592 function vlLoadMarcEditor(type, recId, postReloadHTMLHandler) {
1593     var method = 'open-ils.permacrud.search.vqbr';
1594     if(currentType != 'bib')
1595         method = method.replace(/vqbr/,'vqar');
1596
1597     fieldmapper.standardRequest(
1598         ['open-ils.permacrud', method],
1599         {   async: true, 
1600             params: [authtoken, {id : recId}],
1601             oncomplete: function(r) {
1602                 var rec = r.recv().content();
1603                 if(e = openils.Event.parse(rec))
1604                     return alert(e);
1605                 vlOpenMarcEditWindow(rec, postReloadHTMLHandler);
1606             }
1607         }
1608     );
1609 }
1610
1611
1612
1613 //------------------------------------------------------------
1614 // attribute editors
1615
1616 // attribute-editor global variables
1617
1618 var ATTR_EDITOR_IN_UPDATE_MODE = false; // true on 'edit', false on 'create'
1619 var ATTR_EDIT_ID = null;                // id of current 'edit' attribute
1620 var ATTR_EDIT_GROUP = 'bib';            // bib-attrs or auth-attrs
1621
1622 function vlAttrEditorInit() {
1623     // set up tooltips on the edit form
1624     connectTooltip('attr-editor-tags'); 
1625     connectTooltip('attr-editor-subfields'); 
1626 }
1627
1628 function vlShowAttrEditor() {
1629     displayGlobalDiv('vl-attr-editor-div');
1630     loadAttrEditorGrid();
1631     idHide('vl-generic-progress');
1632 }
1633
1634 function setAttrEditorGroup(groupName) {
1635     // put us into 'bib'-attr or 'auth'-attr mode.
1636     if (ATTR_EDIT_GROUP != groupName) {
1637         ATTR_EDIT_GROUP = groupName;
1638         loadAttrEditorGrid();
1639     }
1640 }
1641
1642 function onAttrEditorOpen() {
1643     // the "bars" have the create/update/cancel/etc. buttons.
1644     var create_bar = document.getElementById('attr-editor-create-bar');
1645     var update_bar = document.getElementById('attr-editor-update-bar');
1646     if (ATTR_EDITOR_IN_UPDATE_MODE) {
1647         update_bar.style.display='table-row';
1648         create_bar.style.display='none';
1649         // hide the dropdown-button
1650         idStyle('vl-create-attr-editor-button', 'visibility', 'hidden');
1651     } else {
1652         dijit.byId('attr-editor-dialog').reset();
1653         create_bar.style.display='table-row';
1654         update_bar.style.display='none';
1655     }
1656 }
1657
1658 function onAttrEditorClose() {
1659     // reset the form to a "create" form. (We may have borrowed it for editing.)
1660     ATTR_EDITOR_IN_UPDATE_MODE = false;
1661     // show the dropdown-button
1662     idStyle('vl-create-attr-editor-button', 'visibility', 'visible');
1663 }
1664
1665 function loadAttrEditorGrid() {
1666     var _data = (ATTR_EDIT_GROUP == 'auth') ? 
1667         vqarad.toStoreData(authAttrDefs) : vqbrad.toStoreData(bibAttrDefs) ;
1668
1669     var store = new dojo.data.ItemFileReadStore({data:_data});
1670     attrEditorGrid.setStore(store);
1671     attrEditorGrid.onRowDblClick = onAttrEditorClick;
1672     attrEditorGrid.update();
1673 }
1674
1675 function attrGridGetTag(n, item) {
1676     // grid helper: return the tags from the row's xpath column.
1677     return item && xpathParser.parse(this.grid.store.getValue(item, 'xpath')).tags;
1678 }
1679
1680 function attrGridGetSubfield(n, item) {
1681     // grid helper: return the subfields from the row's xpath column.
1682     return item && xpathParser.parse(this.grid.store.getValue(item, 'xpath')).subfields;
1683 }
1684
1685 function onAttrEditorClick() {
1686     var row = this.getItem(this.focus.rowIndex);
1687     ATTR_EDIT_ID = this.store.getValue(row, 'id');
1688     ATTR_EDITOR_IN_UPDATE_MODE = true;
1689
1690     // populate the popup editor.
1691     dijit.byId('attr-editor-code').attr('value', this.store.getValue(row, 'code'));
1692     dijit.byId('attr-editor-description').attr('value', this.store.getValue(row, 'description'));
1693     var parsed_xpath = xpathParser.parse(this.store.getValue(row, 'xpath'));
1694     dijit.byId('attr-editor-tags').attr('value', parsed_xpath.tags);
1695     dijit.byId('attr-editor-subfields').attr('value', parsed_xpath.subfields);
1696     dijit.byId('attr-editor-xpath').attr('value', this.store.getValue(row, 'xpath'));
1697     dijit.byId('attr-editor-remove').attr('value', this.store.getValue(row, 'remove'));
1698
1699     // set up UI for editing
1700     dojo.byId('vl-create-attr-editor-button').click();
1701 }
1702
1703 function vlSaveAttrDefinition(data) {
1704     idHide('vl-attr-editor-div');
1705     idShow('vl-generic-progress');
1706
1707     data.id = ATTR_EDIT_ID;
1708
1709     // this ought to honour custom xpaths, but overwrite xpaths
1710     // derived from tags/subfields.
1711     if (data.xpath == '' || looksLikeDerivedXpath(data.xpath)) {
1712         var _xpath = tagAndSubFieldsToXpath(data.tag, data.subfield);
1713         data.xpath = _xpath;
1714     }
1715
1716     // build up our permacrud params. Key variables here are
1717     // "create or update" and "bib or auth".
1718
1719     var isAuth   = (ATTR_EDIT_GROUP == 'auth');
1720     var isCreate = (ATTR_EDIT_ID == null);
1721     var rad      = isAuth ? new vqarad() : new vqbrad() ;
1722     var method   = 'open-ils.permacrud' + (isCreate ? '.create.' : '.update.') 
1723         + (isAuth ? 'vqarad' : 'vqbrad');
1724     var _data    = rad.fromStoreItem(data);
1725
1726     _data.ischanged(1);
1727
1728     fieldmapper.standardRequest(
1729         ['open-ils.permacrud', method],
1730         {   async: true,
1731             params: [authtoken, _data ],
1732             onresponse: function(r) { },
1733             oncomplete: function(r) {
1734                 attrEditorFetchAttrDefs(vlShowAttrEditor);
1735                 ATTR_EDIT_ID = null;
1736             },
1737             onerror: function(r) {
1738                 alert('vlSaveAttrDefinition comms error: ' + r);
1739             }
1740         }
1741     );
1742 }
1743
1744 function attrEditorFetchAttrDefs(callback) {
1745     var fn = (ATTR_EDIT_GROUP == 'auth') ? vlFetchAuthAttrDefs : vlFetchBibAttrDefs;
1746     return fn(callback);
1747 }
1748
1749 function vlAttrDelete() {
1750     idHide('vl-attr-editor-div');
1751     idShow('vl-generic-progress');
1752
1753     var isAuth = (ATTR_EDIT_GROUP == 'auth');
1754     var method = 'open-ils.permacrud.delete.' + (isAuth ? 'vqarad' : 'vqbrad');
1755     var rad    = isAuth ? new vqarad() : new vqbrad() ;
1756     fieldmapper.standardRequest(
1757         ['open-ils.permacrud', method],
1758         {   async: true,
1759             params: [authtoken, rad.fromHash({ id : ATTR_EDIT_ID }), ],
1760             oncomplete: function() {
1761                 dijit.byId('attr-editor-dialog').onCancel(); // close the dialog
1762                 attrEditorFetchAttrDefs(vlShowAttrEditor);
1763                 ATTR_EDIT_ID = null;
1764             },
1765             onerror: function(r) {
1766                 alert('vlAttrDelete comms error: ' + r);
1767             }
1768         }
1769     );
1770 }
1771
1772 // ------------------------------------------------------------
1773 // utilities for attribute editors
1774
1775 // dom utilities (maybe dojo does these, and these should be replaced)
1776
1777 function idStyle(obId, k, v)    { document.getElementById(obId).style[k] = v;   }
1778 function idShow(obId)           { idStyle(obId, 'display', 'block');            }
1779 function idHide(obId)           { idStyle(obId, 'display' , 'none');            }
1780
1781 function connectTooltip(fieldId) {
1782     // Given an element id, look up a tooltip element in the doc (same
1783     // id with a '-tip' suffix) and associate the two. Maybe dojo has
1784     // a better way to do this?
1785     var fld = dojo.byId(fieldId);
1786     var tip = dojo.byId(fieldId + '-tip');
1787     dojo.connect(fld, 'onfocus', function(evt) {
1788                      dijit.showTooltip(tip.innerHTML, fld, ['below', 'after']); });
1789     dojo.connect(fld, 'onblur', function(evt) { dijit.hideTooltip(fld); });
1790 }
1791
1792 // xpath utilities
1793
1794 var xpathParser = new openils.MarcXPathParser();
1795
1796 function tagAndSubFieldsToXpath(tags, subfields) {
1797     // given tags, and subfields, build up an XPath.
1798     try {
1799         var parts = {
1800             'tags':tags.match(/[\d]+/g), 
1801             'subfields':subfields.match(/[a-zA-z]/g) };
1802         return xpathParser.compile(parts);
1803     } catch (err) {
1804         return {'parts':null, 'tags':null, 'error':err};
1805     }
1806 }
1807
1808 function looksLikeDerivedXpath(path) {
1809     // Does this path look like it was derived from tags and subfields?
1810     var parsed = xpathParser.parse(path);
1811     if (parsed.tags == null) 
1812         return false;
1813     var compiled = xpathParser.compile(parsed);
1814     return (path == compiled);
1815 }
1816
1817 // amazing xpath-util unit-tests
1818 if (!looksLikeDerivedXpath('//*[@tag="901"]/*[@code="c"]'))     alert('vandelay xpath-utility error');
1819 if ( looksLikeDerivedXpath('ba-boo-ba-boo!'))                   alert('vandelay xpath-utility error');
1820
1821
1822
1823 var profileContextOrg
1824 function vlShowProfileEditor() {
1825     displayGlobalDiv('vl-profile-editor-div');
1826     buildProfileGrid();
1827
1828     var connect = function() {
1829         dojo.connect(profileContextOrgSelector, 'onChange',
1830             function() {
1831                 profileContextOrg = this.attr('value');
1832                 pGrid.resetStore();
1833                 buildProfileGrid();
1834             }
1835         );
1836     };
1837
1838     new openils.User().buildPermOrgSelector(
1839         'ADMIN_MERGE_PROFILE', profileContextOrgSelector, null, connect);
1840 }
1841
1842 function buildProfileGrid() {
1843
1844     if(profileContextOrg == null)
1845         profileContextOrg = openils.User.user.ws_ou();
1846
1847     pGrid.loadAll( 
1848         {order_by : {vmp : 'name'}}, 
1849         {owner : fieldmapper.aou.fullPath(profileContextOrg, true)}
1850     );
1851 }
1852
1853 /* --- Import Item Attr Grid --------------- */
1854
1855 var itemAttrContextOrg;
1856 var itemAttrGridFirstTime = true;
1857 function vlShowImportItemAttrEditor() {
1858     displayGlobalDiv('vl-item-attr-editor-div');
1859
1860     if (itemAttrGridFirstTime) {
1861
1862         buildImportItemAttrGrid();
1863
1864         var connect = function() {
1865             dojo.connect(itemAttrContextOrgSelector, 'onChange',
1866                 function() {
1867                     itemAttrContextOrg = this.attr('value');
1868                     itemAttrGrid.resetStore();
1869                     buildImportItemAttrGrid();
1870                 }
1871             );
1872         };
1873
1874         new openils.User().buildPermOrgSelector(
1875             'ADMIN_IMPORT_ITEM_ATTR_DEF', 
1876                 itemAttrContextOrgSelector, null, connect);
1877
1878         itemAttrGridFirstTime = false;
1879     }
1880 }
1881
1882 function buildImportItemAttrGrid() {
1883
1884     if(itemAttrContextOrg == null)
1885         itemAttrContextOrg = openils.User.user.ws_ou();
1886
1887     itemAttrGrid.loadAll( 
1888         {order_by : {viiad : 'name'}}, 
1889         {owner : fieldmapper.aou.fullPath(itemAttrContextOrg, true)}
1890     );
1891 }
1892