1 <script src="[% interface %]/lib/codemirror/codemirror-compressed.js"></script>
2 <script src="[% interface %]/lib/filesaver.js"></script>
3 <script src="[% interface %]/lib/koha/cateditor/marc-mode.js"></script>
4 <script src="[% interface %]/lib/require.js"></script>
7 baseUrl: '[% interface %]/lib/koha/cateditor/',
10 marcflavour: '[% marcflavour %]',
11 themelang: '[% themelang %]',
18 [% IF marcflavour == 'MARC21' %]
19 [% PROCESS 'cateditor-widgets-marc21.inc' %]
21 <script>var editorWidgets = {};</script>
25 require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'preferences', 'resources', 'text-marc', 'widget' ], function( KohaBackend, Search, Macros, MARCEditor, MARC, Preferences, Resources, TextMARC, Widget ) {
27 'koha:biblioserver': {
28 name: _("Local catalog"),
32 [%- FOREACH server = z3950_servers -%]
34 name: '[% server.servername %]',
35 recordtype: '[% server.recordtype %]',
36 checked: [% server.checked ? 'true' : 'false' %],
41 // The columns that should show up in a search, in order, and keyed by the corresponding <metadata> tag in the XSL and Pazpar2 config
43 [ "local_number", _("Local number") ],
44 [ "title", _("Title") ],
45 [ "series", _("Series title") ],
46 [ "author", _("Author") ],
47 [ "lccn", _("LCCN") ],
48 [ "isbn", _("ISBN") ],
49 [ "issn", _("ISSN") ],
50 [ "medium", _("Medium") ],
51 [ "edition", _("Edition") ],
52 [ "notes", _("Notes") ],
57 saveBackend: 'catalog',
64 function makeAuthorisedValueWidgets( frameworkCode ) {
65 $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
66 $.each( tagInfo.subfields, function( subfield, subfieldInfo ) {
67 if ( !subfieldInfo.authorised_value ) return;
68 var authvals = KohaBackend.GetAuthorisedValues( subfieldInfo.authorised_value );
69 if ( !authvals ) return;
71 var defaultvalue = subfield.defaultvalue || authvals[0].value;
73 Widget.Register( tag + subfield, {
75 var $result = $( '<span class="subfield-widget"></span>' );
79 postCreate: function() {
80 var value = defaultvalue;
83 $.each( authvals, function() {
84 if ( this.value == widget.text ) {
89 this.setText( value );
91 $( '<select></select>' ).appendTo( this.node );
92 var $node = $( this.node ).find( 'select' );
93 $.each( authvals, function( undef, authval ) {
94 $node.append( '<option value="' + authval.value + '"' + (authval.value == value ? ' selected="selected"' : '') + '>' + authval.lib + '</option>' );
96 $node.val( this.text );
98 $node.change( $.proxy( function() {
99 this.setText( $node.val() );
102 makeTemplate: function() {
110 function bindGlobalKeys() {
111 shortcut.add( 'ctrl+s', function(event) {
112 $( '#save-record' ).click();
114 event.preventDefault();
117 shortcut.add( 'alt+ctrl+k', function(event) {
118 $( '#search-by-keywords' ).focus();
123 shortcut.add( 'alt+ctrl+a', function(event) {
124 $( '#search-by-author' ).focus();
129 shortcut.add( 'alt+ctrl+i', function(event) {
130 $( '#search-by-isbn' ).focus();
135 shortcut.add( 'alt+ctrl+t', function(event) {
136 $( '#search-by-title' ).focus();
141 shortcut.add( 'ctrl+h', function() {
142 var field = editor.getCurrentField();
144 if ( !field ) return;
146 window.open( getFieldHelpURL( field.tag ) );
149 $('#quicksearch .search-box').each( function() {
150 shortcut.add( 'enter', $.proxy( function() {
153 $('#quicksearch .search-box').each( function() {
154 if ( !this.value ) return;
156 terms.push( [ $(this).data('qualifier'), this.value ] );
159 if ( !terms.length ) return;
161 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
162 $("#search-overlay").show();
167 }, this), { target: this, type: 'keypress' } );
171 function getFieldHelpURL( tag ) {
172 [% IF ( marcflavour == 'MARC21' ) %]
173 if ( tag == '000' ) {
174 return "http://www.loc.gov/marc/bibliographic/bdleader.html";
175 } else if ( tag >= '090' && tag < '100' ) {
176 return "http://www.loc.gov/marc/bibliographic/bd09x.html";
177 } else if ( tag < '900' ) {
178 return "http://www.loc.gov/marc/bibliographic/bd" + tag + ".html";
180 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
182 [% ELSIF ( marcflavour == 'UNIMARC' ) %]
183 /* http://archive.ifla.org/VI/3/p1996-1/ is an outdated version of UNIMARC, but
184 seems to be the only version available that can be linked to per tag. More recent
185 versions of the UNIMARC standard are available on the IFLA website only as
188 if ( tag == '000' ) {
189 return "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
192 var url = "http://archive.ifla.org/VI/3/p1996-1/uni" + first + ".htm#";
193 if ( first == '0' ) url += "b";
194 if ( first != '9' ) url += tag;
204 titleForRecord: _("Editing new record"),
205 get: function( id, callback ) {
206 record = new MARC.Record();
207 KohaBackend.FillRecord( '', record );
213 titleForRecord: _("Editing new full record"),
214 get: function( id, callback ) {
215 record = new MARC.Record();
216 KohaBackend.FillRecord( '', record, true );
222 titleForRecord: _("Editing catalog record #{ID}"),
224 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
225 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
227 saveLabel: _("Save to catalog"),
228 get: function( id, callback ) {
229 if ( !id ) return false;
231 KohaBackend.GetRecord( id, callback );
233 save: function( id, record, done ) {
234 function finishCb( data ) {
235 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
239 KohaBackend.SaveRecord( id, record, finishCb );
241 KohaBackend.CreateRecord( record, finishCb );
246 saveLabel: _("Save as ISO2709 (.mrc) file"),
247 save: function( id, record, done ) {
248 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.mrc' );
254 saveLabel: _("Save as MARCXML (.xml) file"),
255 save: function( id, record, done ) {
256 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.xml' );
262 titleForRecord: _("Editing search result"),
263 get: function( id, callback ) {
264 if ( !id ) return false;
265 if ( !backends.search.records[ id ] ) {
266 callback( { error: _( "Invalid record" ) } );
270 callback( backends.search.records[ id ] );
276 function setSource(parts) {
277 state.backend = parts[0];
278 state.recordID = parts[1];
279 state.canSave = backends[ state.backend ].save != null;
280 state.saveBackend = state.canSave ? state.backend : 'catalog';
282 var backend = backends[state.backend];
284 document.location.hash = '#' + parts[0] + '/' + parts[1];
286 $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
288 $.each( backend.links || [], function( i, link ) {
289 $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
291 $( 'title', document.head ).html( _("Koha › Cataloging › ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
292 $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
295 function saveRecord( recid, editor, callback ) {
296 var parts = recid.split('/');
297 if ( parts.length != 2 ) return false;
299 if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
301 editor.removeErrors();
302 var record = editor.getRecord();
304 if ( record.errors ) {
305 state.saving = false;
306 callback( { error: 'syntax', errors: record.errors } );
310 var errors = KohaBackend.ValidateRecord( '', record );
311 if ( errors.length ) {
312 state.saving = false;
313 callback( { error: 'invalid', errors: errors } );
317 backends[ parts[0] ].save( parts[1], record, function(data) {
318 state.saving = false;
320 if (data.newRecord) {
321 var record = new MARC.Record();
322 record.loadMARCXML(data.newRecord);
323 editor.displayRecord( record );
327 setSource(data.newId);
329 setSource( [ state.backend, state.recordID ] );
332 if (callback) callback( data );
336 function loadRecord( recid, editor, callback ) {
337 var parts = recid.split('/');
338 if ( parts.length != 2 ) return false;
340 if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
342 backends[ parts[0] ].get( parts[1], function( record ) {
343 if ( !record.error ) {
344 editor.displayRecord( record );
348 if (callback) callback(record);
354 function openRecord( recid, editor, callback ) {
355 return loadRecord( recid, editor, function ( record ) {
356 setSource( recid.split('/') );
358 if (callback) callback( record );
363 function showAdvancedSearch() {
364 $('#advanced-search-servers').empty();
365 $.each( z3950Servers, function( server_id, server ) {
366 $('#advanced-search-servers').append( '<li data-server-id="' + server_id + '"><label><input class="search-toggle-server" type="checkbox"' + ( server.checked ? ' checked="checked">' : '>' ) + server.name + '</label></li>' );
368 $('#advanced-search-ui').modal('show');
371 function startAdvancedSearch() {
374 $('#advanced-search-ui .search-box').each( function() {
375 if ( !this.value ) return;
377 terms.push( [ $(this).data('qualifier'), this.value ] );
380 if ( !terms.length ) return;
382 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
383 $('#advanced-search-ui').modal('hide');
384 $("#search-overlay").show();
389 function showResultsBox(data) {
390 $('#search-top-pages, #search-bottom-pages').find('.pagination').empty();
391 $('#searchresults thead tr').empty();
392 $('#searchresults tbody').empty();
393 $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
394 $('#search-results-ui').modal('show');
397 function showSearchSorting( sort_key, sort_direction ) {
398 var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
399 $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
401 if ( sort_direction == 'asc' ) {
403 $th.attr( 'class', 'sorting_asc' );
406 $th.attr( 'class', 'sorting_desc' );
410 function showSearchResults( editor, data ) {
411 backends.search.records = {};
413 $('#searchresults thead tr').empty();
414 $('#searchresults tbody').empty();
415 $('#search-serversinfo').empty();
417 $.each( z3950Servers, function( server_id, server ) {
418 var num_fetched = data.num_fetched[server_id];
420 if ( data.errors[server_id] ) {
421 num_fetched = data.errors[server_id];
422 } else if ( num_fetched == null ) {
424 } else if ( num_fetched < data.num_hits[server_id] ) {
428 $('#search-serversinfo').append( '<li data-server-id="' + server_id + '"><label><input class="search-toggle-server" type="checkbox"' + ( server.checked ? ' checked="checked">' : '>' ) + server.name + ' (' + num_fetched + ')' + '</label></li>' );
431 var seenColumns = {};
433 $.each( data.hits, function( undef, hit ) {
434 $.each( hit.metadata, function(key) {
435 seenColumns[key] = true;
439 $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
441 $.each( z3950Labels, function( undef, label ) {
442 if ( seenColumns[ label[0] ] ) {
443 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
447 showSearchSorting( data.sort_key, data.sort_direction );
449 $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
451 var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
452 $.each( data.hits, function( undef, hit ) {
453 backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
455 switch ( hit.server ) {
456 case 'koha:biblioserver':
457 var bibnumField = hit.record.field( bibnumMap[0] );
459 if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
460 hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
464 // Otherwise, fallthrough
467 hit.id = 'search/' + hit.server + ':' + hit.index;
471 result += '<td class="sourcecol">' + z3950Servers[ hit.server ].name + '</td>';
473 $.each( z3950Labels, function( undef, label ) {
474 if ( !seenColumns[ label[0] ] ) return;
476 if ( hit.metadata[ label[0] ] ) {
477 result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
479 result += '<td class="infocol"> </td>';
483 result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
484 result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
485 if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
486 result += '</ul></td></tr>';
488 var $tr = $( result );
489 $tr.find( '.marc-link' ).click( function() {
490 var $info_columns = $tr.find( '.infocol' );
491 var $marc_column = $tr.find( '.marccol' );
493 if ( !$marc_column.length ) {
494 $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
495 CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
498 if ( $marc_column.is(':visible') ) {
499 $tr.find('.marc-link').text( _("View MARC") );
500 $info_columns.show();
503 $tr.find('.marc-link').text( _("Hide MARC") );
505 $info_columns.hide();
510 $tr.find( '.open-link' ).click( function() {
511 $( '#search-results-ui' ).modal('hide');
512 openRecord( hit.id, editor );
516 $tr.find( '.substitute-link' ).click( function() {
517 $( '#search-results-ui' ).modal('hide');
518 loadRecord( hit.id, editor );
522 $('#searchresults tbody').append( $tr );
526 var cur_page = data.offset / data.page_size;
527 var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
529 if ( cur_page != 0 ) {
530 pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '">« ' + _("Previous") + '</a></li>' );
533 for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
534 if ( page == cur_page ) {
535 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
537 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
541 if ( cur_page < max_page ) {
542 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' »</a></li>' );
545 $( '#search-top-pages, #search-bottom-pages' ).find( '.pagination' ).html( pages.length > 1 ? ( '<ul>' + pages.join( '' ) + '</ul>' ) : '' );
547 var $overlay = $('#search-overlay');
548 $overlay.find('span').text(_("Loading"));
549 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
551 if ( data.activeclients ) {
552 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
555 $overlay.find('.bar').css( { display: 'block', width: '100%' } );
557 $('#searchresults')[0].focus();
561 function invalidateSearchResults() {
562 var $overlay = $('#search-overlay');
563 $overlay.find('span').text(_("Search expired, please try again"));
564 $overlay.find('.bar').css( { display: 'none' } );
568 function handleSearchError(error) {
569 if (error.code == 1) {
570 invalidateSearchResults();
573 humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
577 function handleSearchInitError(error) {
578 $('#quicksearch-overlay').fadeIn().find('p').text(error);
581 // Preference functions
582 function showPreference( pref ) {
583 var value = Preferences.user[pref];
587 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
590 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
594 $( '#editor .CodeMirror' ).css( { fontSize: value } );
598 // Macros loaded on first show of modal
600 case 'selected_search_targets':
601 $.each( z3950Servers, function( server_id, server ) {
602 var saved_val = Preferences.user.selected_search_targets[server_id];
604 if ( saved_val != null ) server.checked = saved_val;
610 function bindPreference( editor, pref ) {
611 function _addHandler( sel, event, handler ) {
612 $( sel ).on( event, function (e) {
614 handler( e, Preferences.user[pref] );
615 Preferences.Save( [% USER_INFO.borrowernumber %] );
616 showPreference(pref);
622 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
623 editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
627 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
628 Preferences.user.font = $( e.target ).css( 'font-family' );
632 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
633 Preferences.user.fontSize = $( e.target ).css( 'font-size' );
636 case 'selected_search_targets':
637 $( document ).on( 'change', 'input.search-toggle-server', function() {
638 var server_id = $( this ).closest('li').data('server-id');
639 Preferences.user.selected_search_targets[server_id] = this.checked;
640 Preferences.Save( [% USER_INFO.borrowernumber %] );
646 function displayPreferences( editor ) {
647 $.each( Preferences.user, function( pref, value ) {
648 showPreference( pref );
649 bindPreference( editor, pref );
654 function loadMacro( name ) {
655 $( '#macro-list li' ).removeClass( 'active' );
656 macroEditor.activeMacro = name;
659 macroEditor.setValue( '' );
663 $( '#macro-list li[data-name="' + name + '"]' ).addClass( 'active' );
664 var macro = Preferences.user.macros[name];
665 macroEditor.setValue( macro.contents );
666 macroEditor.setOption( 'readOnly', false );
667 $( '#macro-format' ).val( macro.format || 'its' );
668 if ( macro.history ) macroEditor.setHistory( macro.history );
671 function storeMacro( name, macro ) {
673 Preferences.user.macros[name] = macro;
675 delete Preferences.user.macros[name];
678 Preferences.Save( [% USER_INFO.borrowernumber %] );
681 function showSavedMacros( macros ) {
682 var scrollTop = $('#macro-list').scrollTop();
683 $( '#macro-list' ).empty();
684 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
685 return $.extend( { name: name }, macro );
687 macro_list.sort( function( a, b ) {
688 return a.name.localeCompare(b.name);
690 $.each( macro_list, function( undef, macro ) {
691 var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
692 $li.click( function() {
693 loadMacro(macro.name);
696 if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
697 var modified = macro.modified && new Date(macro.modified);
698 $li.find( '.macro-info' ).append(
699 '<li><span class="label">' + _("Last changed:") + '</span>' +
700 ( modified ? ( modified.toLocaleDateString() + ', ' + modified.toLocaleTimeString() ) : _("never") ) + '</li>'
702 $('#macro-list').append($li);
704 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
705 $new_li.click( function() {
706 // TODO: make this a bit less retro
707 var name = prompt(_("Please enter the name for the new macro:"));
710 if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
714 $('#macro-list').append($new_li);
715 $('#macro-list').scrollTop(scrollTop);
718 function saveMacro() {
719 var name = macroEditor.activeMacro;
721 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
723 macroEditor.savedGeneration = macroEditor.changeGeneration();
724 storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory(), format: $('#macro-format').val() } );
725 $('#macro-save-message').text(_("Saved"));
729 $(document).ready( function() {
731 editor = new MARCEditor( {
732 onCursorActivity: function() {
733 $('#status-tag-info').empty();
734 $('#status-subfield-info').empty();
736 var field = editor.getCurrentField();
737 var cur = editor.getCursor();
739 if ( !field ) return;
741 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
742 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
745 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
747 var subfield = field.getSubfieldAt( cur.ch );
748 if ( !subfield ) return;
750 var subfieldinfo = taginfo.subfields[ subfield.code ];
751 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
753 if ( subfieldinfo ) {
754 $('#status-subfield-info').append( subfieldinfo.lib );
756 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
759 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
762 position: function (elt) { $(elt).insertAfter('#toolbar') },
765 // Automatically detect resizes and change the height of the editor and position of modals.
766 var resizeTimer = null;
767 function onResize() {
768 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
771 var pos = $('#editor .CodeMirror').position();
772 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
774 $('.modal-body').each( function() {
775 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
779 $("#advanced-search-ui, #search-results-ui, #macro-ui").css( {
780 marginLeft: function() {
781 return -($(this).width() / 2);
786 $( '#macro-ui' ).on( 'shown', function() {
787 if ( macroEditor ) return;
789 macroEditor = CodeMirror(
790 $('#macro-editor')[0],
793 'Ctrl-D': function( cm ) {
794 var cur = cm.getCursor();
796 cm.replaceRange( "‡", cur, null );
805 macroEditor.on( 'change', function( cm, change ) {
806 $('#macro-save-message').empty();
807 if ( change.origin == 'setValue' ) return;
809 if ( saveTimeout ) clearTimeout( saveTimeout );
810 saveTimeout = setTimeout( function() {
820 var saveableBackends = [];
821 $.each( backends, function( id, backend ) {
822 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
824 saveableBackends.sort();
825 $.each( saveableBackends, function( undef, backend ) {
826 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
829 var macro_format_list = $.map( Macros.formats, function( format, name ) {
830 return $.extend( { name: name }, format );
832 macro_format_list.sort( function( a, b ) {
833 return a.description.localeCompare(b.description);
835 $.each( macro_format_list, function() {
836 $('#macro-format').append( '<option value="' + this.name + '">' + this.description + '</option>' );
840 $( '#save-record, #save-dropdown a' ).click( function() {
841 $( '#save-record' ).find('i').attr( 'class', 'icon-loading' ).siblings( 'span' ).text( _("Saving...") );
843 function finishCb(result) {
844 if ( result.error == 'syntax' ) {
845 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
846 } else if ( result.error == 'invalid' ) {
847 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
848 } else if ( !result.error ) {
849 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
852 $.each( result.errors || [], function( undef, error ) {
853 switch ( error.type ) {
855 editor.addError( error.line, _("Invalid tag number") );
858 editor.addError( error.line, _("Invalid indicators") );
861 editor.addError( error.line, _("Tag has no subfields") );
864 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
866 case 'missingSubfield':
867 if ( error.subfield == '@' ) {
868 editor.addError( error.line, _("Missing control field contents") );
870 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
873 case 'unrepeatableTag':
874 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
876 case 'unrepeatableSubfield':
877 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
879 case 'itemTagUnsupported':
880 editor.addError( error.line, _("Item tags cannot currently be saved") );
885 $( '#save-record' ).find('i').attr( 'class', 'icon-hdd' );
887 if ( result.error ) {
888 // Reset backend info
889 setSource( [ state.backend, state.recordID ] );
893 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
894 if ( state.backend == backend ) {
895 saveRecord( backend + '/' + state.recordID, editor, finishCb );
897 saveRecord( backend + '/', editor, finishCb );
903 $('#import-records').click( function() {
904 $('#import-records-input')
906 .change( function() {
907 if ( !this.files || !this.files.length ) return;
909 var file = this.files[0];
910 var reader = new FileReader();
912 reader.onload = function() {
913 var record = new MARC.Record();
915 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
916 record.loadISO2709( reader.result );
917 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
918 record.loadMARCXML( reader.result );
920 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
924 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
926 editor.displayRecord( record );
929 reader.readAsText( file );
936 $('#open-macros').click( function() {
937 $('#macro-ui').modal('show');
942 $('#run-macro').click( function() {
943 var result = Macros.Run( editor, $('#macro-format').val(), macroEditor.getValue() );
945 if ( !result.errors.length ) {
946 $('#macro-ui').modal('hide');
951 $.each( result.errors, function() {
952 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
954 switch ( this.error ) {
955 case 'failed': error += _("failed to run"); break;
956 case 'unrecognized': error += _("unrecognized command"); break;
962 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
967 $('#delete-macro').click( function() {
968 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
970 storeMacro( macroEditor.activeMacro, undefined );
972 loadMacro( undefined );
977 $( '#switch-editor' ).click( function() {
978 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
980 $.cookie( 'catalogue_editor_[% USER_INFO.borrowernumber %]', 'basic', { expires: 365, path: '/' } );
982 if ( state.backend == 'catalog' ) {
983 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
984 } else if ( state.backend == 'new' ) {
985 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
987 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
991 $( '#show-advanced-search' ).click( function() {
992 showAdvancedSearch();
997 $('#advanced-search').submit( function() {
998 startAdvancedSearch();
1003 $( document ).on( 'click', 'a.search-nav', function() {
1004 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1005 $("#search-overlay").show();
1011 $( document ).on( 'click', 'th[data-sort-label]', function() {
1014 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1020 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1021 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1023 $("#search-overlay").show();
1029 $( document ).on( 'change', 'input.search-toggle-server', function() {
1030 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1031 server.checked = this.checked;
1033 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1034 $("#search-overlay").show();
1042 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1043 $(this).modal({ show: false });
1046 var $quicksearch = $('#quicksearch fieldset');
1047 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1048 position: 'absolute',
1049 top: $quicksearch.offset().top,
1050 left: $quicksearch.offset().left,
1051 height: $quicksearch.outerHeight(),
1052 width: $quicksearch.outerWidth(),
1053 }).appendTo(document.body).hide();
1055 var prevAlerts = [];
1056 humanMsg.logMsg = function(msg, options) {
1057 $('#show-alerts').popover('hide');
1058 prevAlerts.unshift('<li>' + msg + '</li>');
1059 prevAlerts.splice(5, 999); // Truncate old messages
1062 $('#show-alerts').popover({
1064 placement: 'bottom',
1065 content: function() {
1066 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1070 $('#show-shortcuts').popover({
1072 placement: 'bottom',
1073 content: function() {
1074 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1078 $('#new-record' ).click( function() {
1079 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1081 openRecord( 'new/', editor );
1086 Preferences.Load( [% USER_INFO.borrowernumber || 0 %] );
1087 displayPreferences(editor);
1088 makeAuthorisedValueWidgets( '' );
1091 onresults: function(data) { showSearchResults( editor, data ) },
1092 onerror: handleSearchError,
1095 function finishCb( data ) {
1096 if ( data.error ) openRecord( 'new/', editor, finishCb );
1098 Resources.GetAll().done( function() {
1099 $("#loading").hide();
1100 $( window ).resize( onResize ).resize();
1105 if ( "[% auth_forwarded_hash %]" ) {
1106 document.location.hash = "[% auth_forwarded_hash %]";
1109 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1110 openRecord( 'new/', editor, finishCb );