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