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