]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/vandelay/vandelay.js
This fixes the checkbox selector in Vandelay's Inspect Queue interface, so you can...
[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
44
45 var globalDivs = [
46     'vl-generic-progress',
47     'vl-generic-progress-with-total',
48     'vl-marc-upload-div',
49     'vl-queue-div',
50     'vl-match-div',
51     'vl-marc-html-div',
52     'vl-queue-select-div',
53     'vl-marc-upload-status-div',
54     'vl-attr-editor-div',
55     'vl-marc-export-div'
56 ];
57
58 var authtoken;
59 var VANDELAY_URL = '/vandelay-upload';
60 var bibAttrDefs = [];
61 var authAttrDefs = [];
62 var queuedRecords = [];
63 var queuedRecordsMap = {};
64 var bibAttrsFetched = false;
65 var authAttrsFetched = false;
66 var attrDefMap = {}; // maps attr def code names to attr def ids
67 var currentType;
68 var currentQueueId = null;
69 var userCache = {};
70 var currentMatchedRecords; // set of loaded matched bib records
71 var currentOverlayRecordsMap; // map of import record to overlay record
72 var currentOverlayRecordsMapGid; // map of import record to overlay record grid id
73 var currentImportRecId; // when analyzing matches, this is the current import record
74 var userBibQueues = []; // only non-complete queues
75 var userAuthQueues = []; // only non-complete queues
76 var allUserBibQueues;
77 var allUserAuthQueues;
78 var selectableGridRecords;
79 var cgi = new openils.CGI();
80 var vlQueueGridColumePicker = {};
81 var vlBibSources = [];
82 var importItemDefs = [];
83
84 /**
85   * Grab initial data
86   */
87 function vlInit() {
88     authtoken = openils.User.authtoken;
89     var initNeeded = 6; // how many async responses do we need before we're init'd 
90     var initCount = 0; // how many async reponses we've received
91
92     openils.Util.registerEnterHandler(
93         vlQueueDisplayPage.domNode, function(){retrieveQueuedRecords();});
94     openils.Util.addCSSClass(dojo.byId('vl-menu-marc-upload'), 'toolbar_selected');
95
96     function checkInitDone() {
97         initCount++;
98         if(initCount == initNeeded)
99             runStartupCommands();
100     }
101
102     // Fetch the bib and authority attribute definitions 
103     vlFetchBibAttrDefs(function () { checkInitDone(); });
104     vlFetchAuthAttrDefs(function () { checkInitDone(); });
105
106     vlRetrieveQueueList('bib', null, 
107         function(list) {
108             allUserBibQueues = list;
109             for(var i = 0; i < allUserBibQueues.length; i++) {
110                 if(allUserBibQueues[i].complete() == 'f')
111                     userBibQueues.push(allUserBibQueues[i]);
112             }
113             checkInitDone();
114         }
115     );
116
117     vlRetrieveQueueList('auth', null, 
118         function(list) {
119             allUserAuthQueues = list;
120             for(var i = 0; i < allUserAuthQueues.length; i++) {
121                 if(allUserAuthQueues[i].complete() == 'f')
122                     userAuthQueues.push(allUserAuthQueues[i]);
123             }
124             checkInitDone();
125         }
126     );
127
128     fieldmapper.standardRequest(
129         ['open-ils.permacrud', 'open-ils.permacrud.search.cbs.atomic'],
130         {   async: true,
131             params: [authtoken, {id:{"!=":null}}, {order_by:{cbs:'id'}}],
132             oncomplete : function(r) {
133                 vlBibSources = openils.Util.readResponse(r, false, true);
134                 checkInitDone();
135             }
136         }
137     );
138
139     var owner = fieldmapper.aou.orgNodeTrail(fieldmapper.aou.findOrgUnit(new openils.User().user.ws_ou()));
140     new openils.PermaCrud().search('viiad', 
141         {owner: owner.map(function(org) { return org.id(); })},
142         {   async: true,
143             oncomplete: function(r) {
144                 importItemDefs = openils.Util.readResponse(r);
145                 checkInitDone();
146             }
147         }
148     );
149
150     vlAttrEditorInit();
151 }
152
153
154 openils.Util.addOnLoad(vlInit);
155
156
157 // fetch the bib and authority attribute definitions
158
159 function vlFetchBibAttrDefs(postcomplete) {
160     bibAttrDefs = [];
161     fieldmapper.standardRequest(
162         ['open-ils.permacrud', 'open-ils.permacrud.search.vqbrad'],
163         {   async: true,
164             params: [authtoken, {id:{'!=':null}}],
165             onresponse: function(r) {
166                 var def = r.recv().content(); 
167                 if(e = openils.Event.parse(def[0])) 
168                     return alert(e);
169                 bibAttrDefs.push(def);
170             },
171             oncomplete: function() {
172                 bibAttrDefs = bibAttrDefs.sort(
173                     function(a, b) {
174                         if(a.id() > b.id()) return 1;
175                         if(a.id() < b.id()) return -1;
176                         return 0;
177                     }
178                 );
179                 postcomplete();
180             }
181         }
182     );
183 }
184
185 function vlFetchAuthAttrDefs(postcomplete) {
186     authAttrDefs = [];
187     fieldmapper.standardRequest(
188         ['open-ils.permacrud', 'open-ils.permacrud.search.vqarad'],
189         {   async: true,
190             params: [authtoken, {id:{'!=':null}}],
191             onresponse: function(r) {
192                 var def = r.recv().content(); 
193                 if(e = openils.Event.parse(def[0])) 
194                     return alert(e);
195                 authAttrDefs.push(def);
196             },
197             oncomplete: function() {
198                 authAttrDefs = authAttrDefs.sort(
199                     function(a, b) {
200                         if(a.id() > b.id()) return 1;
201                         if(a.id() < b.id()) return -1;
202                         return 0;
203                     }
204                 );
205                 postcomplete();
206             }
207         }
208     );
209 }
210
211 function vlRetrieveQueueList(type, filter, onload) {
212     type = (type == 'bib') ? type : 'authority';
213     fieldmapper.standardRequest(
214         ['open-ils.vandelay', 'open-ils.vandelay.'+type+'_queue.owner.retrieve.atomic'],
215         {   async: true,
216             params: [authtoken, null, filter],
217             oncomplete: function(r) {
218                 var list = r.recv().content();
219                 if(e = openils.Event.parse(list[0]))
220                     return alert(e);
221                 onload(list);
222             }
223         }
224     );
225
226 }
227
228 function displayGlobalDiv(id) {
229     for(var i = 0; i < globalDivs.length; i++) {
230         try {
231             dojo.style(dojo.byId(globalDivs[i]), 'display', 'none');
232         } catch(e) {
233             alert('please define div ' + globalDivs[i]);
234         }
235     }
236     dojo.style(dojo.byId(id),'display','block');
237
238     openils.Util.removeCSSClass(dojo.byId('vl-menu-marc-export'), 'toolbar_selected');
239     openils.Util.removeCSSClass(dojo.byId('vl-menu-marc-upload'), 'toolbar_selected');
240     openils.Util.removeCSSClass(dojo.byId('vl-menu-queue-select'), 'toolbar_selected');
241     openils.Util.removeCSSClass(dojo.byId('vl-menu-attr-editor'), 'toolbar_selected');
242
243     switch(id) {
244         case 'vl-marc-export-div':
245             openils.Util.addCSSClass(dojo.byId('vl-menu-marc-export'), 'toolbar_selected');
246             break;
247         case 'vl-marc-upload-div':
248             openils.Util.addCSSClass(dojo.byId('vl-menu-marc-upload'), 'toolbar_selected');
249             break;
250         case 'vl-queue-select-div':
251             openils.Util.addCSSClass(dojo.byId('vl-menu-queue-select'), 'toolbar_selected');
252             break;
253         case 'vl-attr-editor-div':
254             openils.Util.addCSSClass(dojo.byId('vl-menu-attr-editor'), 'toolbar_selected');
255             break;
256     }
257 }
258
259 function runStartupCommands() {
260     currentQueueId = cgi.param('qid');
261     currentType = cgi.param('qtype');
262     dojo.style('vl-nav-bar', 'visibility', 'visible');
263     if(currentQueueId)
264         return retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
265     vlShowUploadForm();
266 }
267
268 /**
269   * asynchronously upload a file of MARC records
270   */
271 function uploadMARC(onload){
272     dojo.byId('vl-upload-status-count').innerHTML = '0';
273     dojo.byId('vl-ses-input').value = authtoken;
274     displayGlobalDiv('vl-marc-upload-status-div');
275     dojo.io.iframe.send({
276         url: VANDELAY_URL,
277         method: "post",
278         handleAs: "html",
279         form: dojo.byId('vl-marc-upload-form'),
280         handle: function(data,ioArgs){
281             var content = data.documentElement.textContent;
282             onload(content);
283         }
284     });
285 }       
286
287 /**
288   * Creates a new vandelay queue
289   */
290 function createQueue(queueName, type, onload) {
291     var name = (type=='bib') ? 'bib' : 'authority';
292     var method = 'open-ils.vandelay.'+ name +'_queue.create'
293     fieldmapper.standardRequest(
294         ['open-ils.vandelay', method],
295         {   async: true,
296             params: [authtoken, queueName, null, name],
297             oncomplete : function(r) {
298                 var queue = r.recv().content();
299                 if(e = openils.Event.parse(queue)) 
300                     return alert(e);
301                 onload(queue);
302             }
303         }
304     );
305 }
306
307 /**
308   * Tells vandelay to pull a batch of records from the cache and explode them
309   * out into the vandelay tables
310   */
311 function processSpool(key, queueId, type, onload) {
312     fieldmapper.standardRequest(
313         ['open-ils.vandelay', 'open-ils.vandelay.'+type+'.process_spool'],
314         {   async: true,
315             params: [authtoken, key, queueId],
316             onresponse : function(r) {
317                 var resp = r.recv().content();
318                 if(e = openils.Event.parse(resp)) 
319                     return alert(e);
320                 dojo.byId('vl-upload-status-count').innerHTML = resp;
321             },
322             oncomplete : function(r) {onload();}
323         }
324     );
325 }
326
327 function retrieveQueuedRecords(type, queueId, onload) {
328     displayGlobalDiv('vl-generic-progress');
329     queuedRecords = [];
330     queuedRecordsMap = {};
331     currentOverlayRecordsMap = {};
332     currentOverlayRecordsMapGid = {};
333     selectableGridRecords = {};
334     //resetVlQueueGridLayout();
335
336     if(!type) type = currentType;
337     if(!queueId) queueId = currentQueueId;
338     if(!onload) onload = handleRetrieveRecords;
339
340     var method = 'open-ils.vandelay.'+type+'_queue.records.retrieve.atomic';
341     if(vlQueueGridShowMatches.checked)
342         method = method.replace('records', 'records.matches');
343
344     var sel = dojo.byId('vl-queue-display-limit-selector');
345     var limit = parseInt(sel.options[sel.selectedIndex].value);
346     var offset = limit * parseInt(vlQueueDisplayPage.attr('value')-1);
347
348     var params =  [authtoken, queueId, {clear_marc: 1, offset: offset, limit: limit}];
349     if(vlQueueGridShowNonImport.checked)
350         params[2].non_imported = 1;
351
352     fieldmapper.standardRequest(
353         ['open-ils.vandelay', method],
354         {   async: true,
355             params: params,
356             /*
357             onresponse: function(r) {
358                 console.log("ONREPONSE");
359                 var rec = r.recv().content();
360                 if(e = openils.Event.parse(rec))
361                     return alert(e);
362                 console.log("got record " + rec.id());
363                 queuedRecords.push(rec);
364                 queuedRecordsMap[rec.id()] = rec;
365             },
366             */
367             oncomplete: function(r){
368                 var recs = r.recv().content();
369                 if(e = openils.Event.parse(recs[0]))
370                     return alert(e);
371                 for(var i = 0; i < recs.length; i++) {
372                     var rec = recs[i];
373                     queuedRecords.push(rec);
374                     queuedRecordsMap[rec.id()] = rec;
375                 }
376                 onload();
377             }
378         }
379     );
380 }
381
382 function vlLoadMatchUI(recId) {
383     displayGlobalDiv('vl-generic-progress');
384     var matches = queuedRecordsMap[recId].matches();
385     var records = [];
386     currentImportRecId = recId;
387     for(var i = 0; i < matches.length; i++)
388         records.push(matches[i].eg_record());
389
390     var retrieve = ['open-ils.search', 'open-ils.search.biblio.record_entry.slim.retrieve'];
391     var params = [records];
392     if(currentType == 'auth') {
393         retrieve = ['open-ils.cat', 'open-ils.cat.authority.record.retrieve'];
394         parmas = [authtoken, records, {clear_marc:1}];
395     }
396
397     fieldmapper.standardRequest(
398         retrieve,
399         {   async: true,
400             params:params,
401             oncomplete: function(r) {
402                 var recs = r.recv().content();
403                 if(e = openils.Event.parse(recs))
404                     return alert(e);
405
406                 /* ui mangling */
407                 displayGlobalDiv('vl-match-div');
408                 resetVlMatchGridLayout();
409                 currentMatchedRecords = recs;
410                 vlMatchGrid.setStructure(vlMatchGridLayout);
411
412                 // build the data store of records with match information
413                 var dataStore = bre.toStoreData(recs, null, 
414                     {virtualFields:['dest_matchpoint', 'src_matchpoint', '_id']});
415                 dataStore.identifier = '_id';
416
417                 var matchSeenMap = {};
418
419                 for(var i = 0; i < dataStore.items.length; i++) {
420                     var item = dataStore.items[i];
421                     item._id = i; // just need something unique
422                     for(var j = 0; j < matches.length; j++) {
423                         var match = matches[j];
424                         if(match.eg_record() == item.id && !matchSeenMap[match.id()]) {
425                             item.dest_matchpoint = match.field_type();
426                             var attr = getRecAttrFromMatch(queuedRecordsMap[recId], match);
427                             item.src_matchpoint = getRecAttrDefFromAttr(attr, currentType).code();
428                             matchSeenMap[match.id()] = 1;
429                             break;
430                         }
431                     }
432                 }
433
434                 // now populate the grid
435                 vlPopulateMatchGrid(vlMatchGrid, dataStore);
436             }
437         }
438     );
439 }
440
441 function vlPopulateMatchGrid(grid, data) {
442     var store = new dojo.data.ItemFileReadStore({data:data});
443     grid.setStore(store);
444     grid.update();
445 }
446
447 function showMe(id) {
448     dojo.style(dojo.byId(id), 'display', 'block');
449 }
450 function hideMe(id) {
451     dojo.style(dojo.byId(id), 'display', 'none');
452 }
453
454
455 function vlLoadMARCHtml(recId, inCat, oncomplete) {
456     dijit.byId('vl-marc-html-done-button').onClick = oncomplete;
457     displayGlobalDiv('vl-generic-progress');
458     var api;
459     var params = [recId, 1];
460
461     if(inCat) {
462         hideMe('vl-marc-html-edit-button'); // don't show marc editor button
463         dijit.byId('vl-marc-html-edit-button').onClick = function(){}
464         api = ['open-ils.search', 'open-ils.search.biblio.record.html'];
465         if(currentType == 'auth')
466             api = ['open-ils.search', 'open-ils.search.authority.to_html'];
467     } else {
468         showMe('vl-marc-html-edit-button'); // plug in the marc editor button
469         dijit.byId('vl-marc-html-edit-button').onClick = 
470             function() {vlLoadMarcEditor(currentType, recId, oncomplete);};
471         params = [authtoken, recId];
472         api = ['open-ils.vandelay', 'open-ils.vandelay.queued_bib_record.html'];
473         if(currentType == 'auth')
474             api = ['open-ils.vandelay', 'open-ils.vandelay.queued_authority_record.html'];
475     }
476
477     fieldmapper.standardRequest(
478         api, 
479         {   async: true,
480             params: params,
481             oncomplete: function(r) {
482             displayGlobalDiv('vl-marc-html-div');
483                 var html = r.recv().content();
484                 dojo.byId('vl-marc-record-html').innerHTML = html;
485             }
486         }
487     );
488 }
489
490
491 /*
492 function getRecMatchesFromAttrCode(rec, attrCode) {
493     var matches = [];
494     var attr = getRecAttrFromCode(rec, attrCode);
495     for(var j = 0; j < rec.matches().length; j++) {
496         var match = rec.matches()[j];
497         if(match.matched_attr() == attr.id()) 
498             matches.push(match);
499     }
500     return matches;
501 }
502 */
503
504 function getRecAttrFromMatch(rec, match) {
505     for(var i = 0; i < rec.attributes().length; i++) {
506         var attr = rec.attributes()[i];
507         if(attr.id() == match.matched_attr())
508             return attr;
509     }
510 }
511
512 function getRecAttrDefFromAttr(attr, type) {
513     var defs = (type == 'bib') ? bibAttrDefs : authAttrDefs;
514     for(var i = 0; i < defs.length; i++) {
515         var def = defs[i];
516         if(def.id() == attr.field())
517             return def;
518     }
519 }
520
521 function getRecAttrFromCode(rec, attrCode) {
522     var defId = attrDefMap[currentType][attrCode];
523     var attrs = rec.attributes();
524     for(var i = 0; i < attrs.length; i++) {
525         var attr = attrs[i];
526         if(attr.field() == defId) 
527             return attr;
528     }
529     return null;
530 }
531
532 function vlGetViewMatches(rowIdx, item) {
533     if(item) {
534         var id = this.grid.store.getValue(item, 'id');
535         var rec = queuedRecordsMap[id];
536         if(rec.matches().length > 0)
537             return id;
538     }
539     return -1
540 }
541
542 function vlFormatViewMatches(id) {
543     if(id == -1) return '';
544     return '<a href="javascript:void(0);" onclick="vlLoadMatchUI(' + id + ');">' + this.name + '</a>';
545 }
546
547 function vlFormatViewMatchMARC(id) {
548     return '<a href="javascript:void(0);" onclick="vlLoadMARCHtml(' + id + ', false, '+
549         'function(){displayGlobalDiv(\'vl-match-div\');});">' + this.name + '</a>';
550 }
551
552 function getAttrValue(rowIdx, item) {
553     if(!item) return '';
554     var attrCode = this.field.split('.')[1];
555     var rec = queuedRecordsMap[this.grid.store.getValue(item, 'id')];
556     var attr = getRecAttrFromCode(rec, attrCode);
557     return (attr) ? attr.attr_value() : '';
558 }
559
560 function vlGetDateTimeField(rowIdx, item) {
561     if(!item) return '';
562     var value = this.grid.store.getValue(item, this.field);
563     if(!value) return '';
564     var date = dojo.date.stamp.fromISOString(value);
565     return dojo.date.locale.format(date, {selector:'date'});
566 }
567
568 function vlGetCreator(rowIdx, item) {
569     if(!item) return '';
570     var id = this.grid.store.getValue(item, 'creator');
571     if(userCache[id])
572         return userCache[id].usrname();
573     var user = fieldmapper.standardRequest(
574         ['open-ils.actor', 'open-ils.actor.user.retrieve'], [authtoken, id]);
575     if(e = openils.Event.parse(user))
576         return alert(e);
577     userCache[id] = user;
578     return user.usrname();
579 }
580
581 function vlGetViewMARC(rowIdx, item) {
582     return item && this.grid.store.getValue(item, 'id');
583 }
584
585 function vlFormatViewMARC(id) {
586     return '<a href="javascript:void(0);" onclick="vlLoadMARCHtml(' + id + ', false, '+
587         'function(){displayGlobalDiv(\'vl-queue-div\');});">' + this.name + '</a>';
588 }
589
590 function vlGetOverlayTargetSelector(rowIdx, item) {
591     if(!item) return;
592     return this.grid.store.getValue(item, '_id') + ':' + this.grid.store.getValue(item, 'id');
593 }
594
595 function vlFormatOverlayTargetSelector(val) {
596     if(!val) return '';
597     var parts = val.split(':');
598     var _id = parts[0];
599     var id = parts[1];
600     var value = '<input type="checkbox" name="vl-overlay-target-RECID" '+
601         'onclick="vlHandleOverlayTargetSelected(ID, GRIDID);" gridid="GRIDID" match="ID"/>';
602     value = value.replace(/GRIDID/g, _id);
603     value = value.replace(/RECID/g, currentImportRecId);
604     value = value.replace(/ID/g, id);
605     if(_id == currentOverlayRecordsMapGid[currentImportRecId])
606         return value.replace('/>', 'checked="checked"/>');
607     return value;
608 }
609
610
611 /**
612   * see if the user has enabled overlays for the current match set and, 
613   * if so, map the current import record to the overlay target.
614   */
615 function vlHandleOverlayTargetSelected(recId, gridId) {
616     var noneSelected = true;
617     var checkboxes = dojo.query('[name=vl-overlay-target-'+currentImportRecId+']');
618     for(var i = 0; i < checkboxes.length; i++) {
619         var checkbox = checkboxes[i];
620         var matchRecId = checkbox.getAttribute('match');
621         var gid = checkbox.getAttribute('gridid');
622         if(checkbox.checked) {
623             if(matchRecId == recId && gid == gridId) {
624                 noneSelected = false;
625                 currentOverlayRecordsMap[currentImportRecId] = matchRecId;
626                 currentOverlayRecordsMapGid[currentImportRecId] = gid;
627                 dojo.byId('vl-record-list-selected-' + currentImportRecId).checked = true;
628                 dojo.byId('vl-record-list-selected-' + currentImportRecId).parentNode.className = 'overlay_selected';
629             } else {
630                 checkbox.checked = false;
631             }
632         }
633     }
634
635     if(noneSelected) {
636         delete currentOverlayRecordsMap[currentImportRecId];
637         delete currentOverlayRecordsMapGid[currentImportRecId];
638         dojo.byId('vl-record-list-selected-' + currentImportRecId).checked = false;
639         dojo.byId('vl-record-list-selected-' + currentImportRecId).parentNode.className = '';
640     }
641 }
642
643 var valLastQueueType = null;
644 var vlQueueGridLayout = null;
645 function buildRecordGrid(type) {
646     displayGlobalDiv('vl-queue-div');
647
648     if(type == 'bib') {
649         openils.Util.show('vl-bib-queue-grid-wrapper');
650         openils.Util.hide('vl-auth-queue-grid-wrapper');
651         vlQueueGrid = vlBibQueueGrid;
652     } else {
653         openils.Util.show('vl-auth-queue-grid-wrapper');
654         openils.Util.hide('vl-bib-queue-grid-wrapper');
655         vlQueueGrid = vlAuthQueueGrid;
656     }
657
658
659     if(valLastQueueType != type) {
660         valLastQueueType = type;
661         vlQueueGridLayout = vlQueueGrid.attr('structure');
662         var defs = (type == 'bib') ? bibAttrDefs : authAttrDefs;
663         attrDefMap[type] = {};
664         for(var i = 0; i < defs.length; i++) {
665             var def = defs[i]
666             attrDefMap[type][def.code()] = def.id();
667             var col = {
668                 name:def.description(), 
669                 field:'attr.' + def.code(),
670                 get: getAttrValue,
671                 selectableColumn:true
672             };
673             vlQueueGridLayout[0].cells[0].push(col);
674         }
675     }
676
677     dojo.forEach(vlQueueGridLayout[0].cells[0], 
678         function(cell) { 
679             if(cell.field.match(/^\+/)) 
680                 cell.nonSelectable=true;
681         }
682     );
683
684     var storeData;
685     if(type == 'bib')
686         storeData = vqbr.toStoreData(queuedRecords);
687     else
688         storeData = vqar.toStoreData(queuedRecords);
689
690     var store = new dojo.data.ItemFileReadStore({data:storeData});
691     vlQueueGrid.setStore(store);
692
693     if(vlQueueGridColumePicker[type]) {
694         vlQueueGrid.update();
695     } else {
696
697         vlQueueGridColumePicker[type] =
698             new openils.widget.GridColumnPicker(
699                 authtoken, 'vandelay.queue.'+type, vlQueueGrid, vlQueueGridLayout);
700         vlQueueGridColumePicker[type].load();
701     }
702 }
703
704 function vlQueueGridPrevPage() {
705     var page = parseInt(vlQueueDisplayPage.getValue());
706     if(page < 2) return;
707     vlQueueDisplayPage.setValue(page - 1);
708     retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
709 }
710
711 function vlQueueGridNextPage() {
712     vlQueueDisplayPage.setValue(parseInt(vlQueueDisplayPage.getValue())+1);
713     retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
714 }
715
716 function vlDeleteQueue(type, queueId, onload) {
717     fieldmapper.standardRequest(
718         ['open-ils.vandelay', 'open-ils.vandelay.'+type+'_queue.delete'],
719         {   async: true,
720             params: [authtoken, queueId],
721             oncomplete: function(r) {
722                 var resp = r.recv().content();
723                 if(e = openils.Event.parse(resp))
724                     return alert(e);
725                 onload();
726             }
727         }
728     );
729 }
730
731
732 function vlQueueGridDrawSelectBox(rowIdx, item) {
733     return item &&  this.grid.store.getValue(item, 'id');
734 }
735
736 function vlQueueGridFormatSelectBox(id) {
737     var domId = 'vl-record-list-selected-' + id;
738     if (id) { selectableGridRecords[domId] = id; }
739     return "<div><input type='checkbox' id='"+domId+"'/></div>";
740 }
741
742 function vlSelectAllQueueGridRecords() {
743     for(var id in selectableGridRecords) 
744         dojo.byId(id).checked = true;
745 }
746 function vlSelectNoQueueGridRecords() {
747     for(var id in selectableGridRecords) 
748         dojo.byId(id).checked = false;
749 }
750 function vlToggleQueueGridSelect() {
751     if(dojo.byId('vl-queue-grid-row-selector').checked)
752         vlSelectAllQueueGridRecords();
753     else
754         vlSelectNoQueueGridRecords();
755 }
756
757 var handleRetrieveRecords = function() {
758     buildRecordGrid(currentType);
759     vlFetchQueueSummary(currentQueueId, currentType, 
760         function(summary) {
761             dojo.byId('vl-queue-summary-name').innerHTML = summary.queue.name();
762             dojo.byId('vl-queue-summary-total-count').innerHTML = summary.total +'';
763             dojo.byId('vl-queue-summary-import-count').innerHTML = summary.imported + '';
764         }
765     );
766 }
767
768 function vlFetchQueueSummary(qId, type, onload) {
769     fieldmapper.standardRequest(
770         ['open-ils.vandelay', 'open-ils.vandelay.'+type+'_queue.summary.retrieve'],
771         {   async: true,
772             params: [authtoken, qId],
773             oncomplete : function(r) {
774                 var summary = r.recv().content();
775                 if(e = openils.Event.parse(summary))
776                     return alert(e);
777                 return onload(summary);
778             }
779         }
780     );
781 }
782     
783
784 function vlImportSelectedRecords() {
785     displayGlobalDiv('vl-generic-progress-with-total');
786     var records = [];
787
788     for(var id in selectableGridRecords) {
789         if(dojo.byId(id).checked) {
790             var recId = selectableGridRecords[id];
791             var rec = queuedRecordsMap[recId];
792             if(!rec.import_time()) 
793                 records.push(recId);
794         }
795     }
796
797     fieldmapper.standardRequest(
798         ['open-ils.vandelay', 'open-ils.vandelay.'+currentType+'_record.list.import'],
799         {   async: true,
800             params: [authtoken, records, {overlay_map:currentOverlayRecordsMap}],
801             onresponse: function(r) {
802                 var resp = r.recv().content();
803                 if(e = openils.Event.parse(resp))
804                     return alert(e);
805                 if(resp.complete) {
806                     return retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
807                 } else {
808                     vlControlledProgressBar.update({maximum:resp.total, progress:resp.progress});
809                 }
810             }, 
811         }
812     );
813 }
814
815 function vlImportAllRecords() {
816     vlImportRecordQueue(currentType, currentQueueId, false,
817         function(){displayGlobalDiv('vl-queue-div');});
818 }
819
820 function vlImportRecordQueue(type, queueId, noMatchOnly, onload) {
821     displayGlobalDiv('vl-generic-progress-with-total');
822     var method = 'open-ils.vandelay.bib_queue.import';
823     if(noMatchOnly)
824         method = method.replace('import', 'nomatch.import');
825     if(type == 'auth')
826         method = method.replace('bib', 'auth');
827
828     fieldmapper.standardRequest(
829         ['open-ils.vandelay', method],
830         {   async: true,
831             params: [authtoken, queueId],
832             onresponse: function(r) {
833                 var resp = r.recv().content();
834                 if(e = openils.Event.parse(resp))
835                     return alert(e);
836                 vlControlledProgressBar.update({maximum:resp.total, progress:resp.progress});
837             },
838             oncomplete: function() {onload();}
839         }
840     );
841 }
842
843
844 function vlImportHoldings(queueId, importProfile, onload) {
845     displayGlobalDiv('vl-generic-progress-with-total');
846     fieldmapper.standardRequest(
847         ['open-ils.vandelay', 'open-ils.vandelay.bib_record.queue.asset.import'],
848         {   async: true,
849             params: [authtoken, importProfile, queueId],
850             onresponse: function(r) {
851                 var resp = openils.Util.readResponse(r);
852                 vlControlledProgressBar.update({maximum:resp.total, progress:resp.progress});
853             },
854             oncomplete: function() {onload();}
855         }
856     );
857 }
858
859 /**
860   * Create queue, upload MARC, process spool, load the newly created queue 
861   */
862 function batchUpload() {
863     var queueName = dijit.byId('vl-queue-name').getValue();
864     currentType = dijit.byId('vl-record-type').getValue();
865
866     var handleProcessSpool = function() {
867         if(vlUploadQueueAutoImport.checked) {
868             vlImportRecordQueue(currentType, currentQueueId, true,  
869                 function() {
870                     if(vlUploadQueueHoldingsImport.checked) {
871                         vlImportHoldings(
872                             currentQueueId, 
873                             vlUploadQueueHoldingsImportProfile.attr('value'), 
874                             function() { 
875                                 retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
876                             }
877                         );
878                     } else {
879                         retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
880                     }
881                 }
882             );
883         } else {
884             retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
885         }
886     }
887
888     var handleUploadMARC = function(key) {
889         dojo.style(dojo.byId('vl-upload-status-processing'), 'display', 'block');
890         processSpool(key, currentQueueId, currentType, handleProcessSpool);
891     };
892
893     var handleCreateQueue = function(queue) {
894         currentQueueId = queue.id();
895         uploadMARC(handleUploadMARC);
896     };
897     
898     if(vlUploadQueueSelector.getValue() && !queueName) {
899         currentQueueId = vlUploadQueueSelector.getValue();
900         uploadMARC(handleUploadMARC);
901     } else {
902         createQueue(queueName, currentType, handleCreateQueue);
903     }
904 }
905
906
907 function vlFleshQueueSelect(selector, type) {
908     var data = (type == 'bib') ? vbq.toStoreData(allUserBibQueues) : vaq.toStoreData(allUserAuthQueues);
909     selector.store = new dojo.data.ItemFileReadStore({data:data});
910     selector.setValue(null);
911     selector.setDisplayedValue('');
912     if(data[0])
913         selector.setValue(data[0].id());
914 }
915
916 function vlShowUploadForm() {
917     displayGlobalDiv('vl-marc-upload-div');
918     vlFleshQueueSelect(vlUploadQueueSelector, vlUploadRecordType.getValue());
919     vlUploadSourceSelector.store = 
920         new dojo.data.ItemFileReadStore({data:cbs.toStoreData(vlBibSources, 'source')});
921     vlUploadSourceSelector.setValue(vlBibSources[0].id());
922     vlUploadQueueHoldingsImportProfile.store = 
923         new dojo.data.ItemFileReadStore({data:viiad.toStoreData(importItemDefs)});
924     vlUploadQueueHoldingsImportProfile.attr('disabled', true);
925     dojo.connect(vlUploadQueueHoldingsImport, 'onChange',
926         function(val) {
927             if(val)
928                 vlUploadQueueHoldingsImportProfile.attr('disabled', false);
929             else
930                 vlUploadQueueHoldingsImportProfile.attr('disabled', true);
931         }
932     );
933 }
934
935 function vlShowQueueSelect() {
936     displayGlobalDiv('vl-queue-select-div');
937     vlFleshQueueSelect(vlQueueSelectQueueList, vlQueueSelectType.getValue());
938 }
939
940 function vlFetchQueueFromForm() {
941     currentType = vlQueueSelectType.getValue();
942     currentQueueId = vlQueueSelectQueueList.getValue();
943     retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
944 }
945
946 function vlOpenMarcEditWindow(rec, postReloadHTMLHandler) {
947     /*
948         To run in Firefox directly, must set signed.applets.codebase_principal_support
949         to true in about:config
950     */
951     netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
952     win = window.open('/xul/server/cat/marcedit.xul'); // XXX version?
953
954     function onsave(r) {
955         // after the record is saved, reload the HTML display
956         var stat = r.recv().content();
957         if(e = openils.Event.parse(stat))
958             return alert(e);
959         alert(dojo.byId('vl-marc-edit-complete-label').innerHTML);
960         win.close();
961         vlLoadMARCHtml(rec.id(), false, postReloadHTMLHandler);
962     }
963
964     win.xulG = {
965         record : {marc : rec.marc()},
966         save : {
967             label: dojo.byId('vl-marc-edit-save-label').innerHTML,
968             func: function(xmlString) {
969                 var method = 'open-ils.permacrud.update.' + rec.classname;
970                 rec.marc(xmlString);
971                 fieldmapper.standardRequest(
972                     ['open-ils.permacrud', method],
973                     {   async: true,
974                         params: [authtoken, rec],
975                         oncomplete: onsave
976                     }
977                 );
978             },
979         }
980     };
981 }
982
983 function vlLoadMarcEditor(type, recId, postReloadHTMLHandler) {
984     var method = 'open-ils.permacrud.search.vqbr';
985     if(currentType != 'bib')
986         method = method.replace(/vqbr/,'vqar');
987
988     fieldmapper.standardRequest(
989         ['open-ils.permacrud', method],
990         {   async: true, 
991             params: [authtoken, {id : recId}],
992             oncomplete: function(r) {
993                 var rec = r.recv().content();
994                 if(e = openils.Event.parse(rec))
995                     return alert(e);
996                 vlOpenMarcEditWindow(rec, postReloadHTMLHandler);
997             }
998         }
999     );
1000 }
1001
1002
1003
1004 //------------------------------------------------------------
1005 // attribute editors
1006
1007 // attribute-editor global variables
1008
1009 var ATTR_EDITOR_IN_UPDATE_MODE = false; // true on 'edit', false on 'create'
1010 var ATTR_EDIT_ID = null;                // id of current 'edit' attribute
1011 var ATTR_EDIT_GROUP = 'bib';            // bib-attrs or auth-attrs
1012
1013 function vlAttrEditorInit() {
1014     // set up tooltips on the edit form
1015     connectTooltip('attr-editor-tags'); 
1016     connectTooltip('attr-editor-subfields'); 
1017 }
1018
1019 function vlShowAttrEditor() {
1020     displayGlobalDiv('vl-attr-editor-div');
1021     loadAttrEditorGrid();
1022     idHide('vl-generic-progress');
1023 }
1024
1025 function setAttrEditorGroup(groupName) {
1026     // put us into 'bib'-attr or 'auth'-attr mode.
1027     if (ATTR_EDIT_GROUP != groupName) {
1028         ATTR_EDIT_GROUP = groupName;
1029         loadAttrEditorGrid();
1030     }
1031 }
1032
1033 function onAttrEditorOpen() {
1034     // the "bars" have the create/update/cancel/etc. buttons.
1035     var create_bar = document.getElementById('attr-editor-create-bar');
1036     var update_bar = document.getElementById('attr-editor-update-bar');
1037     if (ATTR_EDITOR_IN_UPDATE_MODE) {
1038         update_bar.style.display='table-row';
1039         create_bar.style.display='none';
1040         // hide the dropdown-button
1041         idStyle('vl-create-attr-editor-button', 'visibility', 'hidden');
1042     } else {
1043         dijit.byId('attr-editor-dialog').reset();
1044         create_bar.style.display='table-row';
1045         update_bar.style.display='none';
1046     }
1047 }
1048
1049 function onAttrEditorClose() {
1050     // reset the form to a "create" form. (We may have borrowed it for editing.)
1051     ATTR_EDITOR_IN_UPDATE_MODE = false;
1052     // show the dropdown-button
1053     idStyle('vl-create-attr-editor-button', 'visibility', 'visible');
1054 }
1055
1056 function loadAttrEditorGrid() {
1057     var _data = (ATTR_EDIT_GROUP == 'auth') ? 
1058         vqarad.toStoreData(authAttrDefs) : vqbrad.toStoreData(bibAttrDefs) ;
1059
1060     var store = new dojo.data.ItemFileReadStore({data:_data});
1061     attrEditorGrid.setStore(store);
1062     dojo.connect(attrEditorGrid, 'onRowDblClick', onAttrEditorClick);
1063     attrEditorGrid.update();
1064 }
1065
1066 function attrGridGetTag(n, item) {
1067     // grid helper: return the tags from the row's xpath column.
1068     return item && xpathParser.parse(this.grid.store.getValue(item, 'xpath')).tags;
1069 }
1070
1071 function attrGridGetSubfield(n, item) {
1072     // grid helper: return the subfields from the row's xpath column.
1073     return item && xpathParser.parse(this.grid.store.getValue(item, 'xpath')).subfields;
1074 }
1075
1076 function onAttrEditorClick() {
1077     var row = this.getItem(this.focus.rowIndex);
1078     ATTR_EDIT_ID = this.store.getValue(row, 'id');
1079     ATTR_EDITOR_IN_UPDATE_MODE = true;
1080
1081     // populate the popup editor.
1082     dijit.byId('attr-editor-code').attr('value', this.store.getValue(row, 'code'));
1083     dijit.byId('attr-editor-description').attr('value', this.store.getValue(row, 'description'));
1084     var parsed_xpath = xpathParser.parse(this.store.getValue(row, 'xpath'));
1085     dijit.byId('attr-editor-tags').attr('value', parsed_xpath.tags);
1086     dijit.byId('attr-editor-subfields').attr('value', parsed_xpath.subfields);
1087     dijit.byId('attr-editor-identifier').attr('value', this.store.getValue(row, 'ident'));
1088     dijit.byId('attr-editor-xpath').attr('value', this.store.getValue(row, 'xpath'));
1089     dijit.byId('attr-editor-remove').attr('value', this.store.getValue(row, 'remove'));
1090
1091     // set up UI for editing
1092     dojo.byId('vl-create-attr-editor-button').click();
1093 }
1094
1095 function vlSaveAttrDefinition(data) {
1096     idHide('vl-attr-editor-div');
1097     idShow('vl-generic-progress');
1098
1099     data.id = ATTR_EDIT_ID;
1100
1101     // this ought to honour custom xpaths, but overwrite xpaths
1102     // derived from tags/subfields.
1103     if (data.xpath == '' || looksLikeDerivedXpath(data.xpath)) {
1104         var _xpath = tagAndSubFieldsToXpath(data.tag, data.subfield);
1105         data.xpath = _xpath;
1106     }
1107
1108     // build up our permacrud params. Key variables here are
1109     // "create or update" and "bib or auth".
1110
1111     var isAuth   = (ATTR_EDIT_GROUP == 'auth');
1112     var isCreate = (ATTR_EDIT_ID == null);
1113     var rad      = isAuth ? new vqarad() : new vqbrad() ;
1114     var method   = 'open-ils.permacrud' + (isCreate ? '.create.' : '.update.') 
1115         + (isAuth ? 'vqarad' : 'vqbrad');
1116     var _data    = rad.fromStoreItem(data);
1117
1118     _data.ischanged(1);
1119
1120     fieldmapper.standardRequest(
1121         ['open-ils.permacrud', method],
1122         {   async: true,
1123             params: [authtoken, _data ],
1124             onresponse: function(r) { },
1125             oncomplete: function(r) {
1126                 attrEditorFetchAttrDefs(vlShowAttrEditor);
1127                 ATTR_EDIT_ID = null;
1128             },
1129             onerror: function(r) {
1130                 alert('vlSaveAttrDefinition comms error: ' + r);
1131             }
1132         }
1133     );
1134 }
1135
1136 function attrEditorFetchAttrDefs(callback) {
1137     var fn = (ATTR_EDIT_GROUP == 'auth') ? vlFetchAuthAttrDefs : vlFetchBibAttrDefs;
1138     return fn(callback);
1139 }
1140
1141 function vlAttrDelete() {
1142     idHide('vl-attr-editor-div');
1143     idShow('vl-generic-progress');
1144
1145     var isAuth = (ATTR_EDIT_GROUP == 'auth');
1146     var method = 'open-ils.permacrud.delete.' + (isAuth ? 'vqarad' : 'vqbrad');
1147     var rad    = isAuth ? new vqarad() : new vqbrad() ;
1148     fieldmapper.standardRequest(
1149         ['open-ils.permacrud', method],
1150         {   async: true,
1151             params: [authtoken, rad.fromHash({ id : ATTR_EDIT_ID }), ],
1152             oncomplete: function() {
1153                 dijit.byId('attr-editor-dialog').onCancel(); // close the dialog
1154                 attrEditorFetchAttrDefs(vlShowAttrEditor);
1155                 ATTR_EDIT_ID = null;
1156             },
1157             onerror: function(r) {
1158                 alert('vlAttrDelete comms error: ' + r);
1159             }
1160         }
1161     );
1162 }
1163
1164 // ------------------------------------------------------------
1165 // utilities for attribute editors
1166
1167 // dom utilities (maybe dojo does these, and these should be replaced)
1168
1169 function idStyle(obId, k, v)    { document.getElementById(obId).style[k] = v;   }
1170 function idShow(obId)           { idStyle(obId, 'display', 'block');            }
1171 function idHide(obId)           { idStyle(obId, 'display' , 'none');            }
1172
1173 function connectTooltip(fieldId) {
1174     // Given an element id, look up a tooltip element in the doc (same
1175     // id with a '-tip' suffix) and associate the two. Maybe dojo has
1176     // a better way to do this?
1177     var fld = dojo.byId(fieldId);
1178     var tip = dojo.byId(fieldId + '-tip');
1179     dojo.connect(fld, 'onfocus', function(evt) {
1180                      dijit.showTooltip(tip.innerHTML, fld, ['below', 'after']); });
1181     dojo.connect(fld, 'onblur', function(evt) { dijit.hideTooltip(fld); });
1182 }
1183
1184 // xpath utilities
1185
1186 var xpathParser = new openils.MarcXPathParser();
1187
1188 function tagAndSubFieldsToXpath(tags, subfields) {
1189     // given tags, and subfields, build up an XPath.
1190     try {
1191         var parts = {
1192             'tags':tags.match(/[\d]+/g), 
1193             'subfields':subfields.match(/[a-zA-z]/g) };
1194         return xpathParser.compile(parts);
1195     } catch (err) {
1196         return {'parts':null, 'tags':null, 'error':err};
1197     }
1198 }
1199
1200 function looksLikeDerivedXpath(path) {
1201     // Does this path look like it was derived from tags and subfields?
1202     var parsed = xpathParser.parse(path);
1203     if (parsed.tags == null) 
1204         return false;
1205     var compiled = xpathParser.compile(parsed);
1206     return (path == compiled);
1207 }
1208
1209 // amazing xpath-util unit-tests
1210 if (!looksLikeDerivedXpath('//*[@tag="901"]/*[@code="c"]'))     alert('vandelay xpath-utility error');
1211 if ( looksLikeDerivedXpath('ba-boo-ba-boo!'))                   alert('vandelay xpath-utility error');