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