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