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