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