]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/xul/staff_client/server/cat/marcedit.js
Move the AuthorityControlSet module into openils -- it loads data from the server
[working/Evergreen.git] / Open-ILS / xul / staff_client / server / cat / marcedit.js
1 /* vim: et:sw=4:ts=4:
2  *
3  * Copyright (C) 2004-2008  Georgia Public Library Service
4  * Copyright (C) 2008-2010  Equinox Software, Inc.
5  * Mike Rylander <miker@esilibrary.com> 
6  *
7  * Copyright (C) 2010 Dan Scott <dan@coffeecode.net>
8  * Copyright (C) 2010 Internationaal Instituut voor Sociale Geschiedenis <info@iisg.nl>
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.  
19  *
20  */
21 var xmlDeclaration = /^<\?xml version[^>]+?>/;
22
23 var serializer = new XMLSerializer();
24 var marcns = new Namespace("http://www.loc.gov/MARC21/slim");
25 var gw = new Namespace("http://opensrf.org/-/namespaces/gateway/v1");
26 var xulns = new Namespace("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
27 default xml namespace = marcns;
28
29 var tooltip_hash = {};
30 var current_focus;
31 var _record;
32 var _record_type;
33 var bib_data;
34
35 var xml_record;
36
37 var context_menus;
38 var tag_menu;
39 var p;
40 var auth_pages = {};
41 var show_auth_menu = false;
42
43 function $(id) { return document.getElementById(id); }
44
45 var acs = new openils.AuthorityControlSet ();
46
47 function mangle_005() {
48     var now = new Date();
49     var y = now.getUTCFullYear();
50
51     var m = now.getUTCMonth() + 1;
52     if (m < 10) m = '0' + m;
53     
54     var d = now.getUTCDate();
55     if (d < 10) d = '0' + d;
56     
57     var H = now.getUTCHours();
58     if (H < 10) H = '0' + H;
59     
60     var M = now.getUTCMinutes();
61     if (M < 10) M = '0' + M;
62     
63     var S = now.getUTCSeconds();
64     if (S < 10) S = '0' + S;
65     
66
67     var stamp = '' + y + m + d + H + M + S + '.0';
68     createControlField('005',stamp);
69
70 }
71
72 function createControlField (tag,data) {
73     // first, remove the old field, if any;
74     for (var i in xml_record.controlfield.(@tag == tag)) delete xml_record.controlfield.(@tag == tag)[i];
75
76     var cf = <controlfield tag="" xmlns="http://www.loc.gov/MARC21/slim">{ data }</controlfield>;
77     cf.@tag = tag;
78
79     // then, find the right position and insert it
80     var done = 0;
81     var cfields = xml_record.controlfield;
82     var base = Number(tag.substring(2));
83     for (var i in cfields) {
84         var t = Number(cfields[i].@tag.toString().substring(2));
85         if (t > base) {
86             xml_record.insertChildBefore( cfields[i], cf );
87             done = 1
88             break;
89         }
90     }
91
92     if (!done) xml_record.insertChildBefore( xml_record.datafield[0], cf );
93
94     return cf;
95 }
96
97 function xml_escape_unicode ( str ) {
98     return str.replace(
99         /([\u0080-\ufffe])/g,
100         function (r,s) { return "&#x" + s.charCodeAt(0).toString(16) + ";"; }
101     );
102 }
103
104 function wrap_long_fields (node) {
105     var text_size = dojo.attr(node, 'size');
106     var hard_width = 100; 
107     if (text_size > hard_width) {
108         dojo.attr(node, 'multiline', 'true');
109         dojo.attr(node, 'cols', hard_width);
110         var text_rows = (text_size / hard_width) + 1;
111         dojo.attr(node, 'rows', text_rows);
112     }
113 }
114
115 function set_flat_editor (useFlatText) {
116
117     var xe = $('xul-editor');
118     var te = $('text-editor');
119
120     if (useFlatText) {
121         if (xe.hidden) { return; }
122         te.hidden = false;
123         xe.hidden = true;
124     } else {
125         if (te.hidden) { return; }
126         te.hidden = true;
127         xe.hidden = false;
128     }
129
130     if (te.hidden) {
131         // get the marcxml from the text box
132         var xml_string = new MARC.Record({
133             marcbreaker : $('text-editor-box').value,
134             delimiter : '$'
135         }).toXmlString();
136
137         // reset the xml record and rerender it
138         xml_record = new XML( xml_string );
139         if (xml_record..record[0]) xml_record = xml_record..record[0];
140         loadRecord();
141     } else {
142         var xml_string = xml_record.toXMLString();
143
144         // push the xml record into the textbox
145         var rec = new MARC.Record ({ delimiter : '$', marcxml : xml_string });
146         $('text-editor-box').value = rec.toBreaker();
147     }
148 }
149
150 function my_init() {
151     try {
152
153         netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
154         if (typeof JSAN == 'undefined') { throw( $("commonStrings").getString('common.jsan.missing') ); }
155         JSAN.errorLevel = "die"; // none, warn, or die
156         JSAN.addRepository('/xul/server/');
157
158         // Fake xulG for standalone...
159         try {
160             window.xulG.record;
161         } catch (e) {
162             window.xulG = {};
163             window.xulG.record = {};
164             window.xulG.save = {};
165             window.xulG.marc_control_number_identifier = 'CONS';
166
167             window.xulG.save.label = $('catStrings').getString('staff.cat.marcedit.save.label');
168             window.xulG.save.func = function (r) { alert(r); }
169
170             var cgi = new CGI();
171             var _rid = cgi.param('record');
172             if (_rid) {
173                 window.xulG.record.id = _rid;
174                 window.xulG.record.url = '/opac/extras/supercat/retrieve/marcxml/record/' + _rid;
175             }
176         }
177
178         // End faking part...
179
180         /* Check for an explicitly passed record type
181          * This is not the same as the fixed-field record type; we can't trust
182          * the fixed fields when making modifications to the attributes for a
183          * given record (in particular, config.bib_source only applies for bib
184          * records, but an auth or MFHD record with the same ID and bad fixed
185          * fields could trample the config.bib_source value for the
186          * corresponding bib record if we're not careful.
187          *
188          * are = authority record
189          * sre = serial record (MFHD)
190          * bre = bibliographic record
191          */
192         if (!window.xulG.record.rtype) {
193             var cgi = new CGI();
194             window.xulG.record.rtype = cgi.param('rtype') || false;
195         }
196
197         document.getElementById('save-button').setAttribute('label', window.xulG.save.label);
198         document.getElementById('save-button').setAttribute('oncommand',
199             'if ($("xul-editor").hidden) set_flat_editor(false); ' +
200             'mangle_005(); ' + 
201             'var xml_string = xml_escape_unicode( xml_record.toXMLString() ); ' + 
202             'save_attempt( xml_string ); ' +
203             'loadRecord();'
204         );
205
206         if (window.xulG.record.url) {
207             var req =  new XMLHttpRequest();
208             req.open('POST',window.xulG.record.url,false);
209             req.send(null);
210             window.xulG.record.marc = req.responseText.replace(xmlDeclaration, '');
211         }
212
213         xml_record = new XML( window.xulG.record.marc );
214         if (xml_record..record[0]) xml_record = xml_record..record[0];
215
216         // Get the tooltip xml all async like
217         req =  new XMLHttpRequest();
218
219         // Set a default locale in case preferences fail us
220         var locale = "en-US";
221
222         // Try to get the locale from our preferences
223         netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
224         try {
225             const Cc = Components.classes;
226             const Ci = Components.interfaces;
227             locale = Cc["@mozilla.org/preferences-service;1"].
228                 getService(Ci.nsIPrefBranch).
229                 getCharPref("general.useragent.locale");
230         }
231         catch (e) { }
232
233         // TODO: We should send a HEAD request to check for the existence of the desired file
234         // then fall back to the default locale if preferred locale is not necessary;
235         // however, for now we have a simplistic check:
236         //
237         // we currently have translations for only two locales; in the absence of a
238         // valid locale, default to the almighty en-US
239         if (locale != 'en-US' && locale != 'fr-CA') {
240             locale = 'en-US';
241         }
242
243         // grab the right tooltip based on MARC type
244         var tooltip_doc = 'marcedit-tooltips.xml';
245         switch (window.xulG.record.rtype) {
246             case 'bre':
247                 tooltip_doc = 'marcedit-tooltips.xml';
248                 break; 
249             case 'are':
250                 tooltip_doc = 'marcedit-tooltips-authority.xml';
251                 locale = 'en-US'; // FIXME - note TODO above; at moment only en-US has this
252                 break; 
253             case 'sre':
254                 tooltip_doc = 'marcedit-tooltips-mfhd.xml';
255                 locale = 'en-US'; // FIXME - note TODO above; at moment only en-US has this
256                 break; 
257             default: 
258                 tooltip_doc = 'marcedit-tooltips.xml';
259         }
260
261         // Get the locale-specific tooltips
262         req.open('GET','/xul/server/locale/' + locale + '/' + tooltip_doc,true);
263
264         context_menus = createComplexXULElement('popupset');
265         document.documentElement.appendChild( context_menus );
266
267         tag_menu = createPopup({position : 'after_start', id : 'tags_popup'});
268         context_menus.appendChild( tag_menu );
269
270         tag_menu.appendChild(
271             createMenuitem(
272                 { label : $('catStrings').getString('staff.cat.marcedit.add_row.label'),
273                   oncommand : 
274                     'var e = document.createEvent("KeyEvents");' +
275                     'e.initKeyEvent("keypress",1,1,null,1,0,0,0,13,0);' +
276                     'current_focus.inputField.dispatchEvent(e);'
277                  }
278             )
279         );
280
281         tag_menu.appendChild(
282             createMenuitem(
283                 { label : $('catStrings').getString('staff.cat.marcedit.insert_row.label'),
284                   oncommand : 
285                     'var e = document.createEvent("KeyEvents");' +
286                     'e.initKeyEvent("keypress",1,1,null,1,0,1,0,13,0);' +
287                     'current_focus.inputField.dispatchEvent(e);'
288                  }
289             )
290         );
291
292         tag_menu.appendChild(
293             createMenuitem(
294                 { label : $('catStrings').getString('staff.cat.marcedit.remove_row.label'),
295                   oncommand : 
296                     'var e = document.createEvent("KeyEvents");' +
297                     'e.initKeyEvent("keypress",1,1,null,1,0,0,0,46,0);' +
298                     'current_focus.inputField.dispatchEvent(e);'
299                 }
300             )
301         );
302
303         tag_menu.appendChild( createComplexXULElement( 'separator' ) );
304
305         tag_menu.appendChild(
306             createMenuitem(
307                 { label : $('catStrings').getString('staff.cat.marcedit.replace_006.label'),
308                   oncommand : 
309                     'var e = document.createEvent("KeyEvents");' +
310                     'e.initKeyEvent("keypress",1,1,null,1,0,0,0,64,0);' +
311                     'current_focus.inputField.dispatchEvent(e);'
312                  }
313             )
314         );
315
316         tag_menu.appendChild(
317             createMenuitem(
318                 { label : $('catStrings').getString('staff.cat.marcedit.replace_007.label'),
319                   oncommand : 
320                     'var e = document.createEvent("KeyEvents");' +
321                     'e.initKeyEvent("keypress",1,1,null,1,0,0,0,65,0);' +
322                     'current_focus.inputField.dispatchEvent(e);'
323                 }
324             )
325         );
326
327         tag_menu.appendChild(
328             createMenuitem(
329                 { label : $('catStrings').getString('staff.cat.marcedit.replace_008.label'),
330                   oncommand : 
331                     'var e = document.createEvent("KeyEvents");' +
332                     'e.initKeyEvent("keypress",1,1,null,1,0,0,0,66,0);' +
333                     'current_focus.inputField.dispatchEvent(e);'
334                 }
335             )
336         );
337
338         tag_menu.appendChild( createComplexXULElement( 'separator' ) );
339
340         p = createComplexXULElement('popupset');
341         document.documentElement.appendChild( p );
342
343         req.onreadystatechange = function () {
344             if (req.readyState == 4) {
345                 bib_data = new XML( req.responseText.replace(xmlDeclaration, '') );
346                 genToolTips();
347             }
348         }
349         req.send(null);
350
351         loadRecord();
352
353         if (! xulG.fast_add_item) {
354             document.getElementById('fastItemAdd_checkbox').hidden = true;
355         }
356         document.getElementById('fastItemAdd_textboxes').hidden = document.getElementById('fastItemAdd_checkbox').hidden || !document.getElementById('fastItemAdd_checkbox').checked;
357
358         // Only show bib sources for bib records that already exist in the database
359         if (xulG.record.rtype == 'bre' && xulG.record.id) {
360             dojo.require('openils.PermaCrud');
361             var authtoken = ses();
362             // Retrieve the current record attributes
363             var bib = new openils.PermaCrud({"authtoken": authtoken}).retrieve('bre', xulG.record.id);
364
365             // Remember the current bib source of the record
366             xulG.record.bre = bib;
367
368             buildBibSourceList(authtoken, xulG.record.id);
369         }
370
371         dojo.require('MARC.FixedFields');
372
373     } catch(E) {
374         alert('FIXME, MARC Editor, my_init: ' + E);
375     }
376 }
377
378
379 function createComplexHTMLElement (e, attrs, objects, text) {
380     var l = document.createElementNS('http://www.w3.org/1999/xhtml',e);
381
382     if (attrs) {
383         for (var i in attrs) l.setAttribute(i,attrs[i]);
384     }
385
386     if (objects) {
387         for ( var i in objects ) l.appendChild( objects[i] );
388     }
389
390     if (text) {
391         l.appendChild( document.createTextNode(text) )
392     }
393
394     return l;
395 }
396
397 function createComplexXULElement (e, attrs, objects) {
398     var l = document.createElementNS('http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul',e);
399
400     if (attrs) {
401         for (var i in attrs) {
402             if (typeof attrs[i] == 'function') {
403                 l.addEventListener( i, attrs[i], true );
404             } else {
405                 l.setAttribute(i,attrs[i]);
406             }
407         }
408     } 
409
410     if (objects) {
411         for ( var i in objects ) l.appendChild( objects[i] );
412     }
413
414     return l;
415 }
416
417 function createDescription (attrs) {
418     return createComplexXULElement('description', attrs, Array.prototype.slice.apply(arguments, [1]) );
419 }
420
421 function createTooltip (attrs) {
422     return createComplexXULElement('tooltip', attrs, Array.prototype.slice.apply(arguments, [1]) );
423 }
424
425 function createLabel (attrs) {
426     return createComplexXULElement('label', attrs, Array.prototype.slice.apply(arguments, [1]) );
427 }
428
429 function createVbox (attrs) {
430     return createComplexXULElement('vbox', attrs, Array.prototype.slice.apply(arguments, [1]) );
431 }
432
433 function createHbox (attrs) {
434     return createComplexXULElement('hbox', attrs, Array.prototype.slice.apply(arguments, [1]) );
435 }
436
437 function createRow (attrs) {
438     return createComplexXULElement('row', attrs, Array.prototype.slice.apply(arguments, [1]) );
439 }
440
441 function createTextbox (attrs) {
442     return createComplexXULElement('textbox', attrs, Array.prototype.slice.apply(arguments, [1]) );
443 }
444
445 function createMenu (attrs) {
446     return createComplexXULElement('menu', attrs, Array.prototype.slice.apply(arguments, [1]) );
447 }
448
449 function createMenuPopup (attrs) {
450     return createComplexXULElement('menupopup', attrs, Array.prototype.slice.apply(arguments, [1]) );
451 }
452
453 function createPopup (attrs) {
454     return createComplexXULElement('popup', attrs, Array.prototype.slice.apply(arguments, [1]) );
455 }
456
457 function createMenuitem (attrs) {
458     return createComplexXULElement('menuitem', attrs, Array.prototype.slice.apply(arguments, [1]) );
459 }
460
461 function createCheckbox (attrs) {
462     return createComplexXULElement('checkbox', attrs, Array.prototype.slice.apply(arguments, [1]) );
463 }
464
465 // Find the next textbox that we can use for a focus point
466 // For control fields, use the first editable text box
467 // For data fields, focus on the first subfield text box
468 function setFocusToNextTag (row, direction) {
469     var keep_looking = true;
470     while (keep_looking && (direction == 'up' ? row = row.previousSibling : row = row.nextSibling)) {
471         // Is it a datafield?
472         dojo.query('hbox hbox textbox', row).forEach(function(node, index, arr) {
473             node.focus();
474             keep_looking = false;
475         });
476
477         // No, it's a control field; use the first textbox
478         if (keep_looking) {
479             dojo.query('textbox', row).forEach(function(node, index, arr) {
480                 node.focus();
481                 keep_looking = false;
482             });
483         }
484     }
485
486     return true;
487 }
488
489
490 function createMARCTextbox (element,attrs) {
491
492     var box = createComplexXULElement('textbox', attrs, Array.prototype.slice.apply(arguments, [2]) );
493     box.addEventListener('keypress',function(ev) { netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); if (!(ev.altKey || ev.ctrlKey || ev.metaKey)) { oils_lock_page(); } },false);
494     box.onkeypress = function (event) {
495         var root_node;
496         var node = element;
497         while(node = node.parent()) {
498             root_node = node;
499         }
500
501         var row = event.target;
502         while (row.tagName != 'row') row = row.parentNode;
503
504         if (element.nodeKind() == 'attribute') element[0]=box.value;
505         else element.setChildren( box.value );
506
507         if (element.localName() != 'controlfield') {
508             if ((event.charCode == 100 || event.charCode == 105) && event.ctrlKey) { // ctrl+d or ctrl+i
509
510                 var index_sf, target, move_data;
511                 if (element.localName() == 'subfield') {
512                     index_sf = element;
513                     target = event.target.parentNode;
514
515                     var start = event.target.selectionStart;
516                     var end = event.target.selectionEnd - event.target.selectionStart ?
517                             event.target.selectionEnd :
518                             event.target.value.length;
519
520                     move_data = event.target.value.substring(start,end);
521                     event.target.value = event.target.value.substring(0,start) + event.target.value.substring(end);
522                     event.target.setAttribute('size', event.target.value.length + 2);
523     
524                     element.setChildren( event.target.value );
525
526                 } else if (element.localName() == 'code') {
527                     index_sf = element.parent();
528                     target = event.target.parentNode;
529                 } else if (element.localName() == 'tag' || element.localName() == 'ind1' || element.localName() == 'ind2') {
530                     index_sf = element.parent().children()[element.parent().children().length() - 1];
531                     target = event.target.parentNode.lastChild.lastChild;
532                 }
533
534                 var sf = <subfield code="" xmlns="http://www.loc.gov/MARC21/slim">{ move_data }</subfield>;
535
536                 index_sf.parent().insertChildAfter( index_sf, sf );
537
538                 var new_sf = marcSubfield(sf);
539
540                 if (target === target.parentNode.lastChild) {
541                     target.parentNode.appendChild( new_sf );
542                 } else {
543                     target.parentNode.insertBefore( new_sf, target.nextSibling );
544                 }
545
546                 new_sf.firstChild.nextSibling.focus();
547
548                 event.preventDefault();
549                 return false;
550
551             } else if (event.keyCode == 13 || event.keyCode == 77) {
552                 if (event.ctrlKey) { // ctrl+enter
553
554                     var index;
555                     if (element.localName() == 'subfield') index = element.parent();
556                     if (element.localName() == 'code') index = element.parent().parent();
557                     if (element.localName() == 'tag') index = element.parent();
558                     if (element.localName() == 'ind1') index = element.parent();
559                     if (element.localName() == 'ind2') index = element.parent();
560
561                     var df = <datafield tag="" ind1="" ind2="" xmlns="http://www.loc.gov/MARC21/slim"><subfield code="" /></datafield>;
562
563                     if (event.shiftKey) { // ctrl+shift+enter
564                         index.parent().insertChildBefore( index, df );
565                     } else {
566                         index.parent().insertChildAfter( index, df );
567                     }
568
569                     var new_df = marcDatafield(df);
570
571                     if (row.parentNode.lastChild === row) {
572                         row.parentNode.appendChild( new_df );
573                     } else {
574                         if (event.shiftKey) { // ctrl+shift+enter
575                             row.parentNode.insertBefore( new_df, row );
576                         } else {
577                             row.parentNode.insertBefore( new_df, row.nextSibling );
578                         }
579                     }
580
581                     new_df.firstChild.focus();
582
583                     event.preventDefault();
584                     return false;
585
586                 } else if (event.shiftKey) {
587                     if (row.previousSibling.className.match('marcDatafieldRow'))
588                         row.previousSibling.firstChild.focus();
589                 } else {
590                     row.nextSibling.firstChild.focus();
591                 }
592
593             } else if (event.keyCode == 38 || event.keyCode == 40) { // up-arrow or down-arrow
594                 if (event.ctrlKey) { // CTRL key: copy the field
595                     var index;
596                     if (element.localName() == 'subfield') index = element.parent();
597                     if (element.localName() == 'code') index = element.parent().parent();
598                     if (element.localName() == 'tag') index = element.parent();
599                     if (element.localName() == 'ind1') index = element.parent();
600                     if (element.localName() == 'ind2') index = element.parent();
601
602                     var copyField = index.copy();
603
604                     if (event.keyCode == 38) { // ctrl+up-arrow
605                         index.parent().insertChildBefore( index, copyField );
606                     } else {
607                         index.parent().insertChildAfter( index, copyField );
608                     }
609
610                     var new_df = marcDatafield(copyField);
611
612                     if (row.parentNode.lastChild === row) {
613                         row.parentNode.appendChild( new_df );
614                     } else {
615                         if (event.keyCode == 38) { // ctrl+up-arrow
616                             row.parentNode.insertBefore( new_df, row );
617                         } else { // ctrl+down-arrow
618                             row.parentNode.insertBefore( new_df, row.nextSibling );
619                         }
620                     }
621
622                     new_df.firstChild.focus();
623
624                     event.preventDefault();
625
626                     return false;
627                 } else {
628                     if (event.keyCode == 38) {
629                         return setFocusToNextTag(row, 'up');
630                     }
631                     if (event.keyCode == 40) {
632                         return setFocusToNextTag(row, 'down');
633                     }
634                     return false;
635                 }
636
637             } else if (event.keyCode == 46 && event.ctrlKey) { // ctrl+del
638
639                 var index;
640                 if (element.localName() == 'subfield') index = element.parent();
641                 if (element.localName() == 'code') index = element.parent().parent();
642                 if (element.localName() == 'tag') index = element.parent();
643                 if (element.localName() == 'ind1') index = element.parent();
644                 if (element.localName() == 'ind2') index = element.parent();
645
646                 for (var i in index.parent().children()) {
647                     if (index === index.parent().children()[i]) {
648                         delete index.parent().children()[i];
649                         break;
650                     }
651                 }
652
653                 row.previousSibling.firstChild.focus();
654                 row.parentNode.removeChild(row);
655
656                 event.preventDefault();
657                 return false;
658
659             } else if (event.keyCode == 46 && event.shiftKey) { // shift+del
660
661                 var index;
662                 if (element.localName() == 'subfield') index = element;
663                 if (element.localName() == 'code') index = element.parent();
664
665                 if (index) {
666                     for (var i in index.parent().children()) {
667                         if (index === index.parent().children()[i]) {
668                             delete index.parent().children()[i];
669                             break;
670                         }
671                     }
672
673                     if (event.target.parentNode === event.target.parentNode.parentNode.lastChild) {
674                         event.target.parentNode.previousSibling.lastChild.focus();
675                     } else {
676                         event.target.parentNode.nextSibling.firstChild.nextSibling.focus();
677                     }
678
679                     event.target.parentNode.parentNode.removeChild(event.target.parentNode);
680
681                     event.preventDefault();
682                     return false;
683                 }
684             } else if (event.keyCode == 64 && event.ctrlKey) { // ctrl + F6
685                 createControlField('006','                                        ');
686                 loadRecord();
687             } else if (event.keyCode == 65 && event.ctrlKey) { // ctrl + F7
688                 createControlField('007','                                        ');
689                 loadRecord();
690             } else if (event.keyCode == 66 && event.ctrlKey) { // ctrl + F8
691                 createControlField('008','                                        ');
692                 loadRecord();
693             }
694
695             return true;
696
697         } else { // event on a control field
698             if (event.keyCode == 38) { 
699                 return setFocusToNextTag(row, 'up'); 
700             } else if (event.keyCode == 40) { 
701                 return setFocusToNextTag(row, 'down');
702             }
703         }
704     };
705
706     box.addEventListener(
707         'keypress', 
708         function () {
709             if (element.nodeKind() == 'attribute') element[0]=box.value;
710             else element.setChildren( box.value );
711             return true;
712         },
713         false
714     );
715
716     box.addEventListener(
717         'change', 
718         function () {
719             if (element.nodeKind() == 'attribute') element[0]=box.value;
720             else element.setChildren( box.value );
721             return true;
722         },
723         false
724     );
725
726     box.addEventListener(
727         'keypress', 
728         function () {
729             if (element.nodeKind() == 'attribute') element[0]=box.value;
730             else element.setChildren( box.value );
731             return true;
732         },
733         true
734     );
735
736     // 'input' event catches the box value after the keypress
737     box.addEventListener(
738         'input', 
739         function () {
740             if (element.nodeKind() == 'attribute') element[0]=box.value;
741             else element.setChildren( box.value );
742             return true;
743         },
744         true
745     );
746
747     box.addEventListener(
748         'keyup', 
749         function () {
750             if (element.localName() == 'controlfield')
751                 eval('fillFixedFields();');
752         },
753         true
754     );
755
756     return box;
757 }
758
759 function toggleFFE () {
760     var grid = document.getElementById('leaderGrid');
761     if (grid.hidden) {
762         grid.hidden = false;
763     } else {
764         grid.hidden = true;
765     }
766     return true;
767 }
768
769 function changeFFEditor (type) {
770     var grid = document.getElementById('leaderGrid');
771     grid.setAttribute('type',type);
772
773     // Hide FFEditor rows that we don't need for our current type
774     // If all of the labels for a given row do not include our
775     // desired type in their set attribute, we can hide that row
776     dojo.query('rows row', grid).forEach(function(node, index, arr) {
777         if (dojo.query('label[set~=' + type + ']', node).length == 0) {
778             node.hidden = true;
779         }
780     });
781
782 }
783
784 function fillFixedFields () {
785     try {
786             var grid = document.getElementById('leaderGrid');
787             var marc_rec = new MARC.Record ({ delimiter : '$', marcxml : xml_record.toXMLString() });
788
789             var list = [];
790             var pre_list = grid.getElementsByTagName('label');
791             for (var i in pre_list) {
792                 if ( pre_list[i].getAttribute && pre_list[i].getAttribute('set').indexOf(grid.getAttribute('type')) > -1 ) {
793                     list.push( pre_list[i] );
794                 }
795             }
796
797             for (var i in list) {
798                 var name = list[i].getAttribute('name');
799                 var value = marc_rec.extractFixedField(name, true);
800
801                 if (value === null) continue;
802
803                 list[i].nextSibling.value = value;
804             }
805
806             return true;
807     } catch(E) {
808         alert('FIXME, MARC Editor, fillFixedFields: ' + E);
809     }
810 }
811
812 function updateFixedFields (element) {
813     var grid = document.getElementById('leaderGrid');
814     var recGrid = document.getElementById('recGrid');
815     var new_value = element.value;
816
817     var marc_rec = new MARC.Record ({ delimiter : '$', marcxml : xml_record.toXMLString() });
818     var rtype = marc_rec.recordType();
819
820     var parts = {
821         ldr : marc_rec.leader(),
822         _6 : marc_rec.field('006');
823         _7 : marc_rec.field('007');
824         _8 : marc_rec.field('008');
825     };
826
827     var name = element.getAttribute('name');
828     for (var i in MARC.Record._ff_pos[name]) {
829
830         if (!MARC.Record._ff_pos[name][i][rtype]) continue;
831         if (!parts[i]) {
832             // we're missing the required field.  Add it now.
833
834             var newfield;
835             if (i == '_6') newfield = '006';
836             else if (i == '_7') newfield = '007';
837             else if (i == '_8') newfield = '008';
838             else continue;
839
840             createControlField(newfield,'                                        ');
841             parts[i] = xml_record.controlfield.(@tag==newfield).toString();
842         }
843
844         var before = parts[i].substr(0, MARC.Record._ff_pos[name][i][rtype].start);
845         var after = parts[i].substr(MARC.Record._ff_pos[name][i][rtype].start + MARC.Record._ff_pos[name][i][rtype].len);
846
847         for (var j = 0; new_value.length < MARC.Record._ff_pos[name][i][rtype].len; j++) {
848             new_value += MARC.Record._ff_pos[name][i][rtype].def;
849         }
850
851         recGrid.getElementsByAttribute('tag',i)[0].lastChild.value = before + new_value + after;
852     }
853
854     return true;
855 }
856
857 function marcLeader (leader) {
858     var row = createRow(
859         { class : 'marcLeaderRow',
860           tag : 'ldr' },
861         createLabel(
862             { value : 'LDR',
863               class : 'marcTag',
864               tooltiptext : $('catStrings').getString('staff.cat.marcedit.marcTag.LDR.label') } ),
865         createLabel(
866             { value : '',
867               class : 'marcInd1' } ),
868         createLabel(
869             { value : '',
870               class : 'marcInd2' } ),
871         createLabel(
872             { value : leader.text(),
873               class : 'marcLeader' } )
874     );
875
876     return row;
877 }
878
879 function marcControlfield (field) {
880     tagname = field.@tag.toString().substr(2);
881     var row;
882     if (tagname == '1' || tagname == '3' || tagname == '6' || tagname == '7' || tagname == '8') {
883         row = createRow(
884             { class : 'marcControlfieldRow',
885               tag : '_' + tagname },
886             createLabel(
887                 { value : field.@tag,
888                   class : 'marcTag',
889                   context : 'tags_popup',
890                   onmouseover : 'getTooltip(this, "tag");',
891                   tooltipid : 'tag' + field.@tag } ),
892             createLabel(
893                 { value : field.@ind1,
894                   class : 'marcInd1',
895                   onmouseover : 'getTooltip(this, "ind1");',
896                   tooltipid : 'tag' + field.@tag + 'ind1val' + field.@ind1 } ),
897             createLabel(
898                 { value : field.@ind2,
899                   class : 'marcInd2',
900                   onmouseover : 'getTooltip(this, "ind2");',
901                   tooltipid : 'tag' + field.@tag + 'ind2val' + field.@ind2 } ),
902             createMARCTextbox(
903                 field,
904                 { value : field.text(),
905                   class : 'plain marcEditableControlfield',
906                   name : 'CONTROL' + tagname,
907                   context : 'clipboard',
908                   size : 50,
909                   maxlength : 50 } )
910             );
911     } else {
912         row = createRow(
913             { class : 'marcControlfieldRow',
914               tag : '_' + tagname },
915             createLabel(
916                 { value : field.@tag,
917                   class : 'marcTag',
918                   onmouseover : 'getTooltip(this, "tag");',
919                   tooltipid : 'tag' + field.@tag } ),
920             createLabel(
921                 { value : field.@ind1,
922                   class : 'marcInd1',
923                   onmouseover : 'getTooltip(this, "ind1");',
924                   tooltipid : 'tag' + field.@tag + 'ind1val' + field.@ind1 } ),
925             createLabel(
926                 { value : field.@ind2,
927                   class : 'marcInd2',
928                   onmouseover : 'getTooltip(this, "ind2");',
929                   tooltipid : 'tag' + field.@tag + 'ind2val' + field.@ind2 } ),
930             createLabel(
931                 { value : field.text(),
932                   class : 'marcControlfield' } )
933         );
934     }
935
936     return row;
937 }
938
939 function stackSubfields(checkbox) {
940     var list = document.getElementsByAttribute('name','sf_box');
941
942     var o = 'vertical';
943     if (!checkbox.checked) o = 'horizontal';
944     
945     for (var i = 0; i < list.length; i++) {
946         if (list[i]) list[i].setAttribute('orient',o);
947     }
948 }
949
950 function fastItemAdd_toggle(checkbox) {
951     var x = document.getElementById('fastItemAdd_textboxes');
952     if (checkbox.checked) {
953         x.hidden = false;
954         document.getElementById('fastItemAdd_callnumber').focus();
955         document.getElementById('fastItemAdd_callnumber').select();
956     } else {
957         x.hidden = true;
958     }
959 }
960
961 function fastItemAdd_attempt(doc_id) {
962     try {
963         if (typeof window.xulG.fast_add_item != 'function') { return; }
964         if (!document.getElementById('fastItemAdd_checkbox').checked) { return; }
965         if (!document.getElementById('fastItemAdd_callnumber').value) { return; }
966         if (!document.getElementById('fastItemAdd_barcode').value) { return; }
967         window.xulG.fast_add_item( doc_id, document.getElementById('fastItemAdd_callnumber').value, document.getElementById('fastItemAdd_barcode').value );
968         document.getElementById('fastItemAdd_barcode').value = '';
969     } catch(E) {
970         alert('fastItemAdd_attempt: ' + E);
971     }
972 }
973
974 function save_attempt(xml_string) {
975     try {
976         var result = window.xulG.save.func( xml_string );   
977         if (result) {
978             oils_unlock_page();
979             if (result.id) fastItemAdd_attempt(result.id);
980             if (typeof result.on_complete == 'function') result.on_complete();
981         }
982     } catch(E) {
983         alert('save_attempt: ' + E);
984     }
985 }
986
987 function marcDatafield (field) {
988     var row = createRow(
989         { class : 'marcDatafieldRow' },
990         createMARCTextbox(
991             field.@tag,
992             { value : field.@tag,
993               class : 'plain marcTag',
994               name : 'marcTag',
995               context : 'tags_popup',
996               oninput : 'if (this.value.length == 3) { this.nextSibling.focus(); }',
997               size : 3,
998               maxlength : 3,
999               onmouseover : 'current_focus = this; getTooltip(this, "tag");' } ),
1000         createMARCTextbox(
1001             field.@ind1,
1002             { value : field.@ind1,
1003               class : 'plain marcInd1',
1004               name : 'marcInd1',
1005               oninput : 'if (this.value.length == 1) { this.nextSibling.focus(); }',
1006               size : 1,
1007               maxlength : 1,
1008               onmouseover : 'current_focus = this; getContextMenu(this, "ind1"); getTooltip(this, "ind1");',
1009               oncontextmenu : 'getContextMenu(this, "ind1");' } ),
1010         createMARCTextbox(
1011             field.@ind2,
1012             { value : field.@ind2,
1013               class : 'plain marcInd2',
1014               name : 'marcInd2',
1015               oninput : 'if (this.value.length == 1) { this.nextSibling.firstChild.firstChild.focus(); }',
1016               size : 1,
1017               maxlength : 1,
1018               onmouseover : 'current_focus = this; getContextMenu(this, "ind2"); getTooltip(this, "ind2");',
1019               oncontextmenu : 'getContextMenu(this, "ind2");' } ),
1020         createHbox({ name : 'sf_box' })
1021     );
1022
1023     if (!current_focus && field.@tag == '') current_focus = row.childNodes[0];
1024     if (!current_focus && field.@ind1 == '') current_focus = row.childNodes[1];
1025     if (!current_focus && field.@ind2 == '') current_focus = row.childNodes[2];
1026
1027     var sf_box = row.lastChild;
1028     if (document.getElementById('stackSubfields').checked)
1029         sf_box.setAttribute('orient','vertical');
1030
1031     sf_box.addEventListener(
1032         'click',
1033         function (e) {
1034             if (sf_box === e.target) {
1035                 sf_box.lastChild.lastChild.focus();
1036             } else if (e.target.parentNode === sf_box) {
1037                 e.target.lastChild.focus();
1038             }
1039         },
1040         false
1041     );
1042
1043
1044     for (var i in field.subfield) {
1045         var sf = field.subfield[i];
1046         sf_box.appendChild(
1047             marcSubfield(sf)
1048         );
1049
1050         dojo.query('.marcSubfield', sf_box).forEach(wrap_long_fields);
1051
1052         if (sf.@code == '' && (!current_focus || current_focus.className.match(/Ind/)))
1053             current_focus = sf_box.lastChild.childNodes[1];
1054     }
1055
1056     return row;
1057 }
1058
1059 function marcSubfield (sf) {            
1060     return createHbox(
1061         { class : 'marcSubfieldBox' },
1062         createLabel(
1063             { value : "\u2021",
1064               class : 'plain marcSubfieldDelimiter',
1065               onmouseover : 'getTooltip(this.nextSibling, "subfield");',
1066               oncontextmenu : 'getContextMenu(this.nextSibling, "subfield");',
1067                 //onclick : 'this.nextSibling.focus();',
1068                 onfocus : 'this.nextSibling.focus();',
1069               size : 2 } ),
1070         createMARCTextbox(
1071             sf.@code,
1072             { value : sf.@code,
1073               class : 'plain marcSubfieldCode',
1074               name : 'marcSubfieldCode',
1075               onmouseover : 'current_focus = this; getContextMenu(this, "subfield"); getTooltip(this, "subfield");',
1076               oncontextmenu : 'getContextMenu(this, "subfield");',
1077               oninput : 'if (this.value.length == 1) { this.nextSibling.focus(); }',
1078               size : 2,
1079               maxlength : 1 } ),
1080         createMARCTextbox(
1081             sf,
1082             { value : sf.text(),
1083               name : sf.parent().@tag + ':' + sf.@code,
1084               class : 'plain marcSubfield', 
1085               onmouseover : 'getTooltip(this, "subfield");',
1086               contextmenu : function (event) { getAuthorityContextMenu(event.target, sf) },
1087               size : new String(sf.text()).length + 2,
1088               oninput : "this.setAttribute('size', this.value.length + 2);"
1089             } )
1090     );
1091 }
1092
1093 function loadRecord() {
1094     try {
1095             var grid_rows = document.getElementById('recGrid').lastChild;
1096
1097             while (grid_rows.firstChild) grid_rows.removeChild(grid_rows.firstChild);
1098
1099             grid_rows.appendChild( marcLeader( xml_record.leader ) );
1100
1101             for (var i in xml_record.controlfield) {
1102                 grid_rows.appendChild( marcControlfield( xml_record.controlfield[i] ) );
1103             }
1104
1105             for (var i in xml_record.datafield) {
1106                 grid_rows.appendChild( marcDatafield( xml_record.datafield[i] ) );
1107             }
1108
1109             grid_rows.getElementsByAttribute('class','marcDatafieldRow')[0].firstChild.focus();
1110
1111             var marc_rec = new MARC.Record ({ delimiter : '$', marcxml : xml_record.toXMLString() });
1112             changeFFEditor(marc_rec.recordType());
1113             fillFixedFields();
1114     } catch(E) {
1115         alert('FIXME, MARC Editor, loadRecord: ' + E);
1116     }
1117 }
1118
1119
1120 function genToolTips () {
1121     for (var i in bib_data.field) {
1122         var f = bib_data.field[i];
1123     
1124         tag_menu.appendChild(
1125             createMenuitem(
1126                 { label : f.@tag,
1127                   oncommand : 
1128                       'current_focus.value = "' + f.@tag + '";' +
1129                     'var e = document.createEvent("MutationEvents");' +
1130                     'e.initMutationEvent("change",1,1,null,0,0,0,0);' +
1131                     'current_focus.inputField.dispatchEvent(e);',
1132                   disabled : f.@tag < '010' ? "true" : "false",
1133                   tooltiptext : f.description }
1134             )
1135         );
1136     
1137         var i1_popup = createPopup({position : 'after_start', id : 't' + f.@tag + 'i1' });
1138         context_menus.appendChild( i1_popup );
1139     
1140         var i2_popup = createPopup({position : 'after_start', id : 't' + f.@tag + 'i2' });
1141         context_menus.appendChild( i2_popup );
1142     
1143         var sf_popup = createPopup({position : 'after_start', id : 't' + f.@tag + 'sf' });
1144         context_menus.appendChild( sf_popup );
1145     
1146         tooltip_hash['tag' + f.@tag] = f.description;
1147         for (var j in f.indicator) {
1148             var ind = f.indicator[j];
1149             tooltip_hash['tag' + f.@tag + 'ind' + ind.@position + 'val' + ind.@value] = ind.description;
1150     
1151             if (ind.@position == 1) {
1152                 i1_popup.appendChild(
1153                     createMenuitem(
1154                         { label : ind.@value,
1155                           oncommand : 
1156                               'current_focus.value = "' + ind.@value + '";' +
1157                             'var e = document.createEvent("MutationEvents");' +
1158                             'e.initMutationEvent("change",1,1,null,0,0,0,0);' +
1159                             'current_focus.inputField.dispatchEvent(e);',
1160                           tooltiptext : ind.description }
1161                     )
1162                 );
1163             }
1164     
1165             if (ind.@position == 2) {
1166                 i2_popup.appendChild(
1167                     createMenuitem(
1168                         { label : ind.@value,
1169                           oncommand : 
1170                               'current_focus.value = "' + ind.@value + '";' +
1171                             'var e = document.createEvent("MutationEvents");' +
1172                             'e.initMutationEvent("change",1,1,null,0,0,0,0);' +
1173                             'current_focus.inputField.dispatchEvent(e);',
1174                           tooltiptext : ind.description }
1175                     )
1176                 );
1177             }
1178         }
1179     
1180         for (var j in f.subfield) {
1181             var sf = f.subfield[j];
1182             tooltip_hash['tag' + f.@tag + 'sf' + sf.@code] = sf.description;
1183     
1184             sf_popup.appendChild(
1185                 createMenuitem(
1186                     { label : sf.@code,
1187                       oncommand : 
1188                           'current_focus.value = "' + sf.@code + '";' +
1189                         'var e = document.createEvent("MutationEvents");' +
1190                         'e.initMutationEvent("change",1,1,null,0,0,0,0);' +
1191                         'current_focus.inputField.dispatchEvent(e);',
1192                       tooltiptext : sf.description
1193                     }
1194                 )
1195             );
1196         }
1197     }
1198 }
1199
1200 function getTooltip (target, type) {
1201
1202     var tt = '';
1203     if (type == 'subfield')
1204         tt = 'tag' + target.parentNode.parentNode.parentNode.firstChild.value + 'sf' + target.parentNode.childNodes[1].value;
1205
1206     if (type == 'ind1')
1207         tt = 'tag' + target.parentNode.firstChild.value + 'ind1val' + target.value;
1208
1209     if (type == 'ind2')
1210         tt = 'tag' + target.parentNode.firstChild.value + 'ind2val' + target.value;
1211
1212     if (type == 'tag')
1213         tt = 'tag' + target.parentNode.firstChild.value;
1214
1215     if (!document.getElementById( tt )) {
1216         p.appendChild(
1217             createTooltip(
1218                 { id : tt,
1219                   flex : "1",
1220                   orient : 'vertical',
1221                   onpopupshown : 'this.width = this.firstChild.boxObject.width + 10; this.height = this.firstChild.boxObject.height + 10;',
1222                   class : 'tooltip' },
1223                 createDescription({}, document.createTextNode( tooltip_hash[tt] ) )
1224             )
1225         );
1226     }
1227
1228     target.tooltip = tt;
1229     return true;
1230 }
1231
1232 function getContextMenu (target, type) {
1233
1234     var tt = '';
1235     if (type == 'subfield')
1236         tt = 't' + target.parentNode.parentNode.parentNode.firstChild.value + 'sf';
1237
1238     if (type == 'ind1')
1239         tt = 't' + target.parentNode.firstChild.value + 'i1';
1240
1241     if (type == 'ind2')
1242         tt = 't' + target.parentNode.firstChild.value + 'i2';
1243
1244     target.setAttribute('context', tt);
1245     return true;
1246 }
1247
1248 var control_map = {
1249     100 : {
1250         'a' : { 100 : 'a' },
1251         'd' : { 100 : 'd' },
1252         'e' : { 100 : 'e' },
1253         'q' : { 100 : 'q' }
1254     },
1255     110 : {
1256         'a' : { 110 : 'a' },
1257         'd' : { 110 : 'd' }
1258     },
1259     111 : {
1260         'a' : { 111 : 'a' },
1261         'd' : { 111 : 'd' }
1262     },
1263     130 : {
1264         'a' : { 130 : 'a' },
1265         'd' : { 130 : 'd' }
1266     },
1267     240 : {
1268         'a' : { 130 : 'a' },
1269         'd' : { 130 : 'd' }
1270     },
1271     400 : {
1272         'a' : { 100 : 'a' },
1273         'd' : { 100 : 'd' }
1274     },
1275     410 : {
1276         'a' : { 110 : 'a' },
1277         'd' : { 110 : 'd' }
1278     },
1279     411 : {
1280         'a' : { 111 : 'a' },
1281         'd' : { 111 : 'd' }
1282     },
1283     440 : {
1284         'a' : { 130 : 'a' },
1285         'n' : { 130 : 'n' },
1286         'p' : { 130 : 'p' }
1287     },
1288     700 : {
1289         'a' : { 100 : 'a' },
1290         'd' : { 100 : 'd' },
1291         'q' : { 100 : 'q' },
1292         't' : { 100 : 't' }
1293     },
1294     710 : {
1295         'a' : { 110 : 'a' },
1296         'd' : { 110 : 'd' }
1297     },
1298     711 : {
1299         'a' : { 111 : 'a' },
1300         'c' : { 111 : 'c' },
1301         'd' : { 111 : 'd' }
1302     },
1303     730 : {
1304         'a' : { 130 : 'a' },
1305         'd' : { 130 : 'd' }
1306     },
1307     800 : {
1308         'a' : { 100 : 'a' },
1309         'd' : { 100 : 'd' }
1310     },
1311     810 : {
1312         'a' : { 110 : 'a' },
1313         'd' : { 110 : 'd' }
1314     },
1315     811 : {
1316         'a' : { 111 : 'a' },
1317         'd' : { 111 : 'd' }
1318     },
1319     830 : {
1320         'a' : { 130 : 'a' },
1321         'd' : { 130 : 'd' }
1322     },
1323     600 : {
1324         'a' : { 100 : 'a' },
1325         'd' : { 100 : 'd' },
1326         'q' : { 100 : 'q' },
1327         't' : { 100 : 't' },
1328         'v' : { 180 : 'v',
1329             100 : 'v',
1330             181 : 'v',
1331             182 : 'v',
1332             185 : 'v'
1333         },
1334         'x' : { 180 : 'x',
1335             100 : 'x',
1336             181 : 'x',
1337             182 : 'x',
1338             185 : 'x'
1339         },
1340         'y' : { 180 : 'y',
1341             100 : 'y',
1342             181 : 'y',
1343             182 : 'y',
1344             185 : 'y'
1345         },
1346         'z' : { 180 : 'z',
1347             100 : 'z',
1348             181 : 'z',
1349             182 : 'z',
1350             185 : 'z'
1351         }
1352     },
1353     610 : {
1354         'a' : { 110 : 'a' },
1355         'd' : { 110 : 'd' },
1356         't' : { 110 : 't' },
1357         'v' : { 180 : 'v',
1358             110 : 'v',
1359             181 : 'v',
1360             182 : 'v',
1361             185 : 'v'
1362         },
1363         'x' : { 180 : 'x',
1364             110 : 'x',
1365             181 : 'x',
1366             182 : 'x',
1367             185 : 'x'
1368         },
1369         'y' : { 180 : 'y',
1370             110 : 'y',
1371             181 : 'y',
1372             182 : 'y',
1373             185 : 'y'
1374         },
1375         'z' : { 180 : 'z',
1376             110 : 'z',
1377             181 : 'z',
1378             182 : 'z',
1379             185 : 'z'
1380         }
1381     },
1382     611 : {
1383         'a' : { 111 : 'a' },
1384         'd' : { 111 : 'd' },
1385         't' : { 111 : 't' },
1386         'v' : { 180 : 'v',
1387             111 : 'v',
1388             181 : 'v',
1389             182 : 'v',
1390             185 : 'v'
1391         },
1392         'x' : { 180 : 'x',
1393             111 : 'x',
1394             181 : 'x',
1395             182 : 'x',
1396             185 : 'x'
1397         },
1398         'y' : { 180 : 'y',
1399             111 : 'y',
1400             181 : 'y',
1401             182 : 'y',
1402             185 : 'y'
1403         },
1404         'z' : { 180 : 'z',
1405             111 : 'z',
1406             181 : 'z',
1407             182 : 'z',
1408             185 : 'z'
1409         }
1410     },
1411     630 : {
1412         'a' : { 130 : 'a' },
1413         'd' : { 130 : 'd' }
1414     },
1415     648 : {
1416         'a' : { 148 : 'a' },
1417         'v' : { 148 : 'v' },
1418         'x' : { 148 : 'x' },
1419         'y' : { 148 : 'y' },
1420         'z' : { 148 : 'z' }
1421     },
1422     650 : {
1423         'a' : { 150 : 'a' },
1424         'b' : { 150 : 'b' },
1425         'v' : { 180 : 'v',
1426             150 : 'v',
1427             181 : 'v',
1428             182 : 'v',
1429             185 : 'v'
1430         },
1431         'x' : { 180 : 'x',
1432             150 : 'x',
1433             181 : 'x',
1434             182 : 'x',
1435             185 : 'x'
1436         },
1437         'y' : { 180 : 'y',
1438             150 : 'y',
1439             181 : 'y',
1440             182 : 'y',
1441             185 : 'y'
1442         },
1443         'z' : { 180 : 'z',
1444             150 : 'z',
1445             181 : 'z',
1446             182 : 'z',
1447             185 : 'z'
1448         }
1449     },
1450     651 : {
1451         'a' : { 151 : 'a' },
1452         'v' : { 180 : 'v',
1453             151 : 'v',
1454             181 : 'v',
1455             182 : 'v',
1456             185 : 'v'
1457         },
1458         'x' : { 180 : 'x',
1459             151 : 'x',
1460             181 : 'x',
1461             182 : 'x',
1462             185 : 'x'
1463         },
1464         'y' : { 180 : 'y',
1465             151 : 'y',
1466             181 : 'y',
1467             182 : 'y',
1468             185 : 'y'
1469         },
1470         'z' : { 180 : 'z',
1471             151 : 'z',
1472             181 : 'z',
1473             182 : 'z',
1474             185 : 'z'
1475         }
1476     },
1477     655 : {
1478         'a' : { 155 : 'a' },
1479         'v' : { 180 : 'v',
1480             155 : 'v',
1481             181 : 'v',
1482             182 : 'v',
1483             185 : 'v'
1484         },
1485         'x' : { 180 : 'x',
1486             155 : 'x',
1487             181 : 'x',
1488             182 : 'x',
1489             185 : 'x'
1490         },
1491         'y' : { 180 : 'y',
1492             155 : 'y',
1493             181 : 'y',
1494             182 : 'y',
1495             185 : 'y'
1496         },
1497         'z' : { 180 : 'z',
1498             155 : 'z',
1499             181 : 'z',
1500             182 : 'z',
1501             185 : 'z'
1502         }
1503     }
1504 };
1505
1506 function getAuthorityContextMenu (target, sf) {
1507     var menu_id = sf.parent().@tag + ':' + sf.@code + '-authority-context-' + sf;
1508
1509     var page = 0;
1510     var old = dojo.byId( menu_id );
1511     if (old) {
1512         page = auth_pages[menu_id];
1513         old.parentNode.removeChild(old);
1514     } else {
1515         auth_pages[menu_id] = 0;
1516     }
1517
1518     var sf_popup = createPopup({ id : menu_id, flex : 1 });
1519
1520     sf_popup.addEventListener("popuphiding", function(event) {
1521         if (show_auth_menu) {
1522             show_auth_menu = false;
1523             getAuthorityContextMenu(target, sf);
1524             dojo.byId(menu_id).openPopup();
1525         }  
1526     }, false);
1527
1528     context_menus.appendChild( sf_popup );
1529
1530     var found_acs = [];
1531     dojo.forEach( acs.controlSetList(), function (acs_id) {
1532         if (ac.controlSet(acs_id).control_map[sf.parent().@tag]) found_acs.push(acs_id);
1533     });
1534
1535     if (!found_acs.length) {
1536         sf_popup.appendChild(createLabel( { value : $('catStrings').getString('staff.cat.marcedit.not_authority_field.label') } ) );
1537         target.setAttribute('context', 'clipboard');
1538         return false;
1539     }
1540
1541     if (sf.toString().replace(/\s*/, '')) {
1542         browseAuthority(sf_popup, menu_id, target, sf, 20, page);
1543     }
1544
1545     return true;
1546 }
1547
1548 /* Apply the complete 1xx */
1549 function applyFullAuthority ( target, ui_sf, e4x_sf ) {
1550     var new_vals = dojo.query('*[tag^="1"]', target);
1551     return applyAuthority( target, ui_sf, e4x_sf, new_vals );
1552 }
1553
1554 function applySelectedAuthority ( target, ui_sf, e4x_sf ) {
1555     var new_vals = target.getElementsByAttribute('checked','true');
1556     return applyAuthority( target, ui_sf, e4x_sf, new_vals );
1557 }
1558
1559 function applyAuthority ( target, ui_sf, e4x_sf, new_vals ) {
1560     var field = e4x_sf.parent();
1561
1562     for (var i = 0; i < new_vals.length; i++) {
1563
1564         var sf_list = field.subfield;
1565         for (var j in sf_list) {
1566
1567             if (sf_list[j].@code == new_vals[i].getAttribute('subfield')) {
1568                 sf_list[j] = new_vals[i].getAttribute('value');
1569                 new_vals[i].setAttribute('subfield','');
1570                 break;
1571             }
1572         }
1573     }
1574
1575     for (var i = 0; i < new_vals.length; i++) {
1576         if (!new_vals[i].getAttribute('subfield')) continue;
1577
1578         var val = new_vals[i].getAttribute('value');
1579
1580         var sf = <subfield code="" xmlns="http://www.loc.gov/MARC21/slim">{val}</subfield>;
1581         sf.@code = new_vals[i].getAttribute('subfield');
1582
1583         field.insertChildAfter(field.subfield[field.subfield.length() - 1], sf);
1584     }
1585
1586     var row = marcDatafield( field );
1587
1588     var node = ui_sf;
1589     while (node.nodeName != 'row') {
1590         node = node.parentNode;
1591     }
1592
1593     node.parentNode.replaceChild( row, node );
1594     return true;
1595 }
1596
1597 function validateAuthority (button) {
1598     var grid = document.getElementById('recGrid');
1599     var label = button.getAttribute('label');
1600
1601     //loop over rows
1602     var rows = grid.lastChild.childNodes;
1603     for (var i = 0; i < rows.length; i++) {
1604         var row = rows[i];
1605         var tag = row.firstChild;
1606
1607         for (var acs_id in acs.controlSetList()) {
1608             var control_map = acs.controlSet(acs_id).control_map;
1609     
1610             if (!control_map[tag.value]) continue
1611             button.setAttribute('label', label + ' - ' + tag.value);
1612     
1613             var ind1 = tag.nextSibling;
1614             var ind2 = ind1.nextSibling;
1615             var subfields = ind2.nextSibling.childNodes;
1616     
1617             var tags = {};
1618     
1619             for (var j = 0; j < subfields.length; j++) {
1620                 var sf = subfields[j];
1621                 var sf_code = sf.childNodes[1].value;
1622                 var sf_value = sf.childNodes[2].value;
1623     
1624                 if (!control_map[tag.value][sf_code]) continue;
1625     
1626                 var found = 0;
1627                 for (var a_tag in control_map[tag.value][sf_code]) {
1628                     if (!tags[a_tag]) tags[a_tag] = [];
1629                     tags[a_tag].push({ term : sf_value, subfield : sf_code });
1630                 }
1631     
1632             }
1633     
1634             for (var val_tag in tags) {
1635                 var auth_data = validateBibField( acs_id, [val_tag], tags[val_tag]);
1636                 var res = new XML( auth_data.responseText );
1637                 found = parseInt(res.gw::payload.gw::string.toString());
1638                 if (found) break;
1639             }
1640     
1641             // XXX If adt, etc should be validated separately from vxz, etc then move this up into the above for loop
1642             for (var j = 0; j < subfields.length; j++) {
1643                 var sf = subfields[j];
1644                 if (!found) {
1645                     dojo.removeClass(sf.childNodes[2], 'marcValidated');
1646                     dojo.addClass(sf.childNodes[2], 'marcUnvalidated');
1647                 } else {
1648                     dojo.removeClass(sf.childNodes[2], 'marcUnvalidated');
1649                     dojo.addClass(sf.childNodes[2], 'marcValidated');
1650                 }
1651             }
1652
1653             if (found) break;
1654         }
1655     }
1656
1657     button.setAttribute('label', label);
1658
1659     return true;
1660 }
1661
1662
1663 function validateBibField (tags, searches) {
1664     var url = "/gateway?input_format=json&format=xml&service=open-ils.search&method=open-ils.search.authority.validate.tag";
1665     url += '&param="tags"&param=' + js2JSON(tags);
1666     url += '&param="searches"&param=' + js2JSON(searches);
1667
1668
1669     var req = new XMLHttpRequest();
1670     req.open('GET',url,false);
1671     req.send(null);
1672
1673     return req;
1674
1675 }
1676 function searchAuthority (term, tag, sf, limit) {
1677     var url = "/gateway?input_format=json&format=xml&service=open-ils.search&method=open-ils.search.authority.fts";
1678     url += '&param="term"&param="' + term + '"';
1679     url += '&param="limit"&param=' + limit;
1680     url += '&param="tag"&param=' + tag;
1681     url += '&param="subfield"&param="' + sf + '"';
1682
1683
1684     var req = new XMLHttpRequest();
1685     req.open('GET',url,false);
1686     req.send(null);
1687
1688     return req;
1689
1690 }
1691
1692 function browseAuthority (sf_popup, menu_id, target, sf, limit, page) {
1693     dojo.require('dojox.xml.parser');
1694
1695     // map tag + subfield to the appropriate authority browse axis:
1696     // currently authority.author, authority.subject, authority.title, authority.topic
1697     // based on mappings in OpenILS::Application::SuperCat
1698
1699     var type;
1700
1701     // Map based on replacing the first char of the selected tag with '1'
1702     switch ('1' + (sf.parent().@tag.toString()).substring(1)) {
1703         case "130":
1704             type = 'authority.title';
1705             break;
1706
1707         case "100":
1708         case "110":
1709         case "111":
1710             type = 'authority.author';
1711             break;
1712
1713         case "150":
1714             type = 'authority.topic';
1715             break;
1716
1717         case "148":
1718         case "151":
1719         case "155":
1720             type = 'authority.subject';
1721             break;
1722
1723         // No matching tag means no authorities to search - shortcut
1724         default:
1725             return;
1726     }
1727
1728     if (!limit) {
1729         limit = 10;
1730     }
1731
1732     if (!page) {
1733         page = 0;
1734     }
1735
1736     var url = '/opac/extras/browse/marcxml/'
1737         + type + '.refs'
1738         + '/1' // OU - currently unscoped
1739         + '/' + sf.toString()
1740         + '/' + page
1741         + '/' + limit
1742     ;
1743
1744     // would be good to carve this out into a separate function
1745     dojo.xhrGet({"url":url, "sync": true, "preventCache": true, "handleAs":"xml", "load": function(records) {
1746         var create_menu = createMenu({ label: $('catStrings').getString('staff.cat.marcedit.create_authority.label')});
1747
1748         var cm_popup = create_menu.appendChild(
1749             createMenuPopup()
1750         );
1751
1752         cm_popup.appendChild(
1753             createMenuitem({ label : $('catStrings').getString('staff.cat.marcedit.create_authority_now.label'),
1754                 command : function() { 
1755                     // Call middle-layer function to create and save the new authority
1756                     var source_f = summarizeField(sf);
1757                     var new_auth = fieldmapper.standardRequest(
1758                         ["open-ils.cat", "open-ils.cat.authority.record.create_from_bib"],
1759                         [source_f, xulG.marc_control_number_identifier, ses()]
1760                     );
1761                     if (new_auth && new_auth.id()) {
1762                         addNewAuthorityID(new_auth, sf, target);
1763                     }
1764                 }
1765             })
1766         );
1767
1768         cm_popup.appendChild(
1769             createMenuitem({ label : $('catStrings').getString('staff.cat.marcedit.create_authority_edit.label'),
1770                 command : function() { 
1771                     // Generate the new authority by calling the new middle-layer
1772                     // function (a non-saving variant), then display in another
1773                     // MARC editor
1774                     var source_f = summarizeField(sf);
1775                     var authtoken = ses();
1776                     dojo.require('openils.PermaCrud');
1777                     var pcrud = new openils.PermaCrud({"authtoken": authtoken});
1778                     var rec = fieldmapper.standardRequest(
1779                         ["open-ils.cat", "open-ils.cat.authority.record.create_from_bib.readonly"],
1780                         { "params": [source_f, xulG.marc_control_number_identifier] }
1781                     );
1782                     loadMarcEditor(pcrud, rec, target, sf);
1783                 }
1784             })
1785         );
1786
1787         sf_popup.appendChild(create_menu);
1788         sf_popup.appendChild( createComplexXULElement( 'menuseparator' ) );
1789
1790         // append "Previous page" results browser
1791         sf_popup.appendChild(
1792             createMenuitem({ label : $('catStrings').getString('staff.cat.marcedit.previous_page.label'),
1793                 command : function(event) { 
1794                     auth_pages[menu_id] -= 1;
1795                     show_auth_menu = true;
1796                 }
1797             })
1798         );
1799         sf_popup.appendChild( createComplexXULElement( 'menuseparator' ) );
1800
1801         dojo.query('record', records).forEach(function(record) {
1802             var main_text = '';
1803             var see_from = [];
1804             var see_also = [];
1805             var auth_id = dojox.xml.parser.textContent(dojo.query('datafield[tag="901"] subfield[code="c"]', record)[0]);
1806             var auth_org = dojox.xml.parser.textContent(dojo.query('controlfield[tag="003"]', record)[0]);
1807
1808             // Grab the fields with tags beginning with 1 (main entries) and iterate through the subfields
1809             dojo.query('datafield[tag^="1"]', record).forEach(function(field) {
1810                 dojo.query('subfield', field).forEach(function(subfield) {
1811                     if (main_text) {
1812                         main_text += ' / ';
1813                     }
1814                     main_text += dojox.xml.parser.textContent(subfield);
1815                 });
1816             });
1817
1818             // Grab the fields with tags beginning with 4 (see from entries) and iterate through the subfields
1819             dojo.query('datafield[tag^="4"]', record).forEach(function(field) {
1820                 var see_text = '';
1821                 dojo.query('subfield', field).forEach(function(subfield) {
1822                     if (see_text) {
1823                         see_text += ' / ';
1824                     }
1825                     see_text += dojox.xml.parser.textContent(subfield);
1826                 });
1827                 see_from.push($('catStrings').getFormattedString('staff.cat.marcedit.authority_see_from', [see_text]));
1828             });
1829
1830             // Grab the fields with tags beginning with 5 (see also entries) and iterate through the subfields
1831             dojo.query('datafield[tag^="5"]', record).forEach(function(field) {
1832                 var see_text = '';
1833                 dojo.query('subfield', field).forEach(function(subfield) {
1834                     if (see_text) {
1835                         see_text += ' / ';
1836                     }
1837                     see_text += dojox.xml.parser.textContent(subfield);
1838                 });
1839                 see_also.push($('catStrings').getFormattedString('staff.cat.marcedit.authority_see_also', [see_text]));
1840             });
1841
1842             buildAuthorityPopup(main_text, record, auth_org, auth_id, sf_popup, target, sf);
1843
1844             dojo.forEach(see_from, function(entry_text) {
1845                 buildAuthorityPopup(entry_text, record, auth_org, auth_id, sf_popup, target, sf, "font-style: italic; margin-left: 2em;");
1846             });
1847
1848             // To-do: instead of launching the standard selector menu, invoke
1849             // a new authority search using the 5XX entry text
1850             dojo.forEach(see_also, function(entry_text) {
1851                 buildAuthorityPopup(entry_text, record, auth_org, auth_id, sf_popup, target, sf, "font-style: italic; margin-left: 2em;");
1852             });
1853
1854         });
1855
1856         if (sf_popup.childNodes.length == 0) {
1857             sf_popup.appendChild(createLabel( { value : $('catStrings').getString('staff.cat.marcedit.no_authority_match.label') } ) );
1858         } else {
1859             // append "Next page" results browser
1860             sf_popup.appendChild( createComplexXULElement( 'menuseparator' ) );
1861             sf_popup.appendChild(
1862                 createMenuitem({ label : $('catStrings').getString('staff.cat.marcedit.next_page.label'),
1863                     command : function(event) { 
1864                         auth_pages[menu_id] += 1;
1865                         show_auth_menu = true;
1866                     }
1867                 })
1868             );
1869         }
1870
1871         target.setAttribute('context', menu_id);
1872         return true;
1873     }});
1874
1875 }
1876
1877 function buildAuthorityPopup (entry_text, record, auth_org, auth_id, sf_popup, target, sf, style) {
1878     var grid = dojo.query('[name="authority-marc-template"]')[0].cloneNode(true);
1879     grid.setAttribute('name','-none-');
1880     grid.setAttribute('style','overflow:scroll');
1881
1882     var submenu = createMenu( { "label": entry_text } );
1883
1884     var popup = createMenuPopup({ "flex": "1" });
1885     if (style) {
1886         submenu.setAttribute('style', style);
1887         popup.setAttribute('style', 'font-style: normal; margin-left: 0em;');
1888     }
1889     submenu.appendChild(popup);
1890
1891     dojo.query('datafield[tag^="1"], datafield[tag^="4"], datafield[tag^="5"]', record).forEach(function(field) {
1892         buildAuthorityPopupSelector(field, grid, auth_org, auth_id);
1893     });
1894
1895     grid.hidden = false;
1896     popup.appendChild( grid );
1897
1898     popup.appendChild(
1899         createMenuitem(
1900             { label : $('catStrings').getString('staff.cat.marcedit.apply_selected.label'),
1901               command : function (event) {
1902                     applySelectedAuthority(event.target.previousSibling, target, sf);
1903                     return true;
1904               }
1905             }
1906         )
1907     );
1908
1909     popup.appendChild( createComplexXULElement( 'menuseparator' ) );
1910
1911     popup.appendChild(
1912         createMenuitem(
1913             { label : $('catStrings').getString('staff.cat.marcedit.apply_full.label'),
1914               command : function (event) {
1915                     applyFullAuthority(event.target.previousSibling.previousSibling.previousSibling, target, sf);
1916                     return true;
1917               }
1918             }
1919         )
1920     );
1921
1922     sf_popup.appendChild( submenu );
1923 }
1924
1925 function buildAuthorityPopupSelector (field, grid, auth_org, auth_id) {
1926     var row = createRow(
1927         { },
1928         createLabel( { "value" : dojo.attr(field, 'tag') } ),
1929         createLabel( { "value" : dojo.attr(field, 'ind1') } ),
1930         createLabel( { "value" : dojo.attr(field, 'ind2') } )
1931     );
1932
1933     var sf_box = createHbox();
1934     dojo.query('subfield', field).forEach(function(subfield) {
1935         sf_box.appendChild(
1936             createCheckbox(
1937                 { "label"    : '\u2021' + dojo.attr(subfield, 'code') + ' ' + dojox.xml.parser.textContent(subfield),
1938                   "subfield" : dojo.attr(subfield, 'code'),
1939                   "tag"      : dojo.attr(field, 'tag'),
1940                   "value"    : dojox.xml.parser.textContent(subfield)
1941                 }
1942             )
1943         );
1944         row.appendChild(sf_box);
1945     });
1946
1947     // Append the authority linking subfield only for main entries
1948     if (dojo.attr(field, 'tag').charAt(0) == '1') {
1949         sf_box.appendChild(
1950             createCheckbox(
1951                 { "label"    : '\u2021' + '0' + ' (' + auth_org + ')' + auth_id,
1952                   "subfield" : '0',
1953                   "tag"      : dojo.attr(field, 'tag'),
1954                   "value"    : '(' + auth_org + ')' + auth_id
1955                 }
1956             )
1957         );
1958     }
1959     row.appendChild(sf_box);
1960
1961     grid.lastChild.appendChild(row);
1962 }
1963
1964 function summarizeField(sf) {
1965     var source_f= {
1966         "tag": '',
1967         "ind1": '',
1968         "ind2": '',
1969         "subfields": []
1970     };
1971
1972     source_f.tag = sf.parent().@tag.toString();
1973     source_f.ind1 = sf.parent().@ind1.toString();
1974     source_f.ind1 = sf.parent().@ind2.toString();
1975
1976     for (var i = 0; i < sf.parent().subfield.length(); i++) {
1977         var sf_iter = sf.parent().subfield[i];
1978
1979         /* Filter out subfields that are not controlled for this tag */
1980         if (!control_map[source_f.tag][sf_iter.@code.toString()]) {
1981             continue;
1982         }
1983
1984         source_f.subfields.push([sf_iter.@code.toString(), sf_iter.toString()]);
1985     }
1986
1987     return source_f;
1988 }
1989
1990 function buildBibSourceList (authtoken, recId) {
1991     /* TODO: Work out how to set the bib source of the bre that does not yet
1992      * exist - this is specifically in the case of Z39.50 imports. Right now
1993      * we just avoid populating and showing the config.bib_source list
1994      */
1995     if (!recId) {
1996         return false;
1997     }
1998
1999     var bib = xulG.record.bre;
2000
2001     dojo.require('openils.PermaCrud');
2002
2003     // cbsList = the XUL menulist that contains the available bib sources 
2004     var cbsList = dojo.byId('bib-source-list');
2005
2006     // bibSources = an array containing all of the bib source objects
2007     var bibSources = new openils.PermaCrud({"authtoken": authtoken}).retrieveAll('cbs');
2008
2009     // A tad ugly, but gives us the index of the bib source ID in cbsList
2010     var x = 0;
2011     var cbsListArr = [];
2012     dojo.forEach(bibSources, function (item) {
2013         cbsList.appendItem(item.source(), item.id());
2014         cbsListArr[item.id()] = x;
2015         x++;
2016     });
2017
2018     // Show the current value of the bib source for this record
2019     cbsList.selectedIndex = cbsListArr[bib.source()];
2020
2021     // Display the bib source selection widget
2022     dojo.byId('bib-source-list-caption').hidden = false;
2023     dojo.byId('bib-source-list').hidden = false;
2024     dojo.byId('bib-source-list-button').disabled = true;
2025     dojo.byId('bib-source-list-button').hidden = false;
2026 }
2027
2028 // Fired when the "Update Source" button is clicked
2029 // Updates the value of the bib source for the current record
2030 function updateBibSource() {
2031     var authtoken = ses();
2032     var cbs = dojo.byId('bib-source-list').selectedItem.value;
2033     var recId = xulG.record.id;
2034     var pcrud = new openils.PermaCrud({"authtoken": authtoken});
2035     var bib = pcrud.retrieve('bre', recId);
2036     if (bib.source() != cbs) {
2037         bib.source(cbs);
2038         bib.ischanged = true;
2039         pcrud.update(bib);
2040     }
2041 }
2042
2043 function onBibSourceSelect() {
2044     var cbs = dojo.byId('bib-source-list').selectedItem.value;
2045     var bib = xulG.record.bre;
2046     if (bib.source() != cbs) {
2047         dojo.byId('bib-source-list-button').disabled = false;   
2048     } else {
2049         dojo.byId('bib-source-list-button').disabled = true;   
2050     }
2051 }
2052
2053 function addNewAuthorityID(authority, sf, target) {
2054     var id_sf = <subfield code="0" xmlns="http://www.loc.gov/MARC21/slim">({xulG.marc_control_number_identifier}){authority.id()}</subfield>;
2055     sf.parent().appendChild(id_sf);
2056     var new_sf = marcSubfield(id_sf);
2057
2058     var node = target;
2059     while (dojo.attr(node, 'name') != 'sf_box') {
2060         node = node.parentNode;
2061     }
2062     node.appendChild( new_sf );
2063
2064     alert($('catStrings').getString('staff.cat.marcedit.create_authority_success.label'));
2065 }
2066
2067 function loadMarcEditor(pcrud, marcxml, target, sf) {
2068     /*
2069        To run in Firefox directly, must set signed.applets.codebase_principal_support
2070        to true in about:config
2071      */
2072     netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
2073     win = window.open('/xul/server/cat/marcedit.xul'); // XXX version?
2074
2075     // Match marc2are.pl last_xact_id format, roughly
2076     var now = new Date;
2077     var xact_id = 'IMPORT-' + Date.parse(now);
2078     
2079     win.xulG = {
2080         "record": {"marc": marcxml, "rtype": "are"},
2081         "save": {
2082             "label": $('catStrings').getString('staff.cat.marcedit.save.label'),
2083             "func": function(xmlString) {
2084                 var rec = new are();
2085                 rec.marc(xmlString);
2086                 rec.last_xact_id(xact_id);
2087                 rec.isnew(true);
2088                 pcrud.create(rec, {
2089                     "oncomplete": function (r, objs) {
2090                         var new_rec = objs[0];
2091                         if (!new_rec) {
2092                             return '';
2093                         }
2094
2095                         addNewAuthorityID(new_rec, sf, target);
2096
2097                         win.close();
2098                     }
2099                 });
2100             }
2101         }
2102     };
2103 }
2104
2105