]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/xul/staff_client/server/cat/marcedit.js
Use MARC.FixedFields.setFixedField and validate by startwith search against simple_no...
[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     marc_rec.setFixedField(element.getAttribute('name'), new_value);
819
820     var xml_string = marc_rec.toXmlString();
821     xml_record = new XML( xml_string );
822     if (xml_record..record[0]) xml_record = xml_record..record[0];
823     loadRecord();
824
825     return true;
826 }
827
828 function marcLeader (leader) {
829     var row = createRow(
830         { class : 'marcLeaderRow',
831           tag : 'ldr' },
832         createLabel(
833             { value : 'LDR',
834               class : 'marcTag',
835               tooltiptext : $('catStrings').getString('staff.cat.marcedit.marcTag.LDR.label') } ),
836         createLabel(
837             { value : '',
838               class : 'marcInd1' } ),
839         createLabel(
840             { value : '',
841               class : 'marcInd2' } ),
842         createLabel(
843             { value : leader.text(),
844               class : 'marcLeader' } )
845     );
846
847     return row;
848 }
849
850 function marcControlfield (field) {
851     tagname = field.@tag.toString().substr(2);
852     var row;
853     if (tagname == '1' || tagname == '3' || tagname == '6' || tagname == '7' || tagname == '8') {
854         row = createRow(
855             { class : 'marcControlfieldRow',
856               tag : '_' + tagname },
857             createLabel(
858                 { value : field.@tag,
859                   class : 'marcTag',
860                   context : 'tags_popup',
861                   onmouseover : 'getTooltip(this, "tag");',
862                   tooltipid : 'tag' + field.@tag } ),
863             createLabel(
864                 { value : field.@ind1,
865                   class : 'marcInd1',
866                   onmouseover : 'getTooltip(this, "ind1");',
867                   tooltipid : 'tag' + field.@tag + 'ind1val' + field.@ind1 } ),
868             createLabel(
869                 { value : field.@ind2,
870                   class : 'marcInd2',
871                   onmouseover : 'getTooltip(this, "ind2");',
872                   tooltipid : 'tag' + field.@tag + 'ind2val' + field.@ind2 } ),
873             createMARCTextbox(
874                 field,
875                 { value : field.text(),
876                   class : 'plain marcEditableControlfield',
877                   name : 'CONTROL' + tagname,
878                   context : 'clipboard',
879                   size : 50,
880                   maxlength : 50 } )
881             );
882     } else {
883         row = createRow(
884             { class : 'marcControlfieldRow',
885               tag : '_' + tagname },
886             createLabel(
887                 { value : field.@tag,
888                   class : 'marcTag',
889                   onmouseover : 'getTooltip(this, "tag");',
890                   tooltipid : 'tag' + field.@tag } ),
891             createLabel(
892                 { value : field.@ind1,
893                   class : 'marcInd1',
894                   onmouseover : 'getTooltip(this, "ind1");',
895                   tooltipid : 'tag' + field.@tag + 'ind1val' + field.@ind1 } ),
896             createLabel(
897                 { value : field.@ind2,
898                   class : 'marcInd2',
899                   onmouseover : 'getTooltip(this, "ind2");',
900                   tooltipid : 'tag' + field.@tag + 'ind2val' + field.@ind2 } ),
901             createLabel(
902                 { value : field.text(),
903                   class : 'marcControlfield' } )
904         );
905     }
906
907     return row;
908 }
909
910 function stackSubfields(checkbox) {
911     var list = document.getElementsByAttribute('name','sf_box');
912
913     var o = 'vertical';
914     if (!checkbox.checked) o = 'horizontal';
915     
916     for (var i = 0; i < list.length; i++) {
917         if (list[i]) list[i].setAttribute('orient',o);
918     }
919 }
920
921 function fastItemAdd_toggle(checkbox) {
922     var x = document.getElementById('fastItemAdd_textboxes');
923     if (checkbox.checked) {
924         x.hidden = false;
925         document.getElementById('fastItemAdd_callnumber').focus();
926         document.getElementById('fastItemAdd_callnumber').select();
927     } else {
928         x.hidden = true;
929     }
930 }
931
932 function fastItemAdd_attempt(doc_id) {
933     try {
934         if (typeof window.xulG.fast_add_item != 'function') { return; }
935         if (!document.getElementById('fastItemAdd_checkbox').checked) { return; }
936         if (!document.getElementById('fastItemAdd_callnumber').value) { return; }
937         if (!document.getElementById('fastItemAdd_barcode').value) { return; }
938         window.xulG.fast_add_item( doc_id, document.getElementById('fastItemAdd_callnumber').value, document.getElementById('fastItemAdd_barcode').value );
939         document.getElementById('fastItemAdd_barcode').value = '';
940     } catch(E) {
941         alert('fastItemAdd_attempt: ' + E);
942     }
943 }
944
945 function save_attempt(xml_string) {
946     try {
947         var result = window.xulG.save.func( xml_string );   
948         if (result) {
949             oils_unlock_page();
950             if (result.id) fastItemAdd_attempt(result.id);
951             if (typeof result.on_complete == 'function') result.on_complete();
952         }
953     } catch(E) {
954         alert('save_attempt: ' + E);
955     }
956 }
957
958 function marcDatafield (field) {
959     var row = createRow(
960         { class : 'marcDatafieldRow' },
961         createMARCTextbox(
962             field.@tag,
963             { value : field.@tag,
964               class : 'plain marcTag',
965               name : 'marcTag',
966               context : 'tags_popup',
967               oninput : 'if (this.value.length == 3) { this.nextSibling.focus(); }',
968               size : 3,
969               maxlength : 3,
970               onmouseover : 'current_focus = this; getTooltip(this, "tag");' } ),
971         createMARCTextbox(
972             field.@ind1,
973             { value : field.@ind1,
974               class : 'plain marcInd1',
975               name : 'marcInd1',
976               oninput : 'if (this.value.length == 1) { this.nextSibling.focus(); }',
977               size : 1,
978               maxlength : 1,
979               onmouseover : 'current_focus = this; getContextMenu(this, "ind1"); getTooltip(this, "ind1");',
980               oncontextmenu : 'getContextMenu(this, "ind1");' } ),
981         createMARCTextbox(
982             field.@ind2,
983             { value : field.@ind2,
984               class : 'plain marcInd2',
985               name : 'marcInd2',
986               oninput : 'if (this.value.length == 1) { this.nextSibling.firstChild.firstChild.focus(); }',
987               size : 1,
988               maxlength : 1,
989               onmouseover : 'current_focus = this; getContextMenu(this, "ind2"); getTooltip(this, "ind2");',
990               oncontextmenu : 'getContextMenu(this, "ind2");' } ),
991         createHbox({ name : 'sf_box' })
992     );
993
994     if (!current_focus && field.@tag == '') current_focus = row.childNodes[0];
995     if (!current_focus && field.@ind1 == '') current_focus = row.childNodes[1];
996     if (!current_focus && field.@ind2 == '') current_focus = row.childNodes[2];
997
998     var sf_box = row.lastChild;
999     if (document.getElementById('stackSubfields').checked)
1000         sf_box.setAttribute('orient','vertical');
1001
1002     sf_box.addEventListener(
1003         'click',
1004         function (e) {
1005             if (sf_box === e.target) {
1006                 sf_box.lastChild.lastChild.focus();
1007             } else if (e.target.parentNode === sf_box) {
1008                 e.target.lastChild.focus();
1009             }
1010         },
1011         false
1012     );
1013
1014
1015     for (var i in field.subfield) {
1016         var sf = field.subfield[i];
1017         sf_box.appendChild(
1018             marcSubfield(sf)
1019         );
1020
1021         dojo.query('.marcSubfield', sf_box).forEach(wrap_long_fields);
1022
1023         if (sf.@code == '' && (!current_focus || current_focus.className.match(/Ind/)))
1024             current_focus = sf_box.lastChild.childNodes[1];
1025     }
1026
1027     return row;
1028 }
1029
1030 function marcSubfield (sf) {            
1031     return createHbox(
1032         { class : 'marcSubfieldBox' },
1033         createLabel(
1034             { value : "\u2021",
1035               class : 'plain marcSubfieldDelimiter',
1036               onmouseover : 'getTooltip(this.nextSibling, "subfield");',
1037               oncontextmenu : 'getContextMenu(this.nextSibling, "subfield");',
1038                 //onclick : 'this.nextSibling.focus();',
1039                 onfocus : 'this.nextSibling.focus();',
1040               size : 2 } ),
1041         createMARCTextbox(
1042             sf.@code,
1043             { value : sf.@code,
1044               class : 'plain marcSubfieldCode',
1045               name : 'marcSubfieldCode',
1046               onmouseover : 'current_focus = this; getContextMenu(this, "subfield"); getTooltip(this, "subfield");',
1047               oncontextmenu : 'getContextMenu(this, "subfield");',
1048               oninput : 'if (this.value.length == 1) { this.nextSibling.focus(); }',
1049               size : 2,
1050               maxlength : 1 } ),
1051         createMARCTextbox(
1052             sf,
1053             { value : sf.text(),
1054               name : sf.parent().@tag + ':' + sf.@code,
1055               class : 'plain marcSubfield', 
1056               onmouseover : 'getTooltip(this, "subfield");',
1057               contextmenu : function (event) { getAuthorityContextMenu(event.target, sf) },
1058               size : new String(sf.text()).length + 2,
1059               oninput : "this.setAttribute('size', this.value.length + 2);"
1060             } )
1061     );
1062 }
1063
1064 function loadRecord() {
1065     try {
1066             var grid_rows = document.getElementById('recGrid').lastChild;
1067
1068             while (grid_rows.firstChild) grid_rows.removeChild(grid_rows.firstChild);
1069
1070             grid_rows.appendChild( marcLeader( xml_record.leader ) );
1071
1072             for (var i in xml_record.controlfield) {
1073                 grid_rows.appendChild( marcControlfield( xml_record.controlfield[i] ) );
1074             }
1075
1076             for (var i in xml_record.datafield) {
1077                 grid_rows.appendChild( marcDatafield( xml_record.datafield[i] ) );
1078             }
1079
1080             grid_rows.getElementsByAttribute('class','marcDatafieldRow')[0].firstChild.focus();
1081
1082             var marc_rec = new MARC.Record ({ delimiter : '$', marcxml : xml_record.toXMLString() });
1083             changeFFEditor(marc_rec.recordType());
1084             fillFixedFields();
1085     } catch(E) {
1086         alert('FIXME, MARC Editor, loadRecord: ' + E);
1087     }
1088 }
1089
1090
1091 function genToolTips () {
1092     for (var i in bib_data.field) {
1093         var f = bib_data.field[i];
1094     
1095         tag_menu.appendChild(
1096             createMenuitem(
1097                 { label : f.@tag,
1098                   oncommand : 
1099                       'current_focus.value = "' + f.@tag + '";' +
1100                     'var e = document.createEvent("MutationEvents");' +
1101                     'e.initMutationEvent("change",1,1,null,0,0,0,0);' +
1102                     'current_focus.inputField.dispatchEvent(e);',
1103                   disabled : f.@tag < '010' ? "true" : "false",
1104                   tooltiptext : f.description }
1105             )
1106         );
1107     
1108         var i1_popup = createPopup({position : 'after_start', id : 't' + f.@tag + 'i1' });
1109         context_menus.appendChild( i1_popup );
1110     
1111         var i2_popup = createPopup({position : 'after_start', id : 't' + f.@tag + 'i2' });
1112         context_menus.appendChild( i2_popup );
1113     
1114         var sf_popup = createPopup({position : 'after_start', id : 't' + f.@tag + 'sf' });
1115         context_menus.appendChild( sf_popup );
1116     
1117         tooltip_hash['tag' + f.@tag] = f.description;
1118         for (var j in f.indicator) {
1119             var ind = f.indicator[j];
1120             tooltip_hash['tag' + f.@tag + 'ind' + ind.@position + 'val' + ind.@value] = ind.description;
1121     
1122             if (ind.@position == 1) {
1123                 i1_popup.appendChild(
1124                     createMenuitem(
1125                         { label : ind.@value,
1126                           oncommand : 
1127                               'current_focus.value = "' + ind.@value + '";' +
1128                             'var e = document.createEvent("MutationEvents");' +
1129                             'e.initMutationEvent("change",1,1,null,0,0,0,0);' +
1130                             'current_focus.inputField.dispatchEvent(e);',
1131                           tooltiptext : ind.description }
1132                     )
1133                 );
1134             }
1135     
1136             if (ind.@position == 2) {
1137                 i2_popup.appendChild(
1138                     createMenuitem(
1139                         { label : ind.@value,
1140                           oncommand : 
1141                               'current_focus.value = "' + ind.@value + '";' +
1142                             'var e = document.createEvent("MutationEvents");' +
1143                             'e.initMutationEvent("change",1,1,null,0,0,0,0);' +
1144                             'current_focus.inputField.dispatchEvent(e);',
1145                           tooltiptext : ind.description }
1146                     )
1147                 );
1148             }
1149         }
1150     
1151         for (var j in f.subfield) {
1152             var sf = f.subfield[j];
1153             tooltip_hash['tag' + f.@tag + 'sf' + sf.@code] = sf.description;
1154     
1155             sf_popup.appendChild(
1156                 createMenuitem(
1157                     { label : sf.@code,
1158                       oncommand : 
1159                           'current_focus.value = "' + sf.@code + '";' +
1160                         'var e = document.createEvent("MutationEvents");' +
1161                         'e.initMutationEvent("change",1,1,null,0,0,0,0);' +
1162                         'current_focus.inputField.dispatchEvent(e);',
1163                       tooltiptext : sf.description
1164                     }
1165                 )
1166             );
1167         }
1168     }
1169 }
1170
1171 function getTooltip (target, type) {
1172
1173     var tt = '';
1174     if (type == 'subfield')
1175         tt = 'tag' + target.parentNode.parentNode.parentNode.firstChild.value + 'sf' + target.parentNode.childNodes[1].value;
1176
1177     if (type == 'ind1')
1178         tt = 'tag' + target.parentNode.firstChild.value + 'ind1val' + target.value;
1179
1180     if (type == 'ind2')
1181         tt = 'tag' + target.parentNode.firstChild.value + 'ind2val' + target.value;
1182
1183     if (type == 'tag')
1184         tt = 'tag' + target.parentNode.firstChild.value;
1185
1186     if (!document.getElementById( tt )) {
1187         p.appendChild(
1188             createTooltip(
1189                 { id : tt,
1190                   flex : "1",
1191                   orient : 'vertical',
1192                   onpopupshown : 'this.width = this.firstChild.boxObject.width + 10; this.height = this.firstChild.boxObject.height + 10;',
1193                   class : 'tooltip' },
1194                 createDescription({}, document.createTextNode( tooltip_hash[tt] ) )
1195             )
1196         );
1197     }
1198
1199     target.tooltip = tt;
1200     return true;
1201 }
1202
1203 function getContextMenu (target, type) {
1204
1205     var tt = '';
1206     if (type == 'subfield')
1207         tt = 't' + target.parentNode.parentNode.parentNode.firstChild.value + 'sf';
1208
1209     if (type == 'ind1')
1210         tt = 't' + target.parentNode.firstChild.value + 'i1';
1211
1212     if (type == 'ind2')
1213         tt = 't' + target.parentNode.firstChild.value + 'i2';
1214
1215     target.setAttribute('context', tt);
1216     return true;
1217 }
1218
1219 var control_map = {
1220     100 : {
1221         'a' : { 100 : 'a' },
1222         'd' : { 100 : 'd' },
1223         'e' : { 100 : 'e' },
1224         'q' : { 100 : 'q' }
1225     },
1226     110 : {
1227         'a' : { 110 : 'a' },
1228         'd' : { 110 : 'd' }
1229     },
1230     111 : {
1231         'a' : { 111 : 'a' },
1232         'd' : { 111 : 'd' }
1233     },
1234     130 : {
1235         'a' : { 130 : 'a' },
1236         'd' : { 130 : 'd' }
1237     },
1238     240 : {
1239         'a' : { 130 : 'a' },
1240         'd' : { 130 : 'd' }
1241     },
1242     400 : {
1243         'a' : { 100 : 'a' },
1244         'd' : { 100 : 'd' }
1245     },
1246     410 : {
1247         'a' : { 110 : 'a' },
1248         'd' : { 110 : 'd' }
1249     },
1250     411 : {
1251         'a' : { 111 : 'a' },
1252         'd' : { 111 : 'd' }
1253     },
1254     440 : {
1255         'a' : { 130 : 'a' },
1256         'n' : { 130 : 'n' },
1257         'p' : { 130 : 'p' }
1258     },
1259     700 : {
1260         'a' : { 100 : 'a' },
1261         'd' : { 100 : 'd' },
1262         'q' : { 100 : 'q' },
1263         't' : { 100 : 't' }
1264     },
1265     710 : {
1266         'a' : { 110 : 'a' },
1267         'd' : { 110 : 'd' }
1268     },
1269     711 : {
1270         'a' : { 111 : 'a' },
1271         'c' : { 111 : 'c' },
1272         'd' : { 111 : 'd' }
1273     },
1274     730 : {
1275         'a' : { 130 : 'a' },
1276         'd' : { 130 : 'd' }
1277     },
1278     800 : {
1279         'a' : { 100 : 'a' },
1280         'd' : { 100 : 'd' }
1281     },
1282     810 : {
1283         'a' : { 110 : 'a' },
1284         'd' : { 110 : 'd' }
1285     },
1286     811 : {
1287         'a' : { 111 : 'a' },
1288         'd' : { 111 : 'd' }
1289     },
1290     830 : {
1291         'a' : { 130 : 'a' },
1292         'd' : { 130 : 'd' }
1293     },
1294     600 : {
1295         'a' : { 100 : 'a' },
1296         'd' : { 100 : 'd' },
1297         'q' : { 100 : 'q' },
1298         't' : { 100 : 't' },
1299         'v' : { 180 : 'v',
1300             100 : 'v',
1301             181 : 'v',
1302             182 : 'v',
1303             185 : 'v'
1304         },
1305         'x' : { 180 : 'x',
1306             100 : 'x',
1307             181 : 'x',
1308             182 : 'x',
1309             185 : 'x'
1310         },
1311         'y' : { 180 : 'y',
1312             100 : 'y',
1313             181 : 'y',
1314             182 : 'y',
1315             185 : 'y'
1316         },
1317         'z' : { 180 : 'z',
1318             100 : 'z',
1319             181 : 'z',
1320             182 : 'z',
1321             185 : 'z'
1322         }
1323     },
1324     610 : {
1325         'a' : { 110 : 'a' },
1326         'd' : { 110 : 'd' },
1327         't' : { 110 : 't' },
1328         'v' : { 180 : 'v',
1329             110 : 'v',
1330             181 : 'v',
1331             182 : 'v',
1332             185 : 'v'
1333         },
1334         'x' : { 180 : 'x',
1335             110 : 'x',
1336             181 : 'x',
1337             182 : 'x',
1338             185 : 'x'
1339         },
1340         'y' : { 180 : 'y',
1341             110 : 'y',
1342             181 : 'y',
1343             182 : 'y',
1344             185 : 'y'
1345         },
1346         'z' : { 180 : 'z',
1347             110 : 'z',
1348             181 : 'z',
1349             182 : 'z',
1350             185 : 'z'
1351         }
1352     },
1353     611 : {
1354         'a' : { 111 : 'a' },
1355         'd' : { 111 : 'd' },
1356         't' : { 111 : 't' },
1357         'v' : { 180 : 'v',
1358             111 : 'v',
1359             181 : 'v',
1360             182 : 'v',
1361             185 : 'v'
1362         },
1363         'x' : { 180 : 'x',
1364             111 : 'x',
1365             181 : 'x',
1366             182 : 'x',
1367             185 : 'x'
1368         },
1369         'y' : { 180 : 'y',
1370             111 : 'y',
1371             181 : 'y',
1372             182 : 'y',
1373             185 : 'y'
1374         },
1375         'z' : { 180 : 'z',
1376             111 : 'z',
1377             181 : 'z',
1378             182 : 'z',
1379             185 : 'z'
1380         }
1381     },
1382     630 : {
1383         'a' : { 130 : 'a' },
1384         'd' : { 130 : 'd' }
1385     },
1386     648 : {
1387         'a' : { 148 : 'a' },
1388         'v' : { 148 : 'v' },
1389         'x' : { 148 : 'x' },
1390         'y' : { 148 : 'y' },
1391         'z' : { 148 : 'z' }
1392     },
1393     650 : {
1394         'a' : { 150 : 'a' },
1395         'b' : { 150 : 'b' },
1396         'v' : { 180 : 'v',
1397             150 : 'v',
1398             181 : 'v',
1399             182 : 'v',
1400             185 : 'v'
1401         },
1402         'x' : { 180 : 'x',
1403             150 : 'x',
1404             181 : 'x',
1405             182 : 'x',
1406             185 : 'x'
1407         },
1408         'y' : { 180 : 'y',
1409             150 : 'y',
1410             181 : 'y',
1411             182 : 'y',
1412             185 : 'y'
1413         },
1414         'z' : { 180 : 'z',
1415             150 : 'z',
1416             181 : 'z',
1417             182 : 'z',
1418             185 : 'z'
1419         }
1420     },
1421     651 : {
1422         'a' : { 151 : 'a' },
1423         'v' : { 180 : 'v',
1424             151 : 'v',
1425             181 : 'v',
1426             182 : 'v',
1427             185 : 'v'
1428         },
1429         'x' : { 180 : 'x',
1430             151 : 'x',
1431             181 : 'x',
1432             182 : 'x',
1433             185 : 'x'
1434         },
1435         'y' : { 180 : 'y',
1436             151 : 'y',
1437             181 : 'y',
1438             182 : 'y',
1439             185 : 'y'
1440         },
1441         'z' : { 180 : 'z',
1442             151 : 'z',
1443             181 : 'z',
1444             182 : 'z',
1445             185 : 'z'
1446         }
1447     },
1448     655 : {
1449         'a' : { 155 : 'a' },
1450         'v' : { 180 : 'v',
1451             155 : 'v',
1452             181 : 'v',
1453             182 : 'v',
1454             185 : 'v'
1455         },
1456         'x' : { 180 : 'x',
1457             155 : 'x',
1458             181 : 'x',
1459             182 : 'x',
1460             185 : 'x'
1461         },
1462         'y' : { 180 : 'y',
1463             155 : 'y',
1464             181 : 'y',
1465             182 : 'y',
1466             185 : 'y'
1467         },
1468         'z' : { 180 : 'z',
1469             155 : 'z',
1470             181 : 'z',
1471             182 : 'z',
1472             185 : 'z'
1473         }
1474     }
1475 };
1476
1477 function getAuthorityContextMenu (target, sf) {
1478     var menu_id = sf.parent().@tag + ':' + sf.@code + '-authority-context-' + sf;
1479
1480     var page = 0;
1481     var old = dojo.byId( menu_id );
1482     if (old) {
1483         page = auth_pages[menu_id];
1484         old.parentNode.removeChild(old);
1485     } else {
1486         auth_pages[menu_id] = 0;
1487     }
1488
1489     var sf_popup = createPopup({ id : menu_id, flex : 1 });
1490
1491     sf_popup.addEventListener("popuphiding", function(event) {
1492         if (show_auth_menu) {
1493             show_auth_menu = false;
1494             getAuthorityContextMenu(target, sf);
1495             dojo.byId(menu_id).openPopup();
1496         }  
1497     }, false);
1498
1499     context_menus.appendChild( sf_popup );
1500
1501     var found_acs = [];
1502     dojo.forEach( acs.controlSetList(), function (acs_id) {
1503         if (ac.controlSet(acs_id).control_map[sf.parent().@tag]) found_acs.push(acs_id);
1504     });
1505
1506     if (!found_acs.length) {
1507         sf_popup.appendChild(createLabel( { value : $('catStrings').getString('staff.cat.marcedit.not_authority_field.label') } ) );
1508         target.setAttribute('context', 'clipboard');
1509         return false;
1510     }
1511
1512     if (sf.toString().replace(/\s*/, '')) {
1513         browseAuthority(sf_popup, menu_id, target, sf, 20, page);
1514     }
1515
1516     return true;
1517 }
1518
1519 /* Apply the complete 1xx */
1520 function applyFullAuthority ( target, ui_sf, e4x_sf ) {
1521     var new_vals = dojo.query('*[tag^="1"]', target);
1522     return applyAuthority( target, ui_sf, e4x_sf, new_vals );
1523 }
1524
1525 function applySelectedAuthority ( target, ui_sf, e4x_sf ) {
1526     var new_vals = target.getElementsByAttribute('checked','true');
1527     return applyAuthority( target, ui_sf, e4x_sf, new_vals );
1528 }
1529
1530 function applyAuthority ( target, ui_sf, e4x_sf, new_vals ) {
1531     var field = e4x_sf.parent();
1532
1533     for (var i = 0; i < new_vals.length; i++) {
1534
1535         var sf_list = field.subfield;
1536         for (var j in sf_list) {
1537
1538             if (sf_list[j].@code == new_vals[i].getAttribute('subfield')) {
1539                 sf_list[j] = new_vals[i].getAttribute('value');
1540                 new_vals[i].setAttribute('subfield','');
1541                 break;
1542             }
1543         }
1544     }
1545
1546     for (var i = 0; i < new_vals.length; i++) {
1547         if (!new_vals[i].getAttribute('subfield')) continue;
1548
1549         var val = new_vals[i].getAttribute('value');
1550
1551         var sf = <subfield code="" xmlns="http://www.loc.gov/MARC21/slim">{val}</subfield>;
1552         sf.@code = new_vals[i].getAttribute('subfield');
1553
1554         field.insertChildAfter(field.subfield[field.subfield.length() - 1], sf);
1555     }
1556
1557     var row = marcDatafield( field );
1558
1559     var node = ui_sf;
1560     while (node.nodeName != 'row') {
1561         node = node.parentNode;
1562     }
1563
1564     node.parentNode.replaceChild( row, node );
1565     return true;
1566 }
1567
1568 function validateAuthority (button) {
1569     var grid = document.getElementById('recGrid');
1570     var label = button.getAttribute('label');
1571
1572     //loop over rows
1573     var rows = grid.lastChild.childNodes;
1574     for (var i = 0; i < rows.length; i++) {
1575         var row = rows[i];
1576         var tag = row.firstChild;
1577
1578         for (var acs_id in acs.controlSetList()) {
1579             var control_map = acs.controlSet(acs_id).control_map;
1580     
1581             if (!control_map[tag.value]) continue
1582             button.setAttribute('label', label + ' - ' + tag.value);
1583     
1584             var ind1 = tag.nextSibling;
1585             var ind2 = ind1.nextSibling;
1586             var subfields = ind2.nextSibling.childNodes;
1587     
1588             var sf_list = [];
1589             for (var j = 0; j < subfields.length; j++) {
1590                 var sf = subfields[j];
1591                 sf_list.push( sf.childNodes[1].value );
1592                 sf_list.push( sf.childNodes[2].value );
1593             }
1594
1595             var matches = acs.findMatchingAuthorities(
1596                 new MARC.Field({
1597                     'tag'       : tag.value,
1598                     'subfields' : sf_list
1599                 })
1600             );
1601     
1602             // XXX If adt, etc should be validated separately from vxz, etc then move this up into the above for loop
1603             for (var j = 0; j < subfields.length; j++) {
1604                 var sf = subfields[j];
1605                 if (!matches.length) {
1606                     dojo.removeClass(sf.childNodes[2], 'marcValidated');
1607                     dojo.addClass(sf.childNodes[2], 'marcUnvalidated');
1608                 } else {
1609                     dojo.removeClass(sf.childNodes[2], 'marcUnvalidated');
1610                     dojo.addClass(sf.childNodes[2], 'marcValidated');
1611                 }
1612             }
1613
1614             if (matches.length) break;
1615         }
1616     }
1617
1618     button.setAttribute('label', label);
1619
1620     return true;
1621 }
1622
1623
1624 /*
1625 function validateBibField (tags, searches) {
1626     var url = "/gateway?input_format=json&format=xml&service=open-ils.search&method=open-ils.search.authority.validate.tag";
1627     url += '&param="tags"&param=' + js2JSON(tags);
1628     url += '&param="searches"&param=' + js2JSON(searches);
1629
1630
1631     var req = new XMLHttpRequest();
1632     req.open('GET',url,false);
1633     req.send(null);
1634
1635     return req;
1636
1637 }
1638 */
1639
1640 function searchAuthority (term, tag, sf, limit) {
1641     var url = "/gateway?input_format=json&format=xml&service=open-ils.search&method=open-ils.search.authority.fts";
1642     url += '&param="term"&param="' + term + '"';
1643     url += '&param="limit"&param=' + limit;
1644     url += '&param="tag"&param=' + tag;
1645     url += '&param="subfield"&param="' + sf + '"';
1646
1647
1648     var req = new XMLHttpRequest();
1649     req.open('GET',url,false);
1650     req.send(null);
1651
1652     return req;
1653
1654 }
1655
1656 /* TODO new authority browse support for context sets, and use that here */
1657 function browseAuthority (sf_popup, menu_id, target, sf, limit, page) {
1658     dojo.require('dojox.xml.parser');
1659
1660     // map tag + subfield to the appropriate authority browse axis:
1661     // currently authority.author, authority.subject, authority.title, authority.topic
1662     // based on mappings in OpenILS::Application::SuperCat
1663
1664     var type;
1665
1666     // Map based on replacing the first char of the selected tag with '1'
1667     switch ('1' + (sf.parent().@tag.toString()).substring(1)) {
1668         case "130":
1669             type = 'authority.title';
1670             break;
1671
1672         case "100":
1673         case "110":
1674         case "111":
1675             type = 'authority.author';
1676             break;
1677
1678         case "150":
1679             type = 'authority.topic';
1680             break;
1681
1682         case "148":
1683         case "151":
1684         case "155":
1685             type = 'authority.subject';
1686             break;
1687
1688         // No matching tag means no authorities to search - shortcut
1689         default:
1690             return;
1691     }
1692
1693     if (!limit) {
1694         limit = 10;
1695     }
1696
1697     if (!page) {
1698         page = 0;
1699     }
1700
1701     var url = '/opac/extras/browse/marcxml/'
1702         + type + '.refs'
1703         + '/1' // OU - currently unscoped
1704         + '/' + sf.toString()
1705         + '/' + page
1706         + '/' + limit
1707     ;
1708
1709     // would be good to carve this out into a separate function
1710     dojo.xhrGet({"url":url, "sync": true, "preventCache": true, "handleAs":"xml", "load": function(records) {
1711         var create_menu = createMenu({ label: $('catStrings').getString('staff.cat.marcedit.create_authority.label')});
1712
1713         var cm_popup = create_menu.appendChild(
1714             createMenuPopup()
1715         );
1716
1717         cm_popup.appendChild(
1718             createMenuitem({ label : $('catStrings').getString('staff.cat.marcedit.create_authority_now.label'),
1719                 command : function() { 
1720                     // Call middle-layer function to create and save the new authority
1721                     var source_f = summarizeField(sf);
1722                     var new_auth = fieldmapper.standardRequest(
1723                         ["open-ils.cat", "open-ils.cat.authority.record.create_from_bib"],
1724                         [source_f, xulG.marc_control_number_identifier, ses()]
1725                     );
1726                     if (new_auth && new_auth.id()) {
1727                         addNewAuthorityID(new_auth, sf, target);
1728                     }
1729                 }
1730             })
1731         );
1732
1733         cm_popup.appendChild(
1734             createMenuitem({ label : $('catStrings').getString('staff.cat.marcedit.create_authority_edit.label'),
1735                 command : function() { 
1736                     // Generate the new authority by calling the new middle-layer
1737                     // function (a non-saving variant), then display in another
1738                     // MARC editor
1739                     var source_f = summarizeField(sf);
1740                     var authtoken = ses();
1741                     dojo.require('openils.PermaCrud');
1742                     var pcrud = new openils.PermaCrud({"authtoken": authtoken});
1743                     var rec = fieldmapper.standardRequest(
1744                         ["open-ils.cat", "open-ils.cat.authority.record.create_from_bib.readonly"],
1745                         { "params": [source_f, xulG.marc_control_number_identifier] }
1746                     );
1747                     loadMarcEditor(pcrud, rec, target, sf);
1748                 }
1749             })
1750         );
1751
1752         sf_popup.appendChild(create_menu);
1753         sf_popup.appendChild( createComplexXULElement( 'menuseparator' ) );
1754
1755         // append "Previous page" results browser
1756         sf_popup.appendChild(
1757             createMenuitem({ label : $('catStrings').getString('staff.cat.marcedit.previous_page.label'),
1758                 command : function(event) { 
1759                     auth_pages[menu_id] -= 1;
1760                     show_auth_menu = true;
1761                 }
1762             })
1763         );
1764         sf_popup.appendChild( createComplexXULElement( 'menuseparator' ) );
1765
1766         dojo.query('record', records).forEach(function(record) {
1767             var main_text = '';
1768             var see_from = [];
1769             var see_also = [];
1770             var auth_id = dojox.xml.parser.textContent(dojo.query('datafield[tag="901"] subfield[code="c"]', record)[0]);
1771             var auth_org = dojox.xml.parser.textContent(dojo.query('controlfield[tag="003"]', record)[0]);
1772
1773             // Grab the fields with tags beginning with 1 (main entries) and iterate through the subfields
1774             dojo.query('datafield[tag^="1"]', record).forEach(function(field) {
1775                 dojo.query('subfield', field).forEach(function(subfield) {
1776                     if (main_text) {
1777                         main_text += ' / ';
1778                     }
1779                     main_text += dojox.xml.parser.textContent(subfield);
1780                 });
1781             });
1782
1783             // Grab the fields with tags beginning with 4 (see from entries) and iterate through the subfields
1784             dojo.query('datafield[tag^="4"]', record).forEach(function(field) {
1785                 var see_text = '';
1786                 dojo.query('subfield', field).forEach(function(subfield) {
1787                     if (see_text) {
1788                         see_text += ' / ';
1789                     }
1790                     see_text += dojox.xml.parser.textContent(subfield);
1791                 });
1792                 see_from.push($('catStrings').getFormattedString('staff.cat.marcedit.authority_see_from', [see_text]));
1793             });
1794
1795             // Grab the fields with tags beginning with 5 (see also entries) and iterate through the subfields
1796             dojo.query('datafield[tag^="5"]', record).forEach(function(field) {
1797                 var see_text = '';
1798                 dojo.query('subfield', field).forEach(function(subfield) {
1799                     if (see_text) {
1800                         see_text += ' / ';
1801                     }
1802                     see_text += dojox.xml.parser.textContent(subfield);
1803                 });
1804                 see_also.push($('catStrings').getFormattedString('staff.cat.marcedit.authority_see_also', [see_text]));
1805             });
1806
1807             buildAuthorityPopup(main_text, record, auth_org, auth_id, sf_popup, target, sf);
1808
1809             dojo.forEach(see_from, function(entry_text) {
1810                 buildAuthorityPopup(entry_text, record, auth_org, auth_id, sf_popup, target, sf, "font-style: italic; margin-left: 2em;");
1811             });
1812
1813             // To-do: instead of launching the standard selector menu, invoke
1814             // a new authority search using the 5XX entry text
1815             dojo.forEach(see_also, function(entry_text) {
1816                 buildAuthorityPopup(entry_text, record, auth_org, auth_id, sf_popup, target, sf, "font-style: italic; margin-left: 2em;");
1817             });
1818
1819         });
1820
1821         if (sf_popup.childNodes.length == 0) {
1822             sf_popup.appendChild(createLabel( { value : $('catStrings').getString('staff.cat.marcedit.no_authority_match.label') } ) );
1823         } else {
1824             // append "Next page" results browser
1825             sf_popup.appendChild( createComplexXULElement( 'menuseparator' ) );
1826             sf_popup.appendChild(
1827                 createMenuitem({ label : $('catStrings').getString('staff.cat.marcedit.next_page.label'),
1828                     command : function(event) { 
1829                         auth_pages[menu_id] += 1;
1830                         show_auth_menu = true;
1831                     }
1832                 })
1833             );
1834         }
1835
1836         target.setAttribute('context', menu_id);
1837         return true;
1838     }});
1839
1840 }
1841
1842 function buildAuthorityPopup (entry_text, record, auth_org, auth_id, sf_popup, target, sf, style) {
1843     var grid = dojo.query('[name="authority-marc-template"]')[0].cloneNode(true);
1844     grid.setAttribute('name','-none-');
1845     grid.setAttribute('style','overflow:scroll');
1846
1847     var submenu = createMenu( { "label": entry_text } );
1848
1849     var popup = createMenuPopup({ "flex": "1" });
1850     if (style) {
1851         submenu.setAttribute('style', style);
1852         popup.setAttribute('style', 'font-style: normal; margin-left: 0em;');
1853     }
1854     submenu.appendChild(popup);
1855
1856     dojo.query('datafield[tag^="1"], datafield[tag^="4"], datafield[tag^="5"]', record).forEach(function(field) {
1857         buildAuthorityPopupSelector(field, grid, auth_org, auth_id);
1858     });
1859
1860     grid.hidden = false;
1861     popup.appendChild( grid );
1862
1863     popup.appendChild(
1864         createMenuitem(
1865             { label : $('catStrings').getString('staff.cat.marcedit.apply_selected.label'),
1866               command : function (event) {
1867                     applySelectedAuthority(event.target.previousSibling, target, sf);
1868                     return true;
1869               }
1870             }
1871         )
1872     );
1873
1874     popup.appendChild( createComplexXULElement( 'menuseparator' ) );
1875
1876     popup.appendChild(
1877         createMenuitem(
1878             { label : $('catStrings').getString('staff.cat.marcedit.apply_full.label'),
1879               command : function (event) {
1880                     applyFullAuthority(event.target.previousSibling.previousSibling.previousSibling, target, sf);
1881                     return true;
1882               }
1883             }
1884         )
1885     );
1886
1887     sf_popup.appendChild( submenu );
1888 }
1889
1890 function buildAuthorityPopupSelector (field, grid, auth_org, auth_id) {
1891     var row = createRow(
1892         { },
1893         createLabel( { "value" : dojo.attr(field, 'tag') } ),
1894         createLabel( { "value" : dojo.attr(field, 'ind1') } ),
1895         createLabel( { "value" : dojo.attr(field, 'ind2') } )
1896     );
1897
1898     var sf_box = createHbox();
1899     dojo.query('subfield', field).forEach(function(subfield) {
1900         sf_box.appendChild(
1901             createCheckbox(
1902                 { "label"    : '\u2021' + dojo.attr(subfield, 'code') + ' ' + dojox.xml.parser.textContent(subfield),
1903                   "subfield" : dojo.attr(subfield, 'code'),
1904                   "tag"      : dojo.attr(field, 'tag'),
1905                   "value"    : dojox.xml.parser.textContent(subfield)
1906                 }
1907             )
1908         );
1909         row.appendChild(sf_box);
1910     });
1911
1912     // Append the authority linking subfield only for main entries
1913     if (dojo.attr(field, 'tag').charAt(0) == '1') {
1914         sf_box.appendChild(
1915             createCheckbox(
1916                 { "label"    : '\u2021' + '0' + ' (' + auth_org + ')' + auth_id,
1917                   "subfield" : '0',
1918                   "tag"      : dojo.attr(field, 'tag'),
1919                   "value"    : '(' + auth_org + ')' + auth_id
1920                 }
1921             )
1922         );
1923     }
1924     row.appendChild(sf_box);
1925
1926     grid.lastChild.appendChild(row);
1927 }
1928
1929 function summarizeField(sf) {
1930     var source_f= {
1931         "tag": '',
1932         "ind1": '',
1933         "ind2": '',
1934         "subfields": []
1935     };
1936
1937     source_f.tag = sf.parent().@tag.toString();
1938     source_f.ind1 = sf.parent().@ind1.toString();
1939     source_f.ind1 = sf.parent().@ind2.toString();
1940
1941     for (var i = 0; i < sf.parent().subfield.length(); i++) {
1942         var sf_iter = sf.parent().subfield[i];
1943
1944         /* Filter out subfields that are not controlled for this tag */
1945         if (!control_map[source_f.tag][sf_iter.@code.toString()]) {
1946             continue;
1947         }
1948
1949         source_f.subfields.push([sf_iter.@code.toString(), sf_iter.toString()]);
1950     }
1951
1952     return source_f;
1953 }
1954
1955 function buildBibSourceList (authtoken, recId) {
1956     /* TODO: Work out how to set the bib source of the bre that does not yet
1957      * exist - this is specifically in the case of Z39.50 imports. Right now
1958      * we just avoid populating and showing the config.bib_source list
1959      */
1960     if (!recId) {
1961         return false;
1962     }
1963
1964     var bib = xulG.record.bre;
1965
1966     dojo.require('openils.PermaCrud');
1967
1968     // cbsList = the XUL menulist that contains the available bib sources 
1969     var cbsList = dojo.byId('bib-source-list');
1970
1971     // bibSources = an array containing all of the bib source objects
1972     var bibSources = new openils.PermaCrud({"authtoken": authtoken}).retrieveAll('cbs');
1973
1974     // A tad ugly, but gives us the index of the bib source ID in cbsList
1975     var x = 0;
1976     var cbsListArr = [];
1977     dojo.forEach(bibSources, function (item) {
1978         cbsList.appendItem(item.source(), item.id());
1979         cbsListArr[item.id()] = x;
1980         x++;
1981     });
1982
1983     // Show the current value of the bib source for this record
1984     cbsList.selectedIndex = cbsListArr[bib.source()];
1985
1986     // Display the bib source selection widget
1987     dojo.byId('bib-source-list-caption').hidden = false;
1988     dojo.byId('bib-source-list').hidden = false;
1989     dojo.byId('bib-source-list-button').disabled = true;
1990     dojo.byId('bib-source-list-button').hidden = false;
1991 }
1992
1993 // Fired when the "Update Source" button is clicked
1994 // Updates the value of the bib source for the current record
1995 function updateBibSource() {
1996     var authtoken = ses();
1997     var cbs = dojo.byId('bib-source-list').selectedItem.value;
1998     var recId = xulG.record.id;
1999     var pcrud = new openils.PermaCrud({"authtoken": authtoken});
2000     var bib = pcrud.retrieve('bre', recId);
2001     if (bib.source() != cbs) {
2002         bib.source(cbs);
2003         bib.ischanged = true;
2004         pcrud.update(bib);
2005     }
2006 }
2007
2008 function onBibSourceSelect() {
2009     var cbs = dojo.byId('bib-source-list').selectedItem.value;
2010     var bib = xulG.record.bre;
2011     if (bib.source() != cbs) {
2012         dojo.byId('bib-source-list-button').disabled = false;   
2013     } else {
2014         dojo.byId('bib-source-list-button').disabled = true;   
2015     }
2016 }
2017
2018 function addNewAuthorityID(authority, sf, target) {
2019     var id_sf = <subfield code="0" xmlns="http://www.loc.gov/MARC21/slim">({xulG.marc_control_number_identifier}){authority.id()}</subfield>;
2020     sf.parent().appendChild(id_sf);
2021     var new_sf = marcSubfield(id_sf);
2022
2023     var node = target;
2024     while (dojo.attr(node, 'name') != 'sf_box') {
2025         node = node.parentNode;
2026     }
2027     node.appendChild( new_sf );
2028
2029     alert($('catStrings').getString('staff.cat.marcedit.create_authority_success.label'));
2030 }
2031
2032 function loadMarcEditor(pcrud, marcxml, target, sf) {
2033     /*
2034        To run in Firefox directly, must set signed.applets.codebase_principal_support
2035        to true in about:config
2036      */
2037     netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
2038     win = window.open('/xul/server/cat/marcedit.xul'); // XXX version?
2039
2040     // Match marc2are.pl last_xact_id format, roughly
2041     var now = new Date;
2042     var xact_id = 'IMPORT-' + Date.parse(now);
2043     
2044     win.xulG = {
2045         "record": {"marc": marcxml, "rtype": "are"},
2046         "save": {
2047             "label": $('catStrings').getString('staff.cat.marcedit.save.label'),
2048             "func": function(xmlString) {
2049                 var rec = new are();
2050                 rec.marc(xmlString);
2051                 rec.last_xact_id(xact_id);
2052                 rec.isnew(true);
2053                 pcrud.create(rec, {
2054                     "oncomplete": function (r, objs) {
2055                         var new_rec = objs[0];
2056                         if (!new_rec) {
2057                             return '';
2058                         }
2059
2060                         addNewAuthorityID(new_rec, sf, target);
2061
2062                         win.close();
2063                     }
2064                 });
2065             }
2066         }
2067     };
2068 }
2069
2070